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