Xonotic QuakeC
The free, fast arena FPS with crisp movement and a wide array of weapons
sv_assault.qc
Go to the documentation of this file.
1#include "sv_assault.qh"
2
7#include <server/damage.qh>
8#include <server/world.qh>
10
11.entity sprite;
12#define AS_ROUND_DELAY 5
13
14// random functions
15void assault_objective_use(entity this, entity actor, entity trigger)
16{
17 // activate objective
18 SetResourceExplicit(this, RES_HEALTH, 100);
19 //print("^2Activated objective ", this.targetname, "=", etos(this), "\n");
20 //print("Activator is ", actor.classname, "\n");
21
22 IL_EACH(g_assault_objectivedecreasers, it.target == this.targetname,
23 {
24 target_objective_decrease_activate(it);
25 });
26}
27
29{
30 float hlth = GetResource(this, RES_HEALTH);
31 if (hlth < 0 || hlth >= ASSAULT_VALUE_INACTIVE)
32 return '-1 0 0';
33 return current;
34}
35
36// reset this objective. Used when spawning an objective
37// and when a new round starts
42
43// decrease the health of targeted objectives
45{
46 if(actor.team != assault_attacker_team)
47 {
48 // wrong team triggered decrease
49 return;
50 }
51
52 if(trigger.assault_sprite)
53 {
55 if(trigger.classname == "func_assault_destructible")
56 trigger.sprite = NULL; // TODO: just unsetting it?!
57 }
58 else
59 return; // already activated! cannot activate again!
60
61 float hlth = GetResource(this.enemy, RES_HEALTH);
62 if (hlth < ASSAULT_VALUE_INACTIVE)
63 {
64 if (hlth - this.dmg > 0.5)
65 {
66 GameRules_scoring_add_team(actor, SCORE, this.dmg);
67 TakeResource(this.enemy, RES_HEALTH, this.dmg);
68 }
69 else
70 {
71 GameRules_scoring_add_team(actor, SCORE, hlth);
72 GameRules_scoring_add_team(actor, ASSAULT_OBJECTIVES, 1);
73 SetResourceExplicit(this.enemy, RES_HEALTH, -1);
74
75 if(this.enemy.message)
76 FOREACH_CLIENT(IS_PLAYER(it), { centerprint(it, this.enemy.message); });
77
78 SUB_UseTargets(this.enemy, this, trigger);
79 }
80 }
81}
82
84{
85 IL_EACH(g_assault_objectives, it.targetname == this.target,
86 {
87 if(this.enemy == NULL)
88 this.enemy = it;
89 else
90 objerror(this, "more than one objective as target - fix the map!");
91 break;
92 });
93
94 if(this.enemy == NULL)
95 objerror(this, "no objective as target - fix the map!");
96}
97
99{
100 if(GetResource(this.assault_decreaser.enemy, RES_HEALTH) >= ASSAULT_VALUE_INACTIVE)
101 return false;
102
103 return true;
104}
105
107{
108 entity spr;
109 this.owner = NULL;
111 {
112 if(it.assault_sprite != NULL)
113 {
115 if(it.classname == "func_assault_destructible")
116 it.sprite = NULL; // TODO: just unsetting it?!
117 }
118
119 spr = WaypointSprite_SpawnFixed(WP_AssaultDefend, 0.5 * (it.absmin + it.absmax), it, assault_sprite, RADARICON_OBJECTIVE);
120 spr.assault_decreaser = this;
121 spr.waypointsprite_visible_for_player = assault_decreaser_sprite_visible;
123 if(it.classname == "func_assault_destructible")
124 {
125 WaypointSprite_UpdateSprites(spr, WP_AssaultDefend, WP_AssaultDestroy, WP_AssaultDestroy);
126 WaypointSprite_UpdateMaxHealth(spr, it.max_health);
127 WaypointSprite_UpdateHealth(spr, GetResource(it, RES_HEALTH));
128 it.sprite = spr;
129 }
130 else
131 WaypointSprite_UpdateSprites(spr, WP_AssaultDefend, WP_AssaultPush, WP_AssaultPush);
132 });
133}
134
139
141{
142 //print("round end reset\n");
143 ++this.cnt; // up round counter
144 this.winning = false; // up round
145}
146
148{
149 this.winning = 1; // round has been won by attackers
150}
151
153void assault_roundstart_use(entity this, entity actor, entity trigger)
154{
155 SUB_UseTargets(this, this, trigger);
156
157 // (Re)spawn all turrets
158 IL_EACH(g_turrets, true,
159 {
161 {
162 // Swap turret teams
163 if(it.team == NUM_TEAM_1)
164 it.team = NUM_TEAM_2;
165 else
166 it.team = NUM_TEAM_1;
167 }
168
169 // Doubles as teamchange
170 turret_respawn(it);
171 });
172}
177
179{
180 if(GetResource(this.enemy, RES_HEALTH) < 0)
181 {
182 this.model = "";
183 this.solid = SOLID_NOT;
184 }
185 else
186 {
187 this.model = this.mdl;
188 this.solid = SOLID_BSP;
189 }
190
191 this.nextthink = time + 0.2;
192}
193
194// trigger new round
195// reset objectives, toggle spawnpoints, reset triggers, ...
197{
198 // up round counter
199 this.winning = this.winning + 1;
200
201 // swap attacker/defender roles
204 else
206
208 {
209 if(it.team_saved == NUM_TEAM_1)
210 it.team_saved = NUM_TEAM_2;
211 else if(it.team_saved == NUM_TEAM_2)
212 it.team_saved = NUM_TEAM_1;
213 });
214
215 // reset the level with a countdown
216 cvar_set("timelimit", ftos(ceil(time - AS_ROUND_DELAY - game_starttime) / 60));
217 bprint("Starting second round...\n");
218 ReadyRestart_force(true); // sets game_starttime
219}
220
224{
225 game_stopped = false;
226 assault_new_round(as_round.ent_winning);
227 delete(as_round);
228 as_round = NULL;
229}
230
231// Assault winning condition: If the attackers triggered a round end (by fulfilling all objectives)
232// they win. Otherwise the defending team wins once the timelimit passes.
234{
235 if(as_round)
236 return WINNING_NO;
237
238 WinningConditionHelper(NULL); // set worldstatus
239
240 int status = WINNING_NO;
241 // as the timelimit has not yet passed just assume the defending team will win
243 {
245 }
246 else
247 {
249 }
250
251 entity ent;
252 ent = find(NULL, classname, "target_assault_roundend");
253 if(ent)
254 {
255 if(ent.winning) // round end has been triggered by attacking team
256 {
257 bprint(Team_ColoredFullName(assault_attacker_team), " destroyed the objective in ", process_time(2, ceil(time - game_starttime)), "\n");
259
261
262 // in campaign the game ends when the player destroys the objective, there's no second round
263 if(ent.cnt == 1 || autocvar_g_campaign) // this was the second round or the only round in campaign
264 {
265 status = WINNING_YES;
266 }
267 else
268 {
269 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ASSAULT_OBJ_DESTROYED, ceil(time - game_starttime));
270 as_round = new(as_round);
271 as_round.think = as_round_think;
272 as_round.ent_winning = ent;
273 as_round.nextthink = time + AS_ROUND_DELAY;
274 game_stopped = true;
275
276 // make sure timelimit isn't hit while the game is blocked
277 if(autocvar_timelimit > 0)
279 cvar_set("timelimit", ftos(autocvar_timelimit + AS_ROUND_DELAY / 60));
280 }
281 }
282 }
283
284 return status;
285}
286
287// spawnfuncs
288spawnfunc(info_player_attacker)
289{
290 if (!g_assault) { delete(this); return; }
291
292 this.team = NUM_TEAM_1; // red, gets swapped every round
293 spawnfunc_info_player_deathmatch(this);
294}
295
296spawnfunc(info_player_defender)
297{
298 if (!g_assault) { delete(this); return; }
299
300 this.team = NUM_TEAM_2; // blue, gets swapped every round
301 spawnfunc_info_player_deathmatch(this);
302}
303
304spawnfunc(target_objective)
305{
306 if (!g_assault) { delete(this); return; }
307
310 this.reset = assault_objective_reset;
311 this.reset(this);
313}
314
315spawnfunc(target_objective_decrease)
316{
317 if (!g_assault) { delete(this); return; }
318
320
321 if(!this.dmg)
322 this.dmg = 101;
323
327 this.enemy = NULL;
328
330}
331
332// destructible walls that can be used to trigger target_objective_decrease
333bool destructible_heal(entity targ, entity inflictor, float amount, float limit)
334{
335 float true_limit = ((limit != RES_LIMIT_NONE) ? limit : targ.max_health);
336 float hlth = GetResource(targ, RES_HEALTH);
337 if (hlth <= 0 || hlth >= true_limit)
338 return false;
339
340 GiveResourceWithLimit(targ, RES_HEALTH, amount, true_limit);
341 if(targ.sprite)
342 {
343 WaypointSprite_UpdateHealth(targ.sprite, GetResource(targ, RES_HEALTH));
344 }
346 return true;
347}
348
349spawnfunc(func_assault_destructible)
350{
351 if (!g_assault) { delete(this); return; }
352
353 this.spawnflags = 3;
354 this.event_heal = destructible_heal;
356
358 this.team = NUM_TEAM_2;
359 else
360 this.team = NUM_TEAM_1;
361
363}
364
365spawnfunc(func_assault_wall)
366{
367 if (!g_assault) { delete(this); return; }
368
369 this.mdl = this.model;
370 _setmodel(this, this.mdl);
371 this.solid = SOLID_BSP;
373 this.nextthink = time;
375}
376
377spawnfunc(target_assault_roundend)
378{
379 if (!g_assault) { delete(this); return; }
380
381 this.winning = 0; // round not yet won by attackers
383 this.cnt = 0; // first round
385}
386
387spawnfunc(target_assault_roundstart)
388{
389 if (!g_assault) { delete(this); return; }
390
393 this.reset2 = assault_roundstart_use_this;
395}
396
397// legacy bot code
398void havocbot_goalrating_ast_targets(entity this, float ratingscale)
399{
400 IL_EACH(g_assault_destructibles, it.bot_attack,
401 {
402 if (it.target == "")
403 continue;
404
405 bool found = false;
406 entity destr = it;
407 IL_EACH(g_assault_objectivedecreasers, it.targetname == destr.target,
408 {
409 float hlth = GetResource(it.enemy, RES_HEALTH);
410 if (hlth > 0 && hlth < ASSAULT_VALUE_INACTIVE)
411 {
412 found = true;
413 break;
414 }
415 });
416
417 if(!found)
418 continue;
419
420 vector p = 0.5 * (it.absmin + it.absmax);
421
422 // Find and rate waypoints around it
423 found = false;
424 entity best = NULL;
425 float bestvalue = FLOAT_MAX;
426 entity des = it;
427 for (float radius = 500; radius <= 1500 && !found; radius += 500)
428 {
429 FOREACH_ENTITY_RADIUS(p, radius, it.classname == "waypoint" && !(it.wpflags & WAYPOINTFLAG_GENERATED),
430 {
431 if(checkpvs(it.origin, des))
432 {
433 found = true;
434 if(it.cnt < bestvalue)
435 {
436 best = it;
437 bestvalue = it.cnt;
438 }
439 }
440 });
441 }
442
443 if(best)
444 {
446 // te_lightning2(NULL, '0 0 0', best.origin);
447 // te_knightspike(best.origin);
448
449 navigation_routerating(this, best, ratingscale, 4000);
450 best.cnt += 1;
451
452 this.havocbot_attack_time = 0;
453
454 if(checkpvs(this.origin + this.view_ofs, it))
455 if(checkpvs(this.origin + this.view_ofs, best))
456 {
457 // dprint("increasing attack time for this target\n");
458 this.havocbot_attack_time = time + 2;
459 }
460 }
461 });
462}
463
465{
466 if(IS_DEAD(this))
467 {
468 this.havocbot_attack_time = 0;
470 return;
471 }
472
473 // Set the role timeout if necessary
474 if (!this.havocbot_role_timeout)
475 this.havocbot_role_timeout = time + 120;
476
477 if (time > this.havocbot_role_timeout)
478 {
480 return;
481 }
482
484 return;
485
487 {
488 // role: offense
490 havocbot_goalrating_enemyplayers(this, 10000, this.origin, 650);
492 havocbot_goalrating_items(this, 30000, this.origin, 10000);
494
496 }
497}
498
500{
501 if(IS_DEAD(this))
502 {
503 this.havocbot_attack_time = 0;
505 return;
506 }
507
508 // Set the role timeout if necessary
509 if (!this.havocbot_role_timeout)
510 this.havocbot_role_timeout = time + 120;
511
512 if (time > this.havocbot_role_timeout)
513 {
515 return;
516 }
517
519 return;
520
522 {
523 // role: defense
525 havocbot_goalrating_enemyplayers(this, 10000, this.origin, 3000);
527 havocbot_goalrating_items(this, 30000, this.origin, 10000);
529
531 }
532}
533
534void havocbot_role_ast_setrole(entity this, float role)
535{
536 switch(role)
537 {
539 this.havocbot_role = havocbot_role_ast_defense;
540 this.havocbot_role_timeout = 0;
541 break;
543 this.havocbot_role = havocbot_role_ast_offense;
544 this.havocbot_role_timeout = 0;
545 break;
546 }
547}
548
559
560// mutator hooks
561MUTATOR_HOOKFUNCTION(as, PlayerSpawn)
562{
563 entity player = M_ARGV(0, entity);
564
565 if(player.team == assault_attacker_team)
566 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_ASSAULT_ATTACKING);
567 else
568 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_ASSAULT_DEFENDING);
569}
570
571MUTATOR_HOOKFUNCTION(as, TurretSpawn)
572{
573 entity turret = M_ARGV(0, entity);
574
575 if(!turret.team || turret.team == FLOAT_MAX)
576 turret.team = assault_attacker_team; // this gets reversed when match starts (assault_roundstart_use)
577}
578
579MUTATOR_HOOKFUNCTION(as, VehicleInit)
580{
581 entity veh = M_ARGV(0, entity);
582
583 veh.nextthink = time + 0.5;
584}
585
586MUTATOR_HOOKFUNCTION(as, HavocBot_ChooseRole)
587{
588 entity bot = M_ARGV(0, entity);
589
591 return true;
592}
593
594MUTATOR_HOOKFUNCTION(as, PlayHitsound)
595{
596 entity frag_victim = M_ARGV(0, entity);
597
598 return (frag_victim.classname == "func_assault_destructible");
599}
600
602{
603 // assault always has 2 teams
604 M_ARGV(0, float) = BIT(0) | BIT(1);
605 return true;
606}
607
609{
610 M_ARGV(0, float) = WinningCondition_Assault();
611 return true;
612}
613
614MUTATOR_HOOKFUNCTION(as, ReadLevelCvars)
615{
616 // incompatible
617 warmup_stage = 0;
619}
620
621MUTATOR_HOOKFUNCTION(as, OnEntityPreSpawn)
622{
623 entity ent = M_ARGV(0, entity);
624
625 switch(ent.classname)
626 {
627 case "info_player_team1":
628 case "info_player_team2":
629 case "info_player_team3":
630 case "info_player_team4":
631 return true;
632 }
633}
634
635MUTATOR_HOOKFUNCTION(as, ReadyRestart_Deny)
636{
637 // Readyrestart is forbidden because Assault is actually played in 2 different games, called
638 // rounds, where the map is completely restarted and the timelimit of the second round is set
639 // to the time the previous attacker team took to destroy the main objective
640 // In campaign it's allowed because Assault is played in a single game / round
642 {
644 return false;
645 }
646 return true;
647}
void navigation_goalrating_start(entity this)
void navigation_goalrating_timeout_set(entity this)
Definition navigation.qc:20
bool navigation_goalrating_timeout(entity this)
Definition navigation.qc:44
void navigation_routerating(entity this, entity e, float f, float rangebias)
const int WAYPOINTFLAG_GENERATED
Definition api.qh:11
void navigation_goalrating_end(entity this)
float havocbot_role_timeout
Definition api.qh:46
#define g_assault
Definition assault.qh:27
#define MUTATOR_HOOKFUNCTION(...)
Definition base.qh:335
#define BIT(n)
Only ever assign into the first 24 bits in QC (so max is BIT(23)).
Definition bits.qh:8
void func_breakable_setup(entity this)
Definition breakable.qc:317
float dmg
Definition breakable.qc:12
void func_breakable_colormod(entity this)
Definition breakable.qc:81
var entity(vector mins, vector maxs,.entity tofield) findbox_tofield_OrFallback
float max_health
float GetResource(entity e, Resource res_type)
Returns the current amount of resource the given entity has.
void TakeResource(entity receiver, Resource res_type, float amount)
Takes an entity some resource.
bool SetResourceExplicit(entity e, Resource res_type, float amount)
Sets the resource amount of an entity without calling any hooks.
float cnt
Definition powerups.qc:24
entity owner
Definition main.qh:87
bool warmup_stage
Definition main.qh:120
int team
Definition main.qh:188
int spawnflags
Definition ammo.qh:15
string mdl
Definition item.qh:89
float radius
Definition impulse.qh:11
#define M_ARGV(x, type)
Definition events.qh:17
#define IS_CLIENT(s)
Definition player.qh:242
#define IS_DEAD(s)
Definition player.qh:245
#define IS_PLAYER(s)
Definition player.qh:243
#define autocvar_timelimit
Definition stats.qh:92
float game_starttime
Definition stats.qh:82
float game_stopped
Definition stats.qh:81
const int INITPRIO_FINDTARGET
Definition constants.qh:96
ERASEABLE string process_time(float outputtype, int seconds)
Definition counting.qh:120
string classname
const float SOLID_NOT
float time
float checkpvs(vector viewpos, entity viewee)
float nextthink
vector origin
const float SOLID_BSP
#define use
model
Definition ent_cs.qc:139
solid
Definition ent_cs.qc:165
const float FLOAT_MAX
Definition float.qh:3
best
Definition all.qh:82
ERASEABLE entity IL_PUSH(IntrusiveList this, entity it)
Push to tail.
#define IL_EACH(this, cond, body)
#define FOREACH_ENTITY_RADIUS(org, dist, cond, body)
Definition iter.qh:160
#define FOREACH_ENTITY_STRING(fld, match, body)
Definition iter.qh:184
void SUB_UseTargets(entity this, entity actor, entity trigger)
Definition triggers.qc:344
bool autocvar_g_campaign
Definition menu.qc:747
void cvar_set(string name, string value)
float ceil(float f)
entity find(entity start,.string field, string match)
void bprint(string text,...)
void centerprint(string text,...)
string ftos(float f)
void Send_Notification(NOTIF broadcast, entity client, MSG net_type, Notification net_name,...count)
Definition all.qc:1573
#define NULL
Definition post.qh:14
#define objerror
Definition pre.qh:8
vector view_ofs
Definition progsdefs.qc:151
const int RES_LIMIT_NONE
Definition resources.qh:60
void WinningConditionHelper(entity this)
Sets the following results for the current scores entities.
Definition scores.qc:443
float TeamScore_AddToTeam(int t, float scorefield, float score)
Adds a score to the given team.
Definition scores.qc:107
#define setthink(e, f)
vector
Definition self.qh:92
void ReadyRestart_force(bool is_fake_round_start)
Definition vote.qc:441
IntrusiveList g_saved_team
Definition vote.qh:79
spawn_evalfunc_t spawn_evalfunc
#define spawnfunc(id)
Definition spawnfunc.qh:96
void assault_new_round(entity this)
void assault_roundstart_use(entity this, entity actor, entity trigger)
void as_round_think()
void assault_roundstart_use_this(entity this)
void havocbot_role_ast_defense(entity this)
void assault_objective_use(entity this, entity actor, entity trigger)
Definition sv_assault.qc:15
entity sprite
Definition sv_assault.qc:11
void assault_setenemytoobjective(entity this)
Definition sv_assault.qc:83
void havocbot_role_ast_setrole(entity this, float role)
bool assault_turrets_teamswap_forbidden
bool assault_decreaser_sprite_visible(entity this, entity player, entity view)
Definition sv_assault.qc:98
void havocbot_ast_reset_role(entity this)
entity ent_winning
entity as_round
void assault_objective_decrease_use(entity this, entity actor, entity trigger)
Definition sv_assault.qc:44
void assault_wall_think(entity this)
void havocbot_goalrating_ast_targets(entity this, float ratingscale)
void target_assault_roundend_reset(entity this)
vector target_objective_spawn_evalfunc(entity this, entity player, entity spot, vector current)
Definition sv_assault.qc:28
void target_assault_roundend_use(entity this, entity actor, entity trigger)
void target_objective_decrease_activate(entity this)
void havocbot_role_ast_offense(entity this)
bool destructible_heal(entity targ, entity inflictor, float amount, float limit)
void assault_objective_reset(entity this)
Definition sv_assault.qc:38
int WinningCondition_Assault()
void target_objective_decrease_findtarget(entity this)
#define AS_ROUND_DELAY
Definition sv_assault.qc:12
entity assault_decreaser
Definition sv_assault.qh:34
IntrusiveList g_assault_destructibles
Definition sv_assault.qh:10
const int ASSAULT_VALUE_INACTIVE
Definition sv_assault.qh:6
IntrusiveList g_assault_objectives
Definition sv_assault.qh:12
const int HAVOCBOT_AST_ROLE_OFFENSE
Definition sv_assault.qh:40
const int HAVOCBOT_AST_ROLE_DEFENSE
Definition sv_assault.qh:39
float havocbot_attack_time
Definition sv_assault.qh:42
IntrusiveList g_assault_objectivedecreasers
Definition sv_assault.qh:11
entity assault_sprite
Definition sv_assault.qh:35
const int ST_ASSAULT_OBJECTIVES
Definition sv_assault.qh:8
int assault_attacker_team
Definition sv_assault.qh:53
entity enemy
Definition sv_ctf.qh:153
void havocbot_goalrating_items(entity this, float ratingscale, vector org, float sradius)
Definition roles.qc:106
void havocbot_goalrating_enemyplayers(entity this, float ratingscale, vector org, float sradius)
Definition roles.qc:176
void GiveResourceWithLimit(entity receiver, Resource res_type, float amount, float limit)
Gives an entity some resource but not more than a limit.
#define GameRules_scoring_add_team(client, fld, value)
Definition sv_rules.qh:89
void turret_respawn(entity this)
IntrusiveList g_turrets
entity TeamBalance_CheckAllowedTeams(entity for_whom)
Checks whether the player can join teams according to global configuration and mutator settings.
Definition teamplay.qc:459
const int NUM_TEAM_2
Definition teams.qh:14
#define Team_ColoredFullName(teamid)
Definition teams.qh:232
const int NUM_TEAM_1
Definition teams.qh:13
string targetname
Definition triggers.qh:56
string target
Definition triggers.qh:55
#define FOREACH_CLIENT(cond, body)
Definition utils.qh:50
void WaypointSprite_UpdateSprites(entity e, entity _m1, entity _m2, entity _m3)
void WaypointSprite_UpdateMaxHealth(entity e, float f)
void WaypointSprite_UpdateHealth(entity e, float f)
entity WaypointSprite_SpawnFixed(entity spr, vector ofs, entity own,.entity ownfield, entity icon)
void WaypointSprite_Disown(entity wp, float fadetime)
void WaypointSprite_UpdateRule(entity e, float t, float r)
float waypointsprite_deadlifetime
const int SPRITERULE_TEAMPLAY
void CheckRules_World()
Definition world.qc:1705
void SetWinners(.float field, float value)
Definition world.qc:1519
void InitializeEntity(entity e, void(entity this) func, int order)
Definition world.qc:2209
const int WINNING_NO
Definition world.qh:132
float winning
Definition world.qh:131
const int WINNING_YES
Definition world.qh:133
bool sv_ready_restart_after_countdown
Definition world.qh:116