Xonotic QuakeC
The free, fast arena FPS with crisp movement and a wide array of weapons
sv_invasion.qc
Go to the documentation of this file.
1#include "sv_invasion.qh"
2
8
9#include <server/bot/api.qh>
10#include <server/world.qh>
11#include <server/teamplay.qh>
12
19
22
23bool inv_warning_shown; // spammy
24
26{
27 if(!IS_PLAYER(actor)) { return; }
28
29 actor.inv_endreached = true;
30
31 int plnum = 0;
32 int realplnum = 0;
33 // let's not count bots
35 ++realplnum;
36 if(it.inv_endreached)
37 ++plnum;
38 });
39 if(plnum < ceil(realplnum * min(1, this.count))) // 70% of players
40 return;
41
42 this.winning = true;
43}
44
45spawnfunc(target_invasion_roundend)
46{
47 if(!g_invasion) { delete(this); return; }
48
49 victent_present = true; // a victory entity is present, we don't need to rely on monster count TODO: merge this with the intrusive list (can check empty)
50
51 if(!this.count) { this.count = 0.7; } // require at least 70% of the players to reach the end before triggering victory
52
54
56}
57
58spawnfunc(invasion_wave)
59{
60 if(!g_invasion) { delete(this); return; }
61
63}
64
65spawnfunc(invasion_spawnpoint)
66{
67 if(!g_invasion) { delete(this); return; }
68
70}
71
72void ClearWinners();
73
74// Invasion stage mode winning condition: If the attackers triggered a round end (by fulfilling all objectives)
75// they win.
77{
78 WinningConditionHelper(NULL); // set worldstatus
79
80 int status = WINNING_NO;
81
83 {
85
86 int found = 0;
88 {
89 ++found;
90 if(it.winning)
91 {
92 bprint("Invasion: round completed.\n");
93 // winners already set
94
95 status = WINNING_YES;
96 break;
97 }
98 });
99
100 if(!found)
101 status = WINNING_YES; // just end it? TODO: should warn mapper!
102 }
104 {
105 ClearWinners();
106
107 int found = 0; // NOTE: this ends the round if no monsters are placed
108 IL_EACH(g_monsters, !(it.spawnflags & MONSTERFLAG_RESPAWNED),
109 {
110 ++found;
111 });
112
113 if(found <= 0)
114 {
115 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
116 {
117 it.winning = true;
118 });
119 status = WINNING_YES;
120 }
121 }
122
123 return status;
124}
125
126Monster invasion_PickMonster(int supermonster_count)
127{
129
130 FOREACH(Monsters, it != MON_Null,
131 {
133 || ((it.spawnflags & MON_FLAG_SUPERMONSTER) && supermonster_count >= 1))
134 continue;
136 continue;
137 RandomSelection_AddEnt(it, 1, 1);
138 });
139
141}
142
144{
146
148 {
149 RandomSelection_AddEnt(it, 1, ((time < it.spawnshieldtime) ? 0.2 : 1)); // give recently used spawnpoints a very low rating
151 });
152
154}
155
157{
158 IL_EACH(g_invasion_waves, it.cnt == wavenum,
159 {
160 return it; // found one
161 });
162
163 // if no specific one is found, find the last existing wave ent
164 entity best = NULL;
165 IL_EACH(g_invasion_waves, it.cnt <= wavenum,
166 {
167 if(!best || it.cnt > best.cnt)
168 best = it;
169 });
170
171 return best;
172}
173
175{
176 entity monster;
179
180 string tospawn = "";
181 if(wave_ent && wave_ent.spawnmob && wave_ent.spawnmob != "")
182 {
184 FOREACH_WORD(wave_ent.spawnmob, true,
185 {
186 RandomSelection_AddString(it, 1, 1);
187 });
188
190 }
191
192 if(spawn_point == NULL)
193 {
195 {
196 inv_warning_shown = true;
197 LOG_TRACE("Warning: couldn't find any invasion_spawnpoint spawnpoints, attempting to spawn monsters in random locations");
198 }
199 entity e = spawn();
200 setsize(e, mon.m_mins, mon.m_maxs);
201
203 {
204 monster = spawnmonster(e, tospawn, mon, NULL, NULL, e.origin, false, false, 2);
205 monster.angles_x = monster.angles_z = 0;
206 }
207 else
208 {
209 delete(e);
210 return;
211 }
212 }
213 else // if spawnmob field falls through (unset), fallback to mon (relying on spawnmonster for that behaviour)
214 monster = spawnmonster(spawn(), ((spawn_point.spawnmob && spawn_point.spawnmob != "") ? spawn_point.spawnmob : tospawn), mon, spawn_point, spawn_point, spawn_point.origin, false, false, 2);
215
216 if(!monster)
217 return;
218
219 StatusEffects_remove(STATUSEFFECT_SpawnShield, monster, STATUSEFFECT_REMOVE_NORMAL);
220
221 if(spawn_point)
222 {
223 if(spawn_point.target_range)
224 monster.target_range = spawn_point.target_range;
225 monster.target2 = spawn_point.target2;
226 }
227
228 if(monster.monster_attack)
230 monster.monster_attack = false; // it's the player's job to kill all the monsters
231
233 monster.spawnflags |= MONSTERFLAG_MINIBOSS; // last round spawns minibosses
234}
235
236void invasion_SpawnMonsters(int supermonster_count)
237{
238 Monster chosen_monster = invasion_PickMonster(supermonster_count);
239
240 invasion_SpawnChosenMonster(chosen_monster);
241}
242
244{
246 {
247 IL_EACH(g_monsters, true,
248 {
249 Monster_Remove(it);
250 });
252
253 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER);
254 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER);
256 return 1;
257 }
258
259 float total_alive_monsters = 0, supermonster_count = 0;
260
261 IL_EACH(g_monsters, GetResource(it, RES_HEALTH) > 0,
262 {
263 if(it.monsterdef.spawnflags & MON_FLAG_SUPERMONSTER)
264 ++supermonster_count;
265 ++total_alive_monsters;
266 });
267
268 if((total_alive_monsters + inv_numkilled) < inv_maxspawned && inv_maxcurrent < inv_maxspawned)
269 {
270 if(time >= inv_lastcheck)
271 {
272 invasion_SpawnMonsters(supermonster_count);
274 }
275
276 return 0;
277 }
278
279 if(inv_numspawned < 1)
280 return 0; // nothing has spawned yet
281
283 return 0;
284
285 entity winner = NULL;
286 float winning_score = 0;
287
289 float cs = GameRules_scoring_add(it, KILLS, 0);
290 if(cs > winning_score)
291 {
292 winning_score = cs;
293 winner = it;
294 }
295 });
296
297 IL_EACH(g_monsters, true,
298 {
299 Monster_Remove(it);
300 });
302
303 if(winner)
304 {
305 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_PLAYER_WIN, winner.netname);
306 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_PLAYER_WIN, winner.netname);
307 }
308
310
311 return 1;
312}
313
315{
316 return true;
317}
318
320{
321 int numplayers = 0;
323 it.player_blocked = false;
324 ++numplayers;
325 });
326
328 inv_roundcnt += 1; // a limiter to stop crazy counts
329
331
332 inv_maxcurrent = 0;
333 inv_numspawned = 0;
334 inv_numkilled = 0;
335
337}
338
339MUTATOR_HOOKFUNCTION(inv, MonsterDies)
340{
342 entity frag_attacker = M_ARGV(1, entity);
343
344 if(!(frag_target.spawnflags & MONSTERFLAG_RESPAWNED))
345 {
347 {
348 inv_numkilled += 1;
349 inv_maxcurrent -= 1;
350 }
351
352 if(IS_PLAYER(frag_attacker))
353 {
354 if(SAME_TEAM(frag_attacker, frag_target))
355 GameRules_scoring_add(frag_attacker, KILLS, -1);
356 else
357 GameRules_scoring_add(frag_attacker, KILLS, +1);
358 }
359 }
360}
361
362MUTATOR_HOOKFUNCTION(inv, MonsterSpawn)
363{
364 entity mon = M_ARGV(0, entity);
366
368 return false; // allowed
369
370 if(!(mon.spawnflags & MONSTERFLAG_SPAWNED))
371 return true;
372
373 if(!(mon.spawnflags & MONSTERFLAG_RESPAWNED))
374 {
375 inv_numspawned += 1;
376 inv_maxcurrent += 1;
377 }
378
379 mon.monster_skill = inv_monsterskill;
380
381 if(mon.monsterdef.spawnflags & MON_FLAG_SUPERMONSTER)
382 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_INVASION_SUPERMONSTER, mon.m_name);
383}
384
385MUTATOR_HOOKFUNCTION(inv, SV_StartFrame)
386{
388 return; // uses map spawned monsters
389
390 monsters_total = inv_maxspawned; // TODO: make sure numspawned never exceeds maxspawned
392}
393
394MUTATOR_HOOKFUNCTION(inv, PlayerRegen)
395{
396 // no regeneration in invasion, regardless of the gametype
397 return true;
398}
399
400MUTATOR_HOOKFUNCTION(inv, PlayerSpawn)
401{
402 entity player = M_ARGV(0, entity);
403
404 if(player.bot_attack)
405 IL_REMOVE(g_bot_targets, player);
406 player.bot_attack = false;
407}
408
409MUTATOR_HOOKFUNCTION(inv, Damage_Calculate)
410{
411 entity frag_attacker = M_ARGV(1, entity);
413 float frag_damage = M_ARGV(4, float);
415
416 if(IS_PLAYER(frag_attacker) && IS_PLAYER(frag_target) && frag_attacker != frag_target)
417 {
418 frag_damage = 0;
419 frag_force = '0 0 0';
420
421 M_ARGV(4, float) = frag_damage;
423 }
424}
425
426MUTATOR_HOOKFUNCTION(inv, BotShouldAttack)
427{
428 entity targ = M_ARGV(1, entity);
429
430 if(!IS_MONSTER(targ))
431 return true;
432}
433
434MUTATOR_HOOKFUNCTION(inv, SetStartItems)
435{
437 {
438 start_health = 200;
439 start_armorvalue = 200;
440 }
441}
442
443MUTATOR_HOOKFUNCTION(inv, AccuracyTargetValid)
444{
446
448 return MUT_ACCADD_INVALID;
450}
451
452MUTATOR_HOOKFUNCTION(inv, AllowMobSpawning)
453{
454 // monster spawning disabled during an invasion
455 M_ARGV(1, string) = "You cannot spawn monsters during an invasion!";
456 return true;
457}
458
460{
462 return false;
463
464 M_ARGV(0, float) = WinningCondition_Invasion();
465 return true;
466}
467
468MUTATOR_HOOKFUNCTION(inv, AllowMobButcher)
469{
470 M_ARGV(0, string) = "This command does not work during an invasion!";
471 return true;
472}
473
475{
477 GameRules_scoring(0, 0, 0, {
478 field(SP_KILLS, "kills", SFL_SORT_PRIO_PRIMARY);
479 });
480}
481
502
IntrusiveList g_bot_targets
Definition api.qh:149
#define MUTATOR_HOOKFUNCTION(...)
Definition base.qh:335
var entity(vector mins, vector maxs,.entity tofield) findbox_tofield_OrFallback
float GetResource(entity e, Resource res_type)
Returns the current amount of resource the given entity has.
vector m_mins
hitbox size
Definition monster.qh:50
vector m_maxs
hitbox size
Definition monster.qh:52
float count
Definition powerups.qc:22
#define M_ARGV(x, type)
Definition events.qh:17
#define IS_DEAD(s)
Definition player.qh:245
#define IS_PLAYER(s)
Definition player.qh:243
const int SFL_SORT_PRIO_PRIMARY
Definition scores.qh:134
const int INITPRIO_GAMETYPE
Definition constants.qh:94
float Q3SURFACEFLAG_SKY
float DPCONTENTS_SKY
float DPCONTENTS_BOTCLIP
float DPCONTENTS_DONOTENTER
float DPCONTENTS_SOLID
float DPCONTENTS_CORPSE
float DPCONTENTS_BODY
float DPCONTENTS_PLAYERCLIP
float DPCONTENTS_SLIME
float time
float DPCONTENTS_MONSTERCLIP
float DPCONTENTS_LAVA
#define spawn
#define use
#define spawn_point(name, color)
best
Definition all.qh:82
ERASEABLE void IL_REMOVE(IntrusiveList this, entity it)
Remove any element, anywhere in the list.
ERASEABLE entity IL_PUSH(IntrusiveList this, entity it)
Push to tail.
#define IL_EACH(this, cond, body)
#define IL_CLEAR(this)
Remove all elements.
#define FOREACH_WORD(words, cond, body)
Definition iter.qh:33
#define FOREACH(list, cond, body)
Definition iter.qh:19
#define LOG_TRACE(...)
Definition log.qh:76
void cvar_set(string name, string value)
float ceil(float f)
void bprint(string text,...)
float min(float f,...)
float rint(float f)
float max(float f,...)
const int MONSTER_TYPE_PASSIVE
Definition monster.qh:18
const int MONSTER_TYPE_FLY
Definition monster.qh:9
const int MONSTER_TYPE_UNDEAD
Definition monster.qh:19
const int MON_FLAG_HIDDEN
Definition monster.qh:20
const int MON_FLAG_SUPERMONSTER
Definition monster.qh:12
const int MONSTER_TYPE_SWIM
Definition monster.qh:10
const int MONSTER_SIZE_QUAKE
Definition monster.qh:17
@ STATUSEFFECT_REMOVE_NORMAL
Effect is being removed by a function, calls regular removal mechanics.
Definition all.qh:28
void Send_Notification(NOTIF broadcast, entity client, MSG net_type, Notification net_name,...count)
Definition all.qc:1573
#define NULL
Definition post.qh:14
ERASEABLE void RandomSelection_Init()
Definition random.qc:4
#define RandomSelection_AddEnt(e, weight, priority)
Definition random.qh:14
string RandomSelection_chosen_string
Definition random.qh:7
entity RandomSelection_chosen_ent
Definition random.qh:5
void round_handler_Init(float the_delay, float the_count, float the_round_timelimit)
void round_handler_Spawn(bool() canRoundStart_func, bool() canRoundEnd_func, void() roundStart_func)
#define round_handler_GetEndTime()
int numplayers
Definition scoreboard.qh:26
void WinningConditionHelper(entity this)
Sets the following results for the current scores entities.
Definition scores.qc:443
vector
Definition self.qh:92
bool independent_players
Definition client.qh:310
@ MUT_ACCADD_INDIFFERENT
Definition events.qh:862
@ MUT_ACCADD_INVALID
Definition events.qh:861
#define spawnfunc(id)
Definition spawnfunc.qh:96
void StatusEffects_remove(StatusEffect this, entity actor, int removal_type)
float frag_damage
Definition sv_ctf.qc:2322
vector frag_force
Definition sv_ctf.qc:2323
entity frag_target
Definition sv_ctf.qc:2321
int WinningCondition_Invasion()
bool victent_present
void target_invasion_roundend_use(entity this, entity actor, entity trigger)
void ClearWinners()
Definition world.qc:1534
void invasion_DelayedInit(entity this)
void invasion_Initialize()
float autocvar_g_invasion_spawnpoint_spawn_delay
bool Invasion_CheckPlayers()
void Invasion_RoundStart()
float autocvar_g_invasion_warmup
float autocvar_g_invasion_round_timelimit
Monster invasion_PickMonster(int supermonster_count)
int autocvar_g_invasion_monster_count
bool inv_endreached
void invasion_ScoreRules()
entity invasion_GetWaveEntity(int wavenum)
bool Invasion_CheckWinner()
bool autocvar_g_invasion_zombies_only
bool inv_warning_shown
float autocvar_g_invasion_spawn_delay
entity invasion_PickSpawn()
void invasion_SpawnMonsters(int supermonster_count)
void invasion_SpawnChosenMonster(Monster mon)
int inv_numkilled
const int INV_TYPE_ROUND
bool g_invasion
Definition sv_invasion.qh:6
const int INV_TYPE_HUNT
IntrusiveList g_invasion_roundends
Definition sv_invasion.qh:7
float inv_monsterskill
int inv_maxspawned
IntrusiveList g_invasion_waves
Definition sv_invasion.qh:8
int inv_roundcnt
int inv_numspawned
int autocvar_g_invasion_type
Definition sv_invasion.qh:5
int inv_maxrounds
int inv_maxcurrent
IntrusiveList g_invasion_spawns
Definition sv_invasion.qh:9
const int INV_TYPE_STAGE
float inv_lastcheck
void Monster_Remove(entity this)
const int MONSTERFLAG_SPAWNED
int monsters_killed
IntrusiveList g_monster_targets
int monsters_total
const int MONSTERFLAG_MINIBOSS
const int MONSTERFLAG_RESPAWNED
IntrusiveList g_monsters
#define GameRules_score_enabled(value)
Definition sv_rules.qh:41
#define GameRules_scoring_add(client, fld, value)
Definition sv_rules.qh:85
#define GameRules_scoring(teams, spprio, stprio, fields)
Definition sv_rules.qh:58
entity spawnmonster(entity e, string monster, Monster monster_id, entity spawnedby, entity own, vector orig, bool respwn, bool removeifinvalid, int moveflag)
Definition sv_spawn.qc:14
#define SAME_TEAM(a, b)
Definition teams.qh:241
#define IS_REAL_CLIENT(v)
Definition utils.qh:17
#define IS_MONSTER(v)
Definition utils.qh:21
#define FOREACH_CLIENT(cond, body)
Definition utils.qh:50
void CheckRules_World()
Definition world.qc:1705
void SetWinners(.float field, float value)
Definition world.qc:1519
float MoveToRandomMapLocation(entity e, float goodcontents, float badcontents, float badsurfaceflags, float attempts, float maxaboveground, float minviewdistance)
Definition world.qc:1231
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
float start_armorvalue
Definition world.qh:97
const int WINNING_YES
Definition world.qh:133
float start_health
Definition world.qh:96