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