1 #ifndef GAMEMODE_ASSAULT_H
2 #define GAMEMODE_ASSAULT_H
5 .entity assault_decreaser;
6 .entity assault_sprite;
9 const int HAVOCBOT_AST_ROLE_NONE = 0;
10 const int HAVOCBOT_AST_ROLE_DEFENSE = 2;
11 const int HAVOCBOT_AST_ROLE_OFFENSE = 4;
13 .int havocbot_role_flags;
14 .float havocbot_attack_time;
16 .void() havocbot_role;
17 .void() havocbot_previous_role;
19 void() havocbot_role_ast_defense;
20 void() havocbot_role_ast_offense;
21 .entity havocbot_ast_target;
23 void(entity bot) havocbot_ast_reset_role;
25 void(float ratingscale, vector org, float sradius) havocbot_goalrating_items;
26 void(float ratingscale, vector org, float sradius) havocbot_goalrating_enemyplayers;
29 const float ST_ASSAULT_OBJECTIVES = 1;
30 const float SP_ASSAULT_OBJECTIVES = 4;
32 // predefined spawnfuncs
33 void target_objective_decrease_activate();
40 void assault_objective_use()
44 //print("^2Activated objective ", self.targetname, "=", etos(self), "\n");
45 //print("Activator is ", activator.classname, "\n");
47 for (entity e = world; (e = find(e, target, this.targetname)); )
49 if (e.classname == "target_objective_decrease")
51 WITH(entity, self, e, target_objective_decrease_activate());
58 vector target_objective_spawn_evalfunc(entity player, entity spot, vector current)
60 if(self.health < 0 || self.health >= ASSAULT_VALUE_INACTIVE)
65 // reset this objective. Used when spawning an objective
66 // and when a new round starts
67 void assault_objective_reset()
69 self.health = ASSAULT_VALUE_INACTIVE;
72 // decrease the health of targeted objectives
73 void assault_objective_decrease_use()
75 if(activator.team != assault_attacker_team)
77 // wrong team triggered decrease
81 if(other.assault_sprite)
83 WaypointSprite_Disown(other.assault_sprite, waypointsprite_deadlifetime);
84 if(other.classname == "func_assault_destructible")
88 return; // already activated! cannot activate again!
90 if(self.enemy.health < ASSAULT_VALUE_INACTIVE)
92 if(self.enemy.health - self.dmg > 0.5)
94 PlayerTeamScore_Add(activator, SP_SCORE, ST_SCORE, self.dmg);
95 self.enemy.health = self.enemy.health - self.dmg;
99 PlayerTeamScore_Add(activator, SP_SCORE, ST_SCORE, self.enemy.health);
100 PlayerTeamScore_Add(activator, SP_ASSAULT_OBJECTIVES, ST_ASSAULT_OBJECTIVES, 1);
101 self.enemy.health = -1;
103 entity oldactivator, head;
107 FOR_EACH_PLAYER(head)
108 centerprint(head, self.message);
110 oldactivator = activator;
113 activator = oldactivator;
119 void assault_setenemytoobjective()
122 for(objective = world; (objective = find(objective, targetname, self.target)); )
124 if(objective.classname == "target_objective")
126 if(self.enemy == world)
127 self.enemy = objective;
129 objerror("more than one objective as target - fix the map!");
134 if(self.enemy == world)
135 objerror("no objective as target - fix the map!");
138 float assault_decreaser_sprite_visible(entity e)
142 decreaser = self.assault_decreaser;
144 if(decreaser.enemy.health >= ASSAULT_VALUE_INACTIVE)
150 void target_objective_decrease_activate()
154 for(ent = world; (ent = find(ent, target, self.targetname)); )
156 if(ent.assault_sprite != world)
158 WaypointSprite_Disown(ent.assault_sprite, waypointsprite_deadlifetime);
159 if(ent.classname == "func_assault_destructible")
163 spr = WaypointSprite_SpawnFixed(WP_Assault, 0.5 * (ent.absmin + ent.absmax), ent, assault_sprite, RADARICON_OBJECTIVE);
164 spr.assault_decreaser = self;
165 spr.waypointsprite_visible_for_player = assault_decreaser_sprite_visible;
166 spr.classname = "sprite_waypoint";
167 WaypointSprite_UpdateRule(spr, assault_attacker_team, SPRITERULE_TEAMPLAY);
168 if(ent.classname == "func_assault_destructible")
170 WaypointSprite_UpdateSprites(spr, WP_AssaultDefend, WP_AssaultDestroy, WP_AssaultDestroy);
171 WaypointSprite_UpdateMaxHealth(spr, ent.max_health);
172 WaypointSprite_UpdateHealth(spr, ent.health);
176 WaypointSprite_UpdateSprites(spr, WP_AssaultDefend, WP_AssaultPush, WP_AssaultPush);
180 void target_objective_decrease_findtarget()
182 assault_setenemytoobjective();
185 void target_assault_roundend_reset()
187 //print("round end reset\n");
188 self.cnt = self.cnt + 1; // up round counter
189 self.winning = 0; // up round
192 void target_assault_roundend_use()
194 self.winning = 1; // round has been won by attackers
197 void assault_roundstart_use()
202 //(Re)spawn all turrets
203 for(entity ent = NULL; (ent = find(ent, classname, "turret_main")); ) {
205 if(ent.team == NUM_TEAM_1)
206 ent.team = NUM_TEAM_2;
208 ent.team = NUM_TEAM_1;
210 // Dubbles as teamchange
211 WITH(entity, self, ent, turret_respawn());
215 void assault_wall_think()
217 if(self.enemy.health < 0)
220 self.solid = SOLID_NOT;
224 self.model = self.mdl;
225 self.solid = SOLID_BSP;
228 self.nextthink = time + 0.2;
232 // reset objectives, toggle spawnpoints, reset triggers, ...
233 void vehicles_clearreturn(entity veh);
234 void vehicles_spawn();
235 void assault_new_round()
237 //bprint("ASSAULT: new round\n");
239 // Eject players from vehicles
245 WITH(entity, self, e, vehicles_exit(VHEF_RELEASE));
249 for (entity e_ = findchainflags(vehicle_flags, VHF_ISVEHICLE); e_; e_ = e_.chain)
252 vehicles_clearreturn(self);
259 self.winning = self.winning + 1;
261 // swap attacker/defender roles
262 if(assault_attacker_team == NUM_TEAM_1)
263 assault_attacker_team = NUM_TEAM_2;
265 assault_attacker_team = NUM_TEAM_1;
268 for(ent = world; (ent = nextent(ent)); )
270 if(clienttype(ent) == CLIENTTYPE_NOTACLIENT)
272 if(ent.team_saved == NUM_TEAM_1)
273 ent.team_saved = NUM_TEAM_2;
274 else if(ent.team_saved == NUM_TEAM_2)
275 ent.team_saved = NUM_TEAM_1;
279 // reset the level with a countdown
280 cvar_set("timelimit", ftos(ceil(time - game_starttime) / 60));
281 ReadyRestart_force(); // sets game_starttime
285 spawnfunc(info_player_attacker)
287 if (!g_assault) { remove(self); return; }
289 self.team = NUM_TEAM_1; // red, gets swapped every round
290 spawnfunc_info_player_deathmatch(this);
293 spawnfunc(info_player_defender)
295 if (!g_assault) { remove(self); return; }
297 self.team = NUM_TEAM_2; // blue, gets swapped every round
298 spawnfunc_info_player_deathmatch(this);
301 spawnfunc(target_objective)
303 if (!g_assault) { remove(self); return; }
305 self.classname = "target_objective";
306 self.use = assault_objective_use;
307 assault_objective_reset();
308 self.reset = assault_objective_reset;
309 self.spawn_evalfunc = target_objective_spawn_evalfunc;
312 spawnfunc(target_objective_decrease)
314 if (!g_assault) { remove(self); return; }
316 self.classname = "target_objective_decrease";
321 self.use = assault_objective_decrease_use;
322 self.health = ASSAULT_VALUE_INACTIVE;
323 self.max_health = ASSAULT_VALUE_INACTIVE;
326 InitializeEntity(self, target_objective_decrease_findtarget, INITPRIO_FINDTARGET);
329 // destructible walls that can be used to trigger target_objective_decrease
330 spawnfunc(func_breakable);
331 spawnfunc(func_assault_destructible)
333 if (!g_assault) { remove(self); return; }
336 self.classname = "func_assault_destructible";
338 if(assault_attacker_team == NUM_TEAM_1)
339 self.team = NUM_TEAM_2;
341 self.team = NUM_TEAM_1;
343 spawnfunc_func_breakable(this);
346 spawnfunc(func_assault_wall)
348 if (!g_assault) { remove(self); return; }
350 self.classname = "func_assault_wall";
351 self.mdl = self.model;
352 _setmodel(self, self.mdl);
353 self.solid = SOLID_BSP;
354 self.think = assault_wall_think;
355 self.nextthink = time;
356 InitializeEntity(self, assault_setenemytoobjective, INITPRIO_FINDTARGET);
359 spawnfunc(target_assault_roundend)
361 if (!g_assault) { remove(self); return; }
363 self.winning = 0; // round not yet won by attackers
364 self.classname = "target_assault_roundend";
365 self.use = target_assault_roundend_use;
366 self.cnt = 0; // first round
367 self.reset = target_assault_roundend_reset;
370 spawnfunc(target_assault_roundstart)
372 if (!g_assault) { remove(self); return; }
374 assault_attacker_team = NUM_TEAM_1;
375 self.classname = "target_assault_roundstart";
376 self.use = assault_roundstart_use;
377 self.reset2 = assault_roundstart_use;
378 InitializeEntity(self, assault_roundstart_use, INITPRIO_FINDTARGET);
382 void havocbot_goalrating_ast_targets(float ratingscale)
384 entity ad, best, wp, tod;
385 float radius, found, bestvalue;
388 ad = findchain(classname, "func_assault_destructible");
390 for (; ad; ad = ad.chain)
399 for(tod = world; (tod = find(tod, targetname, ad.target)); )
401 if(tod.classname == "target_objective_decrease")
403 if(tod.enemy.health > 0 && tod.enemy.health < ASSAULT_VALUE_INACTIVE)
405 // dprint(etos(ad),"\n");
414 /// dprint("target not found\n");
417 /// dprint("target #", etos(ad), " found\n");
420 p = 0.5 * (ad.absmin + ad.absmax);
421 // dprint(vtos(ad.origin), " ", vtos(ad.absmin), " ", vtos(ad.absmax),"\n");
422 // te_knightspike(p);
423 // te_lightning2(world, '0 0 0', p);
425 // Find and rate waypoints around it
428 bestvalue = 99999999999;
429 for(radius=0; radius<1500 && !found; radius+=500)
431 for(wp=findradius(p, radius); wp; wp=wp.chain)
433 if(!(wp.wpflags & WAYPOINTFLAG_GENERATED))
434 if(wp.classname=="waypoint")
435 if(checkpvs(wp.origin, ad))
449 /// dprint("waypoints around target were found\n");
450 // te_lightning2(world, '0 0 0', best.origin);
451 // te_knightspike(best.origin);
453 navigation_routerating(best, ratingscale, 4000);
456 self.havocbot_attack_time = 0;
458 if(checkpvs(self.view_ofs,ad))
459 if(checkpvs(self.view_ofs,best))
461 // dprint("increasing attack time for this target\n");
462 self.havocbot_attack_time = time + 2;
468 void havocbot_role_ast_offense()
470 if(self.deadflag != DEAD_NO)
472 self.havocbot_attack_time = 0;
473 havocbot_ast_reset_role(self);
477 // Set the role timeout if necessary
478 if (!self.havocbot_role_timeout)
479 self.havocbot_role_timeout = time + 120;
481 if (time > self.havocbot_role_timeout)
483 havocbot_ast_reset_role(self);
487 if(self.havocbot_attack_time>time)
490 if (self.bot_strategytime < time)
492 navigation_goalrating_start();
493 havocbot_goalrating_enemyplayers(20000, self.origin, 650);
494 havocbot_goalrating_ast_targets(20000);
495 havocbot_goalrating_items(15000, self.origin, 10000);
496 navigation_goalrating_end();
498 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
502 void havocbot_role_ast_defense()
504 if(self.deadflag != DEAD_NO)
506 self.havocbot_attack_time = 0;
507 havocbot_ast_reset_role(self);
511 // Set the role timeout if necessary
512 if (!self.havocbot_role_timeout)
513 self.havocbot_role_timeout = time + 120;
515 if (time > self.havocbot_role_timeout)
517 havocbot_ast_reset_role(self);
521 if(self.havocbot_attack_time>time)
524 if (self.bot_strategytime < time)
526 navigation_goalrating_start();
527 havocbot_goalrating_enemyplayers(20000, self.origin, 3000);
528 havocbot_goalrating_ast_targets(20000);
529 havocbot_goalrating_items(15000, self.origin, 10000);
530 navigation_goalrating_end();
532 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
536 void havocbot_role_ast_setrole(entity bot, float role)
540 case HAVOCBOT_AST_ROLE_DEFENSE:
541 bot.havocbot_role = havocbot_role_ast_defense;
542 bot.havocbot_role_flags = HAVOCBOT_AST_ROLE_DEFENSE;
543 bot.havocbot_role_timeout = 0;
545 case HAVOCBOT_AST_ROLE_OFFENSE:
546 bot.havocbot_role = havocbot_role_ast_offense;
547 bot.havocbot_role_flags = HAVOCBOT_AST_ROLE_OFFENSE;
548 bot.havocbot_role_timeout = 0;
553 void havocbot_ast_reset_role(entity bot)
555 if(self.deadflag != DEAD_NO)
558 if(bot.team == assault_attacker_team)
559 havocbot_role_ast_setrole(bot, HAVOCBOT_AST_ROLE_OFFENSE);
561 havocbot_role_ast_setrole(bot, HAVOCBOT_AST_ROLE_DEFENSE);
565 MUTATOR_HOOKFUNCTION(as, PlayerSpawn)
567 if(self.team == assault_attacker_team)
568 Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_ASSAULT_ATTACKING);
570 Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_ASSAULT_DEFENDING);
575 MUTATOR_HOOKFUNCTION(as, TurretSpawn)
577 if(!self.team || self.team == MAX_SHOT_DISTANCE)
578 self.team = 5; // this gets reversed when match starts?
583 MUTATOR_HOOKFUNCTION(as, VehicleSpawn)
585 self.nextthink = time + 0.5;
590 MUTATOR_HOOKFUNCTION(as, HavocBot_ChooseRole)
592 havocbot_ast_reset_role(self);
596 MUTATOR_HOOKFUNCTION(as, PlayHitsound)
598 return (frag_victim.classname == "func_assault_destructible");
601 MUTATOR_HOOKFUNCTION(as, GetTeamCount)
603 // assault always has 2 teams
608 MUTATOR_HOOKFUNCTION(as, CheckRules_World)
610 ret_float = WinningCondition_Assault();
614 MUTATOR_HOOKFUNCTION(as, ReadLevelCvars)
616 // no assault warmups
621 MUTATOR_HOOKFUNCTION(as, OnEntityPreSpawn)
623 switch(self.classname)
625 case "info_player_team1":
626 case "info_player_team2":
627 case "info_player_team3":
628 case "info_player_team4":
636 void assault_ScoreRules()
638 ScoreRules_basics(2, SFL_SORT_PRIO_SECONDARY, SFL_SORT_PRIO_SECONDARY, true);
639 ScoreInfo_SetLabel_TeamScore( ST_ASSAULT_OBJECTIVES, "objectives", SFL_SORT_PRIO_PRIMARY);
640 ScoreInfo_SetLabel_PlayerScore(SP_ASSAULT_OBJECTIVES, "objectives", SFL_SORT_PRIO_PRIMARY);
641 ScoreRules_basics_end();
644 REGISTER_MUTATOR(as, g_assault)
647 have_team_spawns = -1; // request team spawns
651 if(time > 1) // game loads at time 1
652 error("This is a game type and it cannot be added at runtime.");
653 assault_ScoreRules();
656 MUTATOR_ONROLLBACK_OR_REMOVE
658 // we actually cannot roll back assault_Initialize here
659 // BUT: we don't need to! If this gets called, adding always
665 LOG_INFO("This is a game type and it cannot be removed at runtime.");