- wget -O data/maps/stormkeep.waypoints https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.waypoints
- wget -O data/maps/stormkeep.waypoints.cache https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.waypoints.cache
- make
- - EXPECT=585cfa6d62ce59f4854bedfce7c51c20
+ - EXPECT=662ab75eeb91d25c2d86ebb81ce8dc02
- HASH=$(${ENGINE} -noconfig -nohome +exec serverbench.cfg
| tee /dev/stderr
| grep '^:'
alias namemob "editmob name ${* ?}"
alias movemob "editmob movetarget ${* ?}"
alias butcher "editmob butcher ${* ?}"
+alias mobbutcher "editmob butcher ${* ?}"
// ============================================================
alias gotomap "qc_cmd_sv gotomap ${* ?}" // Simple command to switch to another map
alias lockteams "qc_cmd_sv lockteams ${* ?}" // Disable the ability for players to switch or enter teams
alias make_mapinfo "qc_cmd_sv make_mapinfo ${* ?}" // Automatically rebuild mapinfo files
-alias mobbutcher "qc_cmd_sv mobbutcher ${* ?}" // Remove all monsters on the map
alias moveplayer "qc_cmd_sv moveplayer ${* ?}" // Change the team/status of a player
alias nospectators "qc_cmd_sv nospectators ${* ?}" // Automatically remove spectators from a match
alias playerdemo "qc_cmd_sv playerdemo ${* ?}" // Control the ability to save demos of players
r_shadow_realtime_world_importlightentitiesfrommap 0 // Whether build process uses keepLights is nontransparent and may change, so better make keepLights not matter.
cl_decals_fadetime 5
cl_decals_time 1
-seta cl_gunalign 3 "Gun alignment; 1 = center (if allowed by g_shootfromclient) or right, 2 = center (if allowed by g_shootfromclient) or left, 3 = right only, 4 = left only"
+seta cl_gunalign 3 "Gun alignment; 1 = center, 3 = right, 4 = left; requires reconnect"
seta cl_nogibs 0 "reduce number of violence effects, or remove them totally"
seta cl_particlegibs 0 "simpler gibs"
seta cl_gibs_damageforcescale 3.5 "force to push around gibs"
set g_ctf_throw 1 "throwing allows circumventing carrierkill score, so enable this with care!"
set g_ctf_throw_angle_max 90 "maximum upwards angle you can throw the flag"
set g_ctf_throw_angle_min -90 "minimum downwards angle you can throw the flag"
-set g_ctf_throw_punish_count 2
+set g_ctf_throw_punish_count 3
set g_ctf_throw_punish_delay 30
-set g_ctf_throw_punish_time 5
+set g_ctf_throw_punish_time 10
set g_ctf_throw_strengthmultiplier 2 "multiplier for velocity when you have the strength... essentially, throw the flag REALLY hard when you have the strength :D"
set g_ctf_throw_velocity_forward 500 "how fast or far a player can throw the flag"
set g_ctf_throw_velocity_up 200 "upwards velocity added upon initial throw"
set g_invasion_spawnpoint_spawn_delay 0.5
set g_invasion_teams 0 "number of teams in invasion (note: use mapinfo to set this)"
set g_invasion_team_spawns 1 "use team spawns in teamplay invasion mode"
+set g_invasion_type 0 "type of invasion mode - 0: round-based, 1: hunting, 2: complete the stage (note: use mapinfo to set this)"
//float lastpnum;
void Scoreboard_UpdatePlayerTeams()
{
- float Team;
entity pl, tmp;
//int num = 0;
for(pl = players.sort_next; pl; pl = pl.sort_next)
{
//num += 1;
- Team = entcs_GetScoreTeam(pl.sv_entnum);
+ int Team = entcs_GetScoreTeam(pl.sv_entnum);
if(SetTeam(pl, Team))
{
tmp = pl.sort_prev;
maxclients = i;
}
+ // needs to be done so early because of the constants they create
+ static_init();
+ static_init_late();
+ static_init_precache();
+
binddb = db_create();
tempdb = db_create();
ClientProgsDB = db_load("client.db");
GetTeam(NUM_SPECTATOR, true); // add specs first
+ for (int w = 0; w <= WEP_LAST - WEP_FIRST; ++w)
+ weapon_accuracy[w] = -1;
+
// precaches
if(autocvar_cl_reticle)
int f = ReadByte();
- scoreboard_showscores_force = (f & 1);
+ scoreboard_showscores_force = (f & BIT(0));
- if(f & 2)
+ if(f & BIT(1))
{
newspectatee_status = ReadByte();
if(newspectatee_status == player_localnum + 1)
else
newspectatee_status = 0;
- spectatorbutton_zoom = (f & 4);
+ spectatorbutton_zoom = (f & BIT(2));
- if(f & 16)
+ if(f & BIT(4))
{
num_spectators = ReadByte();
best.cnt += 1;
this.havocbot_attack_time = 0;
- if(checkpvs(this.view_ofs,cp))
- if(checkpvs(this.view_ofs,best))
+ if(checkpvs(this.origin + this.view_ofs, cp))
+ if(checkpvs(this.origin + this.view_ofs, best))
this.havocbot_attack_time = time + 2;
}
else
bestwp.cnt += 1;
this.havocbot_attack_time = 0;
- if(checkpvs(this.view_ofs,g))
- if(checkpvs(this.view_ofs,bestwp))
+ if(checkpvs(this.origin + this.view_ofs, g))
+ if(checkpvs(this.origin + this.view_ofs, bestwp))
this.havocbot_attack_time = time + 5;
return true;
CLASS(Invasion, Gametype)
INIT(Invasion)
{
- this.gametype_init(this, _("Invasion"),"inv","g_invasion",false,"","pointlimit=50 teams=0",_("Survive against waves of monsters"));
+ this.gametype_init(this, _("Invasion"),"inv","g_invasion",false,"","pointlimit=50 teams=0 type=0",_("Survive against waves of monsters"));
}
METHOD(Invasion, m_parse_mapinfo, bool(string k, string v))
{
case "teams":
cvar_set("g_invasion_teams", v);
return true;
+ case "type":
+ cvar_set("g_invasion_type", v);
+ return true;
}
return false;
}
//if(invincible) { e.spawnflags |= MONSTERFLAG_INVINCIBLE; }
setorigin(e, orig);
+ bool allow_any = boolean(monster == "anyrandom");
- if(monster == "random")
+ if(monster == "random" || allow_any)
{
RandomSelection_Init();
- FOREACH(Monsters, it != MON_Null && !(it.spawnflags & MONSTER_TYPE_PASSIVE) && !(it.spawnflags & MON_FLAG_HIDDEN),
+ FOREACH(Monsters, it != MON_Null && (allow_any || (!(it.spawnflags & MONSTER_TYPE_PASSIVE) && !(it.spawnflags & MON_FLAG_HIDDEN))),
{
RandomSelection_AddEnt(it, 1, 1);
});
#include "sv_damagetext.qh"
-AUTOCVAR(sv_damagetext, int, 2, "<= 0: disabled, >= 1: spectators, >= 2: players, >= 3: all players");
+AUTOCVAR(sv_damagetext, int, 2, "<= 0: disabled, >= 1: visible to spectators, >= 2: visible to attacker, >= 3: all players see everyone's damage");
REGISTER_MUTATOR(damagetext, true);
-#define SV_DAMAGETEXT_DISABLED() (autocvar_sv_damagetext <= 0 /* disabled */)
-#define SV_DAMAGETEXT_SPECTATORS_ONLY() (autocvar_sv_damagetext >= 1 /* spectators only */)
-#define SV_DAMAGETEXT_PLAYERS() (autocvar_sv_damagetext >= 2 /* players */)
-#define SV_DAMAGETEXT_ALL() (autocvar_sv_damagetext >= 3 /* all players */)
+#define SV_DAMAGETEXT_DISABLED() (autocvar_sv_damagetext <= 0 || autocvar_g_instagib)
+#define SV_DAMAGETEXT_SPECTATORS_ONLY() (autocvar_sv_damagetext >= 1)
+#define SV_DAMAGETEXT_PLAYERS() (autocvar_sv_damagetext >= 2)
+#define SV_DAMAGETEXT_ALL() (autocvar_sv_damagetext >= 3)
MUTATOR_HOOKFUNCTION(damagetext, PlayerDamaged) {
if (SV_DAMAGETEXT_DISABLED()) return;
const entity attacker = M_ARGV(0, entity);
M_ARGV(0, string) = strcat(M_ARGV(0, string), ":Nades");
}
-MUTATOR_HOOKFUNCTION(nades, BuildMutatorsPrettyString)
-{
- M_ARGV(0, string) = strcat(M_ARGV(0, string), ", Nades");
-}
-
MUTATOR_HOOKFUNCTION(nades, BuildGameplayTipsString)
{
M_ARGV(0, string) = strcat(M_ARGV(0, string), "\n\n^3nades^8 are enabled, press 'g' to use them\n");
if(frag_target != frag_attacker)
if(!IS_DEAD(frag_target))
{
- frag_attacker.health += bound(0, damage_take, frag_target.health);
- frag_attacker.health = bound(0, frag_attacker.health, autocvar_g_balance_health_limit);
+ GivePlayerHealth(frag_attacker, bound(0, damage_take, frag_target.health));
}
}
Item_ScheduleRespawnIn(e, max(0, game_starttime - time) + ((e.respawntimestart) ? e.respawntimestart : ITEM_RESPAWNTIME_INITIAL(e)));
}
-void GivePlayerHealth(entity player, float amount)
+void GivePlayerResource(entity player, .float resource_type, float amount)
{
if (amount == 0)
{
return;
}
- player.health = bound(player.health, player.health + amount,
- g_pickup_healthmega_max);
- player.pauserothealth_finished = max(player.pauserothealth_finished, time +
- autocvar_g_balance_pause_health_rot);
+ switch (resource_type)
+ {
+ case health:
+ {
+ // Ugly hack. We do not check if health goes beyond hard limit since
+ // currently it is done in player_regen. We need to bring back this
+ // check when other code is ported to this function.
+ player.health = bound(player.health, player.health + amount,
+ autocvar_g_balance_health_limit);
+ // Correct code:
+ //player.health = bound(player.health, player.health + amount,
+ // min(autocvar_g_balance_health_limit, ITEM_COUNT_HARD_LIMIT));
+ player.pauserothealth_finished = max(player.pauserothealth_finished,
+ time + autocvar_g_balance_pause_health_rot);
+ return;
+ }
+ case armorvalue:
+ {
+ // Ugly hack. We do not check if armor goes beyond hard limit since
+ // currently it is done in player_regen. We need to bring back this
+ // check when other code is ported to this function.
+ player.armorvalue = bound(player.armorvalue, player.armorvalue +
+ amount, autocvar_g_balance_armor_limit);
+ // Correct code:
+ //player.armorvalue = bound(player.armorvalue, player.armorvalue +
+ // amount, min(autocvar_g_balance_armor_limit,
+ // ITEM_COUNT_HARD_LIMIT));
+ player.pauserotarmor_finished = max(player.pauserotarmor_finished,
+ time + autocvar_g_balance_pause_armor_rot);
+ return;
+ }
+ case ammo_shells:
+ case ammo_nails:
+ case ammo_rockets:
+ case ammo_cells:
+ case ammo_plasma:
+ {
+ GivePlayerAmmo(player, resource_type, amount);
+ return;
+ }
+ case ammo_fuel:
+ {
+ player.ammo_fuel = bound(player.ammo_fuel, player.ammo_fuel +
+ amount, min(g_pickup_fuel_max, ITEM_COUNT_HARD_LIMIT));
+ player.pauserotfuel_finished = max(player.pauserotfuel_finished,
+ time + autocvar_g_balance_pause_fuel_rot);
+ return;
+ }
+ }
+}
+
+void GivePlayerHealth(entity player, float amount)
+{
+ GivePlayerResource(player, health, amount);
}
void GivePlayerArmor(entity player, float amount)
{
- if (amount == 0)
- {
- return;
- }
- player.armorvalue = bound(player.armorvalue, player.armorvalue + amount,
- g_pickup_armormega_max);
- player.pauserotarmor_finished = max(player.pauserotarmor_finished, time +
- autocvar_g_balance_pause_armor_rot);
+ GivePlayerResource(player, armorvalue, amount);
}
void GivePlayerAmmo(entity player, .float ammotype, float amount)
{
- float maxvalue = 999;
+ if (amount == 0)
+ {
+ return;
+ }
+ float maxvalue = ITEM_COUNT_HARD_LIMIT;
switch (ammotype)
{
case ammo_shells:
maxvalue = g_pickup_nails_max;
break;
}
- case ammo_fuel:
- {
- maxvalue = g_pickup_fuel_max;
- break;
- }
}
- player.(ammotype) = min(player.(ammotype) + amount, maxvalue);
+ player.(ammotype) = min(player.(ammotype) + amount,
+ min(maxvalue, ITEM_COUNT_HARD_LIMIT));
+}
+
+void GivePlayerFuel(entity player, float amount)
+{
+ GivePlayerResource(player, ammo_fuel, amount);
}
void GivePlayerRandomWeapons(entity player, int num_weapons,
}
}
-float Item_GiveAmmoTo(entity item, entity player, .float ammotype, float ammomax, float mode)
+float Item_GiveAmmoTo(entity item, entity player, .float ammotype, float ammomax)
{
if (!item.(ammotype))
return false;
{
if ((player.(ammotype) < ammomax) || item.pickup_anyway > 0)
{
- player.(ammotype) = bound(player.(ammotype), ammomax, player.(ammotype) + item.(ammotype));
- goto YEAH;
+ float amount = item.(ammotype);
+ if ((player.(ammotype) + amount) > ammomax)
+ {
+ amount = ammomax - player.(ammotype);
+ }
+ GivePlayerResource(player, ammotype, amount);
+ return true;
}
}
else if(g_weapon_stay == 2)
float mi = min(item.(ammotype), ammomax);
if (player.(ammotype) < mi)
{
- player.(ammotype) = mi;
- goto YEAH;
+ GivePlayerResource(player, ammotype, mi - player.(ammotype));
}
+ return true;
}
-
return false;
-
-LABEL(YEAH)
- switch(mode)
- {
- case ITEM_MODE_FUEL:
- player.pauserotfuel_finished = max(player.pauserotfuel_finished, time + autocvar_g_balance_pause_fuel_rot);
- break;
- case ITEM_MODE_HEALTH:
- player.pauserothealth_finished = max(player.pauserothealth_finished, time + autocvar_g_balance_pause_health_rot);
- break;
- case ITEM_MODE_ARMOR:
- player.pauserotarmor_finished = max(player.pauserotarmor_finished, time + autocvar_g_balance_pause_armor_rot);
- break;
- default:
- break;
- }
- return true;
}
float Item_GiveTo(entity item, entity player)
}
}
}
-
- pickedup |= Item_GiveAmmoTo(item, player, ammo_fuel, g_pickup_fuel_max, ITEM_MODE_FUEL);
- pickedup |= Item_GiveAmmoTo(item, player, ammo_shells, g_pickup_shells_max, ITEM_MODE_NONE);
- pickedup |= Item_GiveAmmoTo(item, player, ammo_nails, g_pickup_nails_max, ITEM_MODE_NONE);
- pickedup |= Item_GiveAmmoTo(item, player, ammo_rockets, g_pickup_rockets_max, ITEM_MODE_NONE);
- pickedup |= Item_GiveAmmoTo(item, player, ammo_cells, g_pickup_cells_max, ITEM_MODE_NONE);
- pickedup |= Item_GiveAmmoTo(item, player, ammo_plasma, g_pickup_plasma_max, ITEM_MODE_NONE);
- pickedup |= Item_GiveAmmoTo(item, player, health, item.max_health, ITEM_MODE_HEALTH);
- pickedup |= Item_GiveAmmoTo(item, player, armorvalue, item.max_armorvalue, ITEM_MODE_ARMOR);
-
+ pickedup |= Item_GiveAmmoTo(item, player, health, item.max_health);
+ pickedup |= Item_GiveAmmoTo(item, player, armorvalue, item.max_armorvalue);
+ pickedup |= Item_GiveAmmoTo(item, player, ammo_shells, g_pickup_shells_max);
+ pickedup |= Item_GiveAmmoTo(item, player, ammo_nails, g_pickup_nails_max);
+ pickedup |= Item_GiveAmmoTo(item, player, ammo_rockets, g_pickup_rockets_max);
+ pickedup |= Item_GiveAmmoTo(item, player, ammo_cells, g_pickup_cells_max);
+ pickedup |= Item_GiveAmmoTo(item, player, ammo_plasma, g_pickup_plasma_max);
+ pickedup |= Item_GiveAmmoTo(item, player, ammo_fuel, g_pickup_fuel_max);
if (item.itemdef.instanceOfWeaponPickup)
{
WepSet w;
#include <server/defs.qh>
#endif
+/// \brief Unconditional maximum amount of items the player can have.
+const int ITEM_COUNT_HARD_LIMIT = 999;
+
const int AMMO_COUNT = 4; // amount of ammo types to show in the inventory panel
// item networking
void Item_ScheduleInitialRespawn(entity e);
+/// \brief Gives player a resource such as health, armor or ammo.
+/// \param[in,out] player Player to give resource to.
+/// \param[in] resource_type Type of the resource.
+/// \param[in] amount Amount of resource to give.
+/// \return No return.
+void GivePlayerResource(entity player, .float resource_type, float amount);
+
/// \brief Gives health to the player.
/// \param[in,out] player Player to give health to.
/// \param[in] amount Amount of health to give.
/// \return No return.
void GivePlayerAmmo(entity player, .float ammotype, float amount);
+/// \brief Gives fuel to the player.
+/// \param[in,out] player Player to give fuel to.
+/// \param[in] amount Amount of fuel to give.
+/// \return No return.
+void GivePlayerFuel(entity player, float amount);
+
/// \brief Give several random weapons and ammo to the player.
/// \param[in,out] player Player to give weapons to.
/// \param[in] num_weapons Number of weapons to give.
string weapon_names, float shells, float bullets, float rockets,
float cells, float plasma);
-float ITEM_MODE_NONE = 0;
-float ITEM_MODE_HEALTH = 1;
-float ITEM_MODE_ARMOR = 2;
-float ITEM_MODE_FUEL = 3;
-float Item_GiveAmmoTo(entity item, entity player, .float ammotype, float ammomax, float mode);
+float Item_GiveAmmoTo(entity item, entity player, .float ammotype, float ammomax);
float Item_GiveTo(entity item, entity player);
const int WEP_TYPE_MELEE_SEC = 0x800; // secondary attack is melee swing (for animation)
const int WEP_FLAG_DUALWIELD = 0x1000; // weapon can be dual wielded
const int WEP_FLAG_NODUAL = 0x2000; // weapon doesn't work well with dual wielding (fireball etc just explode on fire), doesn't currently prevent anything
+const int WEP_FLAG_PENETRATEWALLS = 0x4000; // weapon has high calibur bullets that can penetrate thick walls (WEAPONTODO)
// variables:
string weaponorder_byid;
CLASS(Rifle, Weapon)
/* ammotype */ ATTRIB(Rifle, ammo_field, .int, ammo_nails);
/* impulse */ ATTRIB(Rifle, impulse, int, 7);
-/* flags */ ATTRIB(Rifle, spawnflags, int, WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN);
+/* flags */ ATTRIB(Rifle, spawnflags, int, WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN | WEP_FLAG_PENETRATEWALLS);
/* rating */ ATTRIB(Rifle, bot_pickupbasevalue, float, 7000);
/* color */ ATTRIB(Rifle, wpcolor, vector, '0.5 1 0');
/* modelname */ ATTRIB(Rifle, mdl, string, "campingrifle");
#ifdef CSQC
void _CSQC_Init();
- void CSQC_Init()
- {
- static_init();
- static_init_late();
- static_init_precache();
- if (_CSQC_Init) _CSQC_Init();
- }
+ void CSQC_Init() { if (_CSQC_Init) _CSQC_Init(); }
#define CSQC_Init _CSQC_Init
void _CSQC_Shutdown();
//dprint("e ", vtos(diffang), " < ", ftos(maxfiredeviation), "\n");
// decide whether to fire this time
- if ((normalize(v) * shotdir) >= cos(maxfiredeviation * DEG2RAD))
+ if (v * shotdir >= cos(maxfiredeviation * DEG2RAD))
if(vdist(trace_endpos-shotorg, <, 500 + 500 * bound(0, skill + this.bot_aggresskill, 10)) || random()*random()>bound(0,(skill+this.bot_aggresskill)*0.05,1))
this.bot_firetimer = time + bound(0.1, 0.5-(skill+this.bot_aggresskill)*0.05, 0.5);
//traceline(shotorg,shotorg+shotdir*1000,false,NULL);
// only add one bot per frame to avoid utter chaos
if(time > botframe_nextthink)
{
- //dprint(ftos(bots), " ? ", ftos(currentbots), "\n");
- while (currentbots < bots)
+ if (currentbots < bots)
{
if (bot_spawn() == NULL)
{
if (IS_SPEC(e)) e = e.enemy;
sf = 0;
- if (CS(e).race_completed) sf |= 1; // forced scoreboard
- if (CS(to).spectatee_status) sf |= 2; // spectator ent number follows
- if (CS(e).zoomstate) sf |= 4; // zoomed
- if (autocvar_sv_showspectators) sf |= 16; // show spectators
+ if (CS(e).race_completed) sf |= BIT(0); // forced scoreboard
+ if (CS(to).spectatee_status) sf |= BIT(1); // spectator ent number follows
+ if (CS(e).zoomstate) sf |= BIT(2); // zoomed
+ if (autocvar_sv_showspectators) sf |= BIT(4); // show spectators
WriteHeader(MSG_ENTITY, ENT_CLIENT_CLIENTDATA);
WriteByte(MSG_ENTITY, sf);
- if (sf & 2)
- {
+ if (sf & BIT(1))
WriteByte(MSG_ENTITY, CS(to).spectatee_status);
- }
- if(sf & 16)
+ if(sf & BIT(4))
{
float specs = CountSpectators(e, to);
WriteByte(MSG_ENTITY, specs);
regen_health_stable = M_ARGV(9, float);
regen_health_rotstable = M_ARGV(10, float);
-
if(!mutator_returnvalue)
if(!STAT(FROZEN, this))
{
this.ammo_fuel = CalcRotRegen(this.ammo_fuel, minf, autocvar_g_balance_fuel_regen, autocvar_g_balance_fuel_regenlinear, frametime * (time > this.pauseregen_finished) * ((this.items & ITEM_JetpackRegen.m_itemid) != 0), maxf, autocvar_g_balance_fuel_rot, autocvar_g_balance_fuel_rotlinear, frametime * (time > this.pauserotfuel_finished), limitf);
}
+ // Ugly hack to make sure the haelth and armor don't go beyond hard limit.
+ // TODO: Remove this hack when all code uses GivePlayerHealth and
+ // GivePlayerArmor.
+ if (this.health > ITEM_COUNT_HARD_LIMIT)
+ {
+ this.health = ITEM_COUNT_HARD_LIMIT;
+ }
+ if (this.armorvalue > ITEM_COUNT_HARD_LIMIT)
+ {
+ this.armorvalue = ITEM_COUNT_HARD_LIMIT;
+ }
+ // End hack.
}
bool zoomstate_set;
BADCVAR("g_freezetag");
BADCVAR("g_freezetag_teams");
BADCVAR("g_invasion_teams");
+ BADCVAR("g_invasion_type");
BADCVAR("g_jailbreak");
BADCVAR("g_jailbreak_teams");
BADCVAR("g_keepaway");
this.havocbot_attack_time = 0;
- if(checkpvs(this.view_ofs,it))
- if(checkpvs(this.view_ofs,best))
+ if(checkpvs(this.origin + this.view_ofs, it))
+ if(checkpvs(this.origin + this.view_ofs, best))
{
// dprint("increasing attack time for this target\n");
this.havocbot_attack_time = time + 2;
#include <server/teamplay.qh>
+IntrusiveList g_invasion_roundends;
+STATIC_INIT(g_invasion_roundends) { g_invasion_roundends = IL_NEW(); }
+
IntrusiveList g_invasion_waves;
STATIC_INIT(g_invasion_waves) { g_invasion_waves = IL_NEW(); }
bool autocvar_g_invasion_zombies_only;
float autocvar_g_invasion_spawn_delay;
+bool victent_present;
+.bool inv_endreached;
+
bool inv_warning_shown; // spammy
.string spawnmob;
+void target_invasion_roundend_use(entity this, entity actor, entity trigger)
+{
+ if(!IS_PLAYER(actor)) { return; }
+
+ actor.inv_endreached = true;
+
+ int plnum = 0;
+ int realplnum = 0;
+ // let's not count bots
+ FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), {
+ ++realplnum;
+ if(it.inv_endreached)
+ ++plnum;
+ });
+ if(plnum < ceil(realplnum * min(1, this.count))) // 70% of players
+ return;
+
+ this.winning = true;
+}
+
+spawnfunc(target_invasion_roundend)
+{
+ if(!g_invasion) { delete(this); return; }
+
+ 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)
+
+ if(!this.count) { this.count = 0.7; } // require at least 70% of the players to reach the end before triggering victory
+
+ this.use = target_invasion_roundend_use;
+
+ IL_PUSH(g_invasion_roundends, this);
+}
+
spawnfunc(invasion_wave)
{
if(!g_invasion) { delete(this); return; }
IL_PUSH(g_invasion_spawns, this);
}
+void ClearWinners();
+
+// Invasion stage mode winning condition: If the attackers triggered a round end (by fulfilling all objectives)
+// they win.
+int WinningCondition_Invasion()
+{
+ WinningConditionHelper(NULL); // set worldstatus
+
+ int status = WINNING_NO;
+
+ if(autocvar_g_invasion_type == INV_TYPE_STAGE)
+ {
+ SetWinners(inv_endreached, true);
+
+ int found = 0;
+ IL_EACH(g_invasion_roundends, true,
+ {
+ ++found;
+ if(it.winning)
+ {
+ bprint("Invasion: round completed.\n");
+ // winners already set (TODO: teamplay support)
+
+ status = WINNING_YES;
+ break;
+ }
+ });
+
+ if(!found)
+ status = WINNING_YES; // just end it? TODO: should warn mapper!
+ }
+ else if(autocvar_g_invasion_type == INV_TYPE_HUNT)
+ {
+ ClearWinners();
+
+ int found = 0; // NOTE: this ends the round if no monsters are placed
+ IL_EACH(g_monsters, !(it.spawnflags & MONSTERFLAG_RESPAWNED),
+ {
+ ++found;
+ });
+
+ if(found <= 0)
+ {
+ FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
+ {
+ it.winning = true;
+ });
+ status = WINNING_YES;
+ }
+ }
+
+ return status;
+}
+
Monster invasion_PickMonster(int supermonster_count)
{
RandomSelection_Init();
if(!(frag_target.spawnflags & MONSTERFLAG_RESPAWNED))
{
- inv_numkilled += 1;
- inv_maxcurrent -= 1;
+ if(autocvar_g_invasion_type == INV_TYPE_ROUND)
+ {
+ inv_numkilled += 1;
+ inv_maxcurrent -= 1;
+ }
if(teamplay) { inv_monsters_perteam[frag_target.team] -= 1; }
if(IS_PLAYER(frag_attacker))
MUTATOR_HOOKFUNCTION(inv, MonsterSpawn)
{
entity mon = M_ARGV(0, entity);
+ mon.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_BOTCLIP | DPCONTENTS_MONSTERCLIP;
+
+ if(autocvar_g_invasion_type == INV_TYPE_HUNT)
+ return false; // allowed
if(!(mon.spawnflags & MONSTERFLAG_SPAWNED))
return true;
if((get_monsterinfo(mon.monsterid)).spawnflags & MON_FLAG_SUPERMONSTER)
Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_INVASION_SUPERMONSTER, mon.monster_name);
-
- mon.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_BOTCLIP | DPCONTENTS_MONSTERCLIP;
-}
-
-MUTATOR_HOOKFUNCTION(inv, OnEntityPreSpawn)
-{
- entity ent = M_ARGV(0, entity);
-
- // TODO: allow these as "rogues" or something
- if(startsWith(ent.classname, "monster_"))
- if(!(ent.spawnflags & MONSTERFLAG_SPAWNED))
- return true;
}
MUTATOR_HOOKFUNCTION(inv, SV_StartFrame)
{
+ if(autocvar_g_invasion_type != INV_TYPE_ROUND)
+ return; // uses map spawned monsters
+
monsters_total = inv_maxspawned; // TODO: make sure numspawned never exceeds maxspawned
monsters_killed = inv_numkilled;
}
MUTATOR_HOOKFUNCTION(inv, PlayerRegen)
{
- // no regeneration in invasion
+ // no regeneration in invasion, regardless of the game type
return true;
}
}
}
-MUTATOR_HOOKFUNCTION(inv, SV_ParseClientCommand)
-{
- if(MUTATOR_RETURNVALUE) // command was already handled?
- return;
-
- entity player = M_ARGV(0, entity);
- string cmd_name = M_ARGV(1, string);
-
- if(cmd_name == "debuginvasion")
- {
- sprint(player, strcat("inv_maxspawned = ", ftos(inv_maxspawned), "\n"));
- sprint(player, strcat("inv_numspawned = ", ftos(inv_numspawned), "\n"));
- sprint(player, strcat("inv_numkilled = ", ftos(inv_numkilled), "\n"));
- sprint(player, strcat("inv_roundcnt = ", ftos(inv_roundcnt), "\n"));
- sprint(player, strcat("monsters_total = ", ftos(monsters_total), "\n"));
- sprint(player, strcat("monsters_killed = ", ftos(monsters_killed), "\n"));
- sprint(player, strcat("inv_monsterskill = ", ftos(inv_monsterskill), "\n"));
-
- return true;
- }
-}
-
MUTATOR_HOOKFUNCTION(inv, BotShouldAttack)
{
entity targ = M_ARGV(1, entity);
MUTATOR_HOOKFUNCTION(inv, SetStartItems)
{
- start_health = 200;
- start_armorvalue = 200;
+ if(autocvar_g_invasion_type == INV_TYPE_ROUND)
+ {
+ start_health = 200;
+ start_armorvalue = 200;
+ }
}
MUTATOR_HOOKFUNCTION(inv, AccuracyTargetValid)
return true;
}
+MUTATOR_HOOKFUNCTION(inv, CheckRules_World)
+{
+ if(autocvar_g_invasion_type == INV_TYPE_ROUND)
+ return false;
+
+ M_ARGV(0, float) = WinningCondition_Invasion();
+ return true;
+}
+
MUTATOR_HOOKFUNCTION(inv, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
{
M_ARGV(0, float) = invasion_teams;
void invasion_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
{
+ if(autocvar_g_invasion_type == INV_TYPE_HUNT || autocvar_g_invasion_type == INV_TYPE_STAGE)
+ cvar_set("fraglimit", "0");
+
if(autocvar_g_invasion_teams)
{
invasion_teams = bound(2, autocvar_g_invasion_teams, 4);
independent_players = 0;
- round_handler_Spawn(Invasion_CheckPlayers, Invasion_CheckWinner, Invasion_RoundStart);
- round_handler_Init(5, autocvar_g_invasion_warmup, autocvar_g_invasion_round_timelimit);
+ if(autocvar_g_invasion_type == INV_TYPE_ROUND)
+ {
+ round_handler_Spawn(Invasion_CheckPlayers, Invasion_CheckWinner, Invasion_RoundStart);
+ round_handler_Init(5, autocvar_g_invasion_warmup, autocvar_g_invasion_round_timelimit);
- inv_roundcnt = 0;
- inv_maxrounds = 15; // 15?
+ inv_roundcnt = 0;
+ inv_maxrounds = 15; // 15?
+ }
}
void invasion_Initialize()
#define autocvar_g_invasion_point_limit cvar("g_invasion_point_limit")
int autocvar_g_invasion_teams;
+int autocvar_g_invasion_type;
bool autocvar_g_invasion_team_spawns;
bool g_invasion;
void invasion_Initialize();
float inv_monsterskill;
const float ST_INV_KILLS = 1;
+
+const int INV_TYPE_ROUND = 0; // round-based waves of enemies
+const int INV_TYPE_HUNT = 1; // clear the map of placed enemies
+const int INV_TYPE_STAGE = 2; // reach the end of the level
this.takedamage = DAMAGE_NO;
this.event_damage = func_null;
+ MUTATOR_CALLHOOK(PrepareExplosionByDamage, this, attacker);
+
if(IS_CLIENT(attacker) && !autocvar_g_projectiles_keep_owner)
{
this.owner = attacker;
this.realowner = attacker;
}
- MUTATOR_CALLHOOK(PrepareExplosionByDamage, this, attacker);
-
// do not explode NOW but in the NEXT FRAME!
// because recursive calls to RadiusDamage are not allowed
this.nextthink = time;
float oldsolid = ent.dphitcontentsmask;
if(!IS_CLIENT(ent))
antilag = false; // no antilag for non-clients!
- if (IS_PLAYER(ent) && ent.(weaponentity).m_weapon == WEP_RIFLE)
+ if (IS_PLAYER(ent) && (ent.(weaponentity).m_weapon.spawnflags & WEP_FLAG_PENETRATEWALLS))
ent.dphitcontentsmask = DPCONTENTS_BODY | DPCONTENTS_CORPSE;
else
ent.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
{
dpnoshadow
deformVertexes autosprite
- dppolygonoffset -6000
{
map "models/ok_nade_counter/ok_nade_counter_01"
blendfunc add
{
dpnoshadow
deformVertexes autosprite
- dppolygonoffset -6000
{
map "models/ok_nade_counter/ok_nade_counter_02"
blendfunc add
{
dpnoshadow
deformVertexes autosprite
- dppolygonoffset -6000
{
map "models/ok_nade_counter/ok_nade_counter_03"
blendfunc add
{
dpnoshadow
deformVertexes autosprite
- dppolygonoffset -6000
{
map "models/ok_nade_counter/ok_nade_counter_04"
blendfunc add
{
dpnoshadow
deformVertexes autosprite
- dppolygonoffset -6000
{
map "models/ok_nade_counter/ok_nade_counter_05"
blendfunc add
{
dpnoshadow
deformVertexes autosprite
- dppolygonoffset -6000
{
map "models/ok_nade_counter/ok_nade_counter_06"
blendfunc add
{
dpnoshadow
deformVertexes autosprite
- dppolygonoffset -6000
{
map "models/ok_nade_counter/ok_nade_counter_07"
blendfunc add
{
dpnoshadow
deformVertexes autosprite
- dppolygonoffset -6000
{
map "models/ok_nade_counter/ok_nade_counter_08"
blendfunc add
{
dpnoshadow
deformVertexes autosprite
- dppolygonoffset -6000
{
map "models/ok_nade_counter/ok_nade_counter_09"
blendfunc add
}
-}
\ No newline at end of file
+}