]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/mutators/gamemode_assault.qc
Remove oldself variables
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / mutators / gamemode_assault.qc
1 #include "gamemode_assault.qh"
2 #include "../_all.qh"
3
4 #include "gamemode.qh"
5
6 .entity sprite;
7
8 // random functions
9 void assault_objective_use()
10 {SELFPARAM();
11         // activate objective
12         self.health = 100;
13         //print("^2Activated objective ", self.targetname, "=", etos(self), "\n");
14         //print("Activator is ", activator.classname, "\n");
15
16         for (entity e = world; (e = find(e, target, this.targetname)); )
17         {
18                 if (e.classname == "target_objective_decrease")
19                 {
20                         SELFCALL(e, target_objective_decrease_activate());
21                         SELFCALL_DONE();
22                 }
23         }
24
25         setself(this);
26 }
27
28 vector target_objective_spawn_evalfunc(entity player, entity spot, vector current)
29 {SELFPARAM();
30         if(self.health < 0 || self.health >= ASSAULT_VALUE_INACTIVE)
31                 return '-1 0 0';
32         return current;
33 }
34
35 // reset this objective. Used when spawning an objective
36 // and when a new round starts
37 void assault_objective_reset()
38 {SELFPARAM();
39         self.health = ASSAULT_VALUE_INACTIVE;
40 }
41
42 // decrease the health of targeted objectives
43 void assault_objective_decrease_use()
44 {SELFPARAM();
45         if(activator.team != assault_attacker_team)
46         {
47                 // wrong team triggered decrease
48                 return;
49         }
50
51         if(other.assault_sprite)
52         {
53                 WaypointSprite_Disown(other.assault_sprite, waypointsprite_deadlifetime);
54                 if(other.classname == "func_assault_destructible")
55                         other.sprite = world;
56         }
57         else
58                 return; // already activated! cannot activate again!
59
60         if(self.enemy.health < ASSAULT_VALUE_INACTIVE)
61         {
62                 if(self.enemy.health - self.dmg > 0.5)
63                 {
64                         PlayerTeamScore_Add(activator, SP_SCORE, ST_SCORE, self.dmg);
65                         self.enemy.health = self.enemy.health - self.dmg;
66                 }
67                 else
68                 {
69                         PlayerTeamScore_Add(activator, SP_SCORE, ST_SCORE, self.enemy.health);
70                         PlayerTeamScore_Add(activator, SP_ASSAULT_OBJECTIVES, ST_ASSAULT_OBJECTIVES, 1);
71                         self.enemy.health = -1;
72
73                         entity oldactivator, head;
74
75                         setself(this.enemy);
76                         if(self.message)
77                         FOR_EACH_PLAYER(head)
78                                 centerprint(head, self.message);
79
80                         oldactivator = activator;
81                         activator = this;
82                         SUB_UseTargets();
83                         activator = oldactivator;
84                         setself(this);
85                 }
86         }
87 }
88
89 void assault_setenemytoobjective()
90 {SELFPARAM();
91         entity objective;
92         for(objective = world; (objective = find(objective, targetname, self.target)); )
93         {
94                 if(objective.classname == "target_objective")
95                 {
96                         if(self.enemy == world)
97                                 self.enemy = objective;
98                         else
99                                 objerror("more than one objective as target - fix the map!");
100                         break;
101                 }
102         }
103
104         if(self.enemy == world)
105                 objerror("no objective as target - fix the map!");
106 }
107
108 float assault_decreaser_sprite_visible(entity e)
109 {SELFPARAM();
110         entity decreaser;
111
112         decreaser = self.assault_decreaser;
113
114         if(decreaser.enemy.health >= ASSAULT_VALUE_INACTIVE)
115                 return false;
116
117         return true;
118 }
119
120 void target_objective_decrease_activate()
121 {SELFPARAM();
122         entity ent, spr;
123         self.owner = world;
124         for(ent = world; (ent = find(ent, target, self.targetname)); )
125         {
126                 if(ent.assault_sprite != world)
127                 {
128                         WaypointSprite_Disown(ent.assault_sprite, waypointsprite_deadlifetime);
129                         if(ent.classname == "func_assault_destructible")
130                                 ent.sprite = world;
131                 }
132
133                 spr = WaypointSprite_SpawnFixed(WP_Assault, 0.5 * (ent.absmin + ent.absmax), ent, assault_sprite, RADARICON_OBJECTIVE);
134                 spr.assault_decreaser = self;
135                 spr.waypointsprite_visible_for_player = assault_decreaser_sprite_visible;
136                 spr.classname = "sprite_waypoint";
137                 WaypointSprite_UpdateRule(spr, assault_attacker_team, SPRITERULE_TEAMPLAY);
138                 if(ent.classname == "func_assault_destructible")
139                 {
140                         WaypointSprite_UpdateSprites(spr, WP_AssaultDefend, WP_AssaultDestroy, WP_AssaultDestroy);
141                         WaypointSprite_UpdateMaxHealth(spr, ent.max_health);
142                         WaypointSprite_UpdateHealth(spr, ent.health);
143                         ent.sprite = spr;
144                 }
145                 else
146                         WaypointSprite_UpdateSprites(spr, WP_AssaultDefend, WP_AssaultPush, WP_AssaultPush);
147         }
148 }
149
150 void target_objective_decrease_findtarget()
151 {
152         assault_setenemytoobjective();
153 }
154
155 void target_assault_roundend_reset()
156 {SELFPARAM();
157         //print("round end reset\n");
158         self.cnt = self.cnt + 1; // up round counter
159         self.winning = 0; // up round
160 }
161
162 void target_assault_roundend_use()
163 {SELFPARAM();
164         self.winning = 1; // round has been won by attackers
165 }
166
167 void assault_roundstart_use()
168 {SELFPARAM();
169         activator = self;
170         SUB_UseTargets();
171
172         //(Re)spawn all turrets
173         for(entity ent = NULL; (ent = find(ent, classname, "turret_main")); ) {
174                 // Swap turret teams
175                 if(ent.team == NUM_TEAM_1)
176                         ent.team = NUM_TEAM_2;
177                 else
178                         ent.team = NUM_TEAM_1;
179
180                 // Dubbles as teamchange
181                 SELFCALL(ent, turret_respawn());
182                 SELFCALL_DONE();
183         }
184 }
185
186 void assault_wall_think()
187 {SELFPARAM();
188         if(self.enemy.health < 0)
189         {
190                 self.model = "";
191                 self.solid = SOLID_NOT;
192         }
193         else
194         {
195                 self.model = self.mdl;
196                 self.solid = SOLID_BSP;
197         }
198
199         self.nextthink = time + 0.2;
200 }
201
202 // trigger new round
203 // reset objectives, toggle spawnpoints, reset triggers, ...
204 void vehicles_clearreturn(entity veh);
205 void vehicles_spawn();
206 void assault_new_round()
207 {SELFPARAM();
208         //bprint("ASSAULT: new round\n");
209
210         // Eject players from vehicles
211         entity e;
212     FOR_EACH_PLAYER(e)
213     {
214         if(e.vehicle)
215         {
216                 SELFCALL(e, vehicles_exit(VHEF_RELEASE));
217             SELFCALL_DONE();
218         }
219     }
220
221     for (entity e_ = findchainflags(vehicle_flags, VHF_ISVEHICLE); e_; e_ = e_.chain)
222     {
223         setself(e_);
224         vehicles_clearreturn(self);
225         vehicles_spawn();
226     }
227
228     setself(this);
229
230         // up round counter
231         self.winning = self.winning + 1;
232
233         // swap attacker/defender roles
234         if(assault_attacker_team == NUM_TEAM_1)
235                 assault_attacker_team = NUM_TEAM_2;
236         else
237                 assault_attacker_team = NUM_TEAM_1;
238
239         entity ent;
240         for(ent = world; (ent = nextent(ent)); )
241         {
242                 if(clienttype(ent) == CLIENTTYPE_NOTACLIENT)
243                 {
244                         if(ent.team_saved == NUM_TEAM_1)
245                                 ent.team_saved = NUM_TEAM_2;
246                         else if(ent.team_saved == NUM_TEAM_2)
247                                 ent.team_saved = NUM_TEAM_1;
248                 }
249         }
250
251         // reset the level with a countdown
252         cvar_set("timelimit", ftos(ceil(time - game_starttime) / 60));
253         ReadyRestart_force(); // sets game_starttime
254 }
255
256 // spawnfuncs
257 void spawnfunc_info_player_attacker()
258 {SELFPARAM();
259         if (!g_assault) { remove(self); return; }
260
261         self.team = NUM_TEAM_1; // red, gets swapped every round
262         spawnfunc_info_player_deathmatch();
263 }
264
265 void spawnfunc_info_player_defender()
266 {SELFPARAM();
267         if (!g_assault) { remove(self); return; }
268
269         self.team = NUM_TEAM_2; // blue, gets swapped every round
270         spawnfunc_info_player_deathmatch();
271 }
272
273 void spawnfunc_target_objective()
274 {SELFPARAM();
275         if (!g_assault) { remove(self); return; }
276
277         self.classname = "target_objective";
278         self.use = assault_objective_use;
279         assault_objective_reset();
280         self.reset = assault_objective_reset;
281         self.spawn_evalfunc = target_objective_spawn_evalfunc;
282 }
283
284 void spawnfunc_target_objective_decrease()
285 {SELFPARAM();
286         if (!g_assault) { remove(self); return; }
287
288         self.classname = "target_objective_decrease";
289
290         if(!self.dmg)
291                 self.dmg = 101;
292
293         self.use = assault_objective_decrease_use;
294         self.health = ASSAULT_VALUE_INACTIVE;
295         self.max_health = ASSAULT_VALUE_INACTIVE;
296         self.enemy = world;
297
298         InitializeEntity(self, target_objective_decrease_findtarget, INITPRIO_FINDTARGET);
299 }
300
301 // destructible walls that can be used to trigger target_objective_decrease
302 void spawnfunc_func_assault_destructible()
303 {SELFPARAM();
304         if (!g_assault) { remove(self); return; }
305
306         self.spawnflags = 3;
307         self.classname = "func_assault_destructible";
308
309         if(assault_attacker_team == NUM_TEAM_1)
310                 self.team = NUM_TEAM_2;
311         else
312                 self.team = NUM_TEAM_1;
313
314         spawnfunc_func_breakable();
315 }
316
317 void spawnfunc_func_assault_wall()
318 {SELFPARAM();
319         if (!g_assault) { remove(self); return; }
320
321         self.classname = "func_assault_wall";
322         self.mdl = self.model;
323         setmodel(self, self.mdl);
324         self.solid = SOLID_BSP;
325         self.think = assault_wall_think;
326         self.nextthink = time;
327         InitializeEntity(self, assault_setenemytoobjective, INITPRIO_FINDTARGET);
328 }
329
330 void spawnfunc_target_assault_roundend()
331 {SELFPARAM();
332         if (!g_assault) { remove(self); return; }
333
334         self.winning = 0; // round not yet won by attackers
335         self.classname = "target_assault_roundend";
336         self.use = target_assault_roundend_use;
337         self.cnt = 0; // first round
338         self.reset = target_assault_roundend_reset;
339 }
340
341 void spawnfunc_target_assault_roundstart()
342 {SELFPARAM();
343         if (!g_assault) { remove(self); return; }
344
345         assault_attacker_team = NUM_TEAM_1;
346         self.classname = "target_assault_roundstart";
347         self.use = assault_roundstart_use;
348         self.reset2 = assault_roundstart_use;
349         InitializeEntity(self, assault_roundstart_use, INITPRIO_FINDTARGET);
350 }
351
352 // legacy bot code
353 void havocbot_goalrating_ast_targets(float ratingscale)
354 {SELFPARAM();
355         entity ad, best, wp, tod;
356         float radius, found, bestvalue;
357         vector p;
358
359         ad = findchain(classname, "func_assault_destructible");
360
361         for (; ad; ad = ad.chain)
362         {
363                 if (ad.target == "")
364                         continue;
365
366                 if (!ad.bot_attack)
367                         continue;
368
369                 found = false;
370                 for(tod = world; (tod = find(tod, targetname, ad.target)); )
371                 {
372                         if(tod.classname == "target_objective_decrease")
373                         {
374                                 if(tod.enemy.health > 0 && tod.enemy.health < ASSAULT_VALUE_INACTIVE)
375                                 {
376                                 //      dprint(etos(ad),"\n");
377                                         found = true;
378                                         break;
379                                 }
380                         }
381                 }
382
383                 if(!found)
384                 {
385                 ///     dprint("target not found\n");
386                         continue;
387                 }
388                 /// dprint("target #", etos(ad), " found\n");
389
390
391                 p = 0.5 * (ad.absmin + ad.absmax);
392         //      dprint(vtos(ad.origin), " ", vtos(ad.absmin), " ", vtos(ad.absmax),"\n");
393         //      te_knightspike(p);
394         //      te_lightning2(world, '0 0 0', p);
395
396                 // Find and rate waypoints around it
397                 found = false;
398                 best = world;
399                 bestvalue = 99999999999;
400                 for(radius=0; radius<1500 && !found; radius+=500)
401                 {
402                         for(wp=findradius(p, radius); wp; wp=wp.chain)
403                         {
404                                 if(!(wp.wpflags & WAYPOINTFLAG_GENERATED))
405                                 if(wp.classname=="waypoint")
406                                 if(checkpvs(wp.origin, ad))
407                                 {
408                                         found = true;
409                                         if(wp.cnt<bestvalue)
410                                         {
411                                                 best = wp;
412                                                 bestvalue = wp.cnt;
413                                         }
414                                 }
415                         }
416                 }
417
418                 if(best)
419                 {
420                 ///     dprint("waypoints around target were found\n");
421                 //      te_lightning2(world, '0 0 0', best.origin);
422                 //      te_knightspike(best.origin);
423
424                         navigation_routerating(best, ratingscale, 4000);
425                         best.cnt += 1;
426
427                         self.havocbot_attack_time = 0;
428
429                         if(checkpvs(self.view_ofs,ad))
430                         if(checkpvs(self.view_ofs,best))
431                         {
432                         //      dprint("increasing attack time for this target\n");
433                                 self.havocbot_attack_time = time + 2;
434                         }
435                 }
436         }
437 }
438
439 void havocbot_role_ast_offense()
440 {SELFPARAM();
441         if(self.deadflag != DEAD_NO)
442         {
443                 self.havocbot_attack_time = 0;
444                 havocbot_ast_reset_role(self);
445                 return;
446         }
447
448         // Set the role timeout if necessary
449         if (!self.havocbot_role_timeout)
450                 self.havocbot_role_timeout = time + 120;
451
452         if (time > self.havocbot_role_timeout)
453         {
454                 havocbot_ast_reset_role(self);
455                 return;
456         }
457
458         if(self.havocbot_attack_time>time)
459                 return;
460
461         if (self.bot_strategytime < time)
462         {
463                 navigation_goalrating_start();
464                 havocbot_goalrating_enemyplayers(20000, self.origin, 650);
465                 havocbot_goalrating_ast_targets(20000);
466                 havocbot_goalrating_items(15000, self.origin, 10000);
467                 navigation_goalrating_end();
468
469                 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
470         }
471 }
472
473 void havocbot_role_ast_defense()
474 {SELFPARAM();
475         if(self.deadflag != DEAD_NO)
476         {
477                 self.havocbot_attack_time = 0;
478                 havocbot_ast_reset_role(self);
479                 return;
480         }
481
482         // Set the role timeout if necessary
483         if (!self.havocbot_role_timeout)
484                 self.havocbot_role_timeout = time + 120;
485
486         if (time > self.havocbot_role_timeout)
487         {
488                 havocbot_ast_reset_role(self);
489                 return;
490         }
491
492         if(self.havocbot_attack_time>time)
493                 return;
494
495         if (self.bot_strategytime < time)
496         {
497                 navigation_goalrating_start();
498                 havocbot_goalrating_enemyplayers(20000, self.origin, 3000);
499                 havocbot_goalrating_ast_targets(20000);
500                 havocbot_goalrating_items(15000, self.origin, 10000);
501                 navigation_goalrating_end();
502
503                 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
504         }
505 }
506
507 void havocbot_role_ast_setrole(entity bot, float role)
508 {
509         switch(role)
510         {
511                 case HAVOCBOT_AST_ROLE_DEFENSE:
512                         bot.havocbot_role = havocbot_role_ast_defense;
513                         bot.havocbot_role_flags = HAVOCBOT_AST_ROLE_DEFENSE;
514                         bot.havocbot_role_timeout = 0;
515                         break;
516                 case HAVOCBOT_AST_ROLE_OFFENSE:
517                         bot.havocbot_role = havocbot_role_ast_offense;
518                         bot.havocbot_role_flags = HAVOCBOT_AST_ROLE_OFFENSE;
519                         bot.havocbot_role_timeout = 0;
520                         break;
521         }
522 }
523
524 void havocbot_ast_reset_role(entity bot)
525 {SELFPARAM();
526         if(self.deadflag != DEAD_NO)
527                 return;
528
529         if(bot.team == assault_attacker_team)
530                 havocbot_role_ast_setrole(bot, HAVOCBOT_AST_ROLE_OFFENSE);
531         else
532                 havocbot_role_ast_setrole(bot, HAVOCBOT_AST_ROLE_DEFENSE);
533 }
534
535 // mutator hooks
536 MUTATOR_HOOKFUNCTION(assault_PlayerSpawn)
537 {SELFPARAM();
538         if(self.team == assault_attacker_team)
539                 Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_ASSAULT_ATTACKING);
540         else
541                 Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_ASSAULT_DEFENDING);
542
543         return false;
544 }
545
546 MUTATOR_HOOKFUNCTION(assault_TurretSpawn)
547 {SELFPARAM();
548         if(!self.team || self.team == MAX_SHOT_DISTANCE)
549                 self.team = 5; // this gets reversed when match starts?
550
551         return false;
552 }
553
554 MUTATOR_HOOKFUNCTION(assault_VehicleSpawn)
555 {SELFPARAM();
556         self.nextthink = time + 0.5;
557
558         return false;
559 }
560
561 MUTATOR_HOOKFUNCTION(assault_BotRoles)
562 {SELFPARAM();
563         havocbot_ast_reset_role(self);
564         return true;
565 }
566
567 MUTATOR_HOOKFUNCTION(assault_PlayHitsound)
568 {
569         return (frag_victim.classname == "func_assault_destructible");
570 }
571
572 // scoreboard setup
573 void assault_ScoreRules()
574 {
575         ScoreRules_basics(2, SFL_SORT_PRIO_SECONDARY, SFL_SORT_PRIO_SECONDARY, true);
576         ScoreInfo_SetLabel_TeamScore(  ST_ASSAULT_OBJECTIVES,    "objectives",      SFL_SORT_PRIO_PRIMARY);
577         ScoreInfo_SetLabel_PlayerScore(SP_ASSAULT_OBJECTIVES,    "objectives",      SFL_SORT_PRIO_PRIMARY);
578         ScoreRules_basics_end();
579 }
580
581 MUTATOR_DEFINITION(gamemode_assault)
582 {
583         MUTATOR_HOOK(PlayerSpawn, assault_PlayerSpawn, CBC_ORDER_ANY);
584         MUTATOR_HOOK(TurretSpawn, assault_TurretSpawn, CBC_ORDER_ANY);
585         MUTATOR_HOOK(VehicleSpawn, assault_VehicleSpawn, CBC_ORDER_ANY);
586         MUTATOR_HOOK(HavocBot_ChooseRole, assault_BotRoles, CBC_ORDER_ANY);
587         MUTATOR_HOOK(PlayHitsound, assault_PlayHitsound, CBC_ORDER_ANY);
588
589         MUTATOR_ONADD
590         {
591                 if(time > 1) // game loads at time 1
592                         error("This is a game type and it cannot be added at runtime.");
593                 assault_ScoreRules();
594         }
595
596         MUTATOR_ONROLLBACK_OR_REMOVE
597         {
598                 // we actually cannot roll back assault_Initialize here
599                 // BUT: we don't need to! If this gets called, adding always
600                 // succeeds.
601         }
602
603         MUTATOR_ONREMOVE
604         {
605                 LOG_INFO("This is a game type and it cannot be removed at runtime.");
606                 return -1;
607         }
608
609         return 0;
610 }