#ifdef SVQC
.float strength_finished; // NOTE: this field is used only by map entities, it does not directly apply the strength stat
.float invincible_finished; // ditto
+ .float buffs_finished; // ditts
-#define spawnfunc_body(item) \
- if (!Item_IsDefinitionAllowed(item)) \
+#define SPAWNFUNC_BODY(item) \
+ if (item && Item_IsDefinitionAllowed(item)) \
+ StartItem(this, item); \
+ else \
{ \
startitem_failed = true; \
delete(this); \
- return; \
- } \
- StartItem(this, item)
+ }
#define SPAWNFUNC_ITEM(name, item) \
spawnfunc(name) \
{ \
- spawnfunc_body(item); \
+ SPAWNFUNC_BODY(item) \
}
#define SPAWNFUNC_ITEM_COND(name, cond, item1, item2) \
- spawnfunc(name) \
- { \
- entity item = (cond) ? item1 : item2; \
- spawnfunc_body(item); \
- }
+ SPAWNFUNC_ITEM(name, (cond ? item1 : item2))
#else
{
switch(buffname)
{
- case "ammoregen": return "ammo";
- case "haste": case "scout": return "speed";
- case "guard": return "resistance";
- case "revival": case "regen": return "medic";
- case "invis": return "invisible";
- case "jumper": return "jump";
+ case "ammoregen": return "ammo"; // Q3TA ammoregen
+ case "haste": return "speed"; // Q3A haste
+ case "doubler": return "inferno"; // Q3TA doubler
+ case "scout": return "bash"; // Q3TA scout
+ case "guard": return "resistance"; // Q3TA guard
+ case "revival": case "regen": return "medic"; // WOP revival, Q3A regen
+ case "invis": return "invisible"; // Q3A invis
+ case "jumper": return "jump"; // WOP jumper
+ case "invulnerability": return "vampire"; // Q3TA invulnerability
+ case "kamikaze": return "vengeance"; // Q3TA kamikaze
+ case "teleporter": return "swapper"; // Q3A personal teleporter
default: return buffname;
}
}
REGISTER_BUFF(AMMO) {
this.m_name = _("Ammo");
this.netname = "ammo";
+ this.m_icon = "buff_ammo";
this.m_skin = 3;
this.m_color = '0.76 1 0.1';
}
BUFF_SPAWNFUNCS(ammo, BUFF_AMMO)
-BUFF_SPAWNFUNC_Q3TA_COMPAT(ammoregen, BUFF_AMMO)
+BUFF_SPAWNFUNC_Q3COMPAT(item_ammoregen, BUFF_AMMO)
REGISTER_BUFF(RESISTANCE) {
this.m_name = _("Resistance");
this.netname = "resistance";
+ this.m_icon = "buff_resistance";
this.m_skin = 0;
this.m_color = '0.36 1 0.07';
}
BUFF_SPAWNFUNCS(resistance, BUFF_RESISTANCE)
-BUFF_SPAWNFUNC_Q3TA_COMPAT(guard, BUFF_RESISTANCE)
+BUFF_SPAWNFUNC_Q3COMPAT(item_guard, BUFF_RESISTANCE)
REGISTER_BUFF(SPEED) {
this.m_name = _("Speed");
this.netname = "speed";
+ this.m_icon = "buff_speed";
this.m_skin = 9;
this.m_color = '0.1 1 0.84';
}
BUFF_SPAWNFUNCS(speed, BUFF_SPEED)
-BUFF_SPAWNFUNC_Q3TA_COMPAT(haste, BUFF_SPEED)
-BUFF_SPAWNFUNC_Q3TA_COMPAT(scout, BUFF_SPEED)
+BUFF_SPAWNFUNC_Q3COMPAT(item_haste, BUFF_SPEED)
REGISTER_BUFF(MEDIC) {
this.m_name = _("Medic");
this.netname = "medic";
+ this.m_icon = "buff_medic";
this.m_skin = 1;
this.m_color = '1 0.12 0';
}
BUFF_SPAWNFUNCS(medic, BUFF_MEDIC)
-BUFF_SPAWNFUNC_Q3TA_COMPAT(regen, BUFF_MEDIC)
-BUFF_SPAWNFUNC_Q3TA_COMPAT(revival, BUFF_MEDIC)
+BUFF_SPAWNFUNC_Q3COMPAT(item_regen, BUFF_MEDIC)
+BUFF_SPAWNFUNC_Q3COMPAT(item_revival, BUFF_MEDIC)
REGISTER_BUFF(BASH) {
this.m_name = _("Bash");
this.netname = "bash";
+ this.m_icon = "buff_bash";
this.m_skin = 5;
this.m_color = '1 0.39 0';
}
BUFF_SPAWNFUNCS(bash, BUFF_BASH)
-BUFF_SPAWNFUNC_Q3TA_COMPAT(doubler, BUFF_BASH)
+BUFF_SPAWNFUNC_Q3COMPAT(item_scout, BUFF_BASH)
REGISTER_BUFF(VAMPIRE) {
this.m_name = _("Vampire");
this.netname = "vampire";
+ this.m_icon = "buff_vampire";
this.m_skin = 2;
this.m_color = '1 0 0.24';
}
BUFF_SPAWNFUNCS(vampire, BUFF_VAMPIRE)
+BUFF_SPAWNFUNC_Q3COMPAT(holdable_invulnerability, BUFF_VAMPIRE)
REGISTER_BUFF(DISABILITY) {
this.m_name = _("Disability");
this.netname = "disability";
+ this.m_icon = "buff_disability";
this.m_skin = 7;
this.m_color = '0.94 0.3 1';
}
REGISTER_BUFF(VENGEANCE) {
this.m_name = _("Vengeance");
this.netname = "vengeance";
+ this.m_icon = "buff_vengeance";
this.m_skin = 15;
this.m_color = '1 0.23 0.61';
}
BUFF_SPAWNFUNCS(vengeance, BUFF_VENGEANCE)
+BUFF_SPAWNFUNC_Q3COMPAT(holdable_kamikaze, BUFF_VENGEANCE)
REGISTER_BUFF(JUMP) {
this.m_name = _("Jump");
this.netname = "jump";
+ this.m_icon = "buff_jump";
this.m_skin = 10;
this.m_color = '0.24 0.78 1';
}
BUFF_SPAWNFUNCS(jump, BUFF_JUMP)
-BUFF_SPAWNFUNC_Q3TA_COMPAT(jumper, BUFF_JUMP)
+BUFF_SPAWNFUNC_Q3COMPAT(item_jumper, BUFF_JUMP)
REGISTER_BUFF(INVISIBLE) {
this.m_name = _("Invisible");
this.netname = "invisible";
+ this.m_icon = "buff_invisible";
this.m_skin = 12;
this.m_color = '0.5 0.5 1';
}
BUFF_SPAWNFUNCS(invisible, BUFF_INVISIBLE)
-BUFF_SPAWNFUNC_Q3TA_COMPAT(invis, BUFF_INVISIBLE)
+BUFF_SPAWNFUNC_Q3COMPAT(item_invis, BUFF_INVISIBLE)
REGISTER_BUFF(INFERNO) {
this.m_name = _("Inferno");
this.netname = "inferno";
+ this.m_icon = "buff_inferno";
this.m_skin = 16;
this.m_color = '1 0.62 0';
}
BUFF_SPAWNFUNCS(inferno, BUFF_INFERNO)
+BUFF_SPAWNFUNC_Q3COMPAT(item_doubler, BUFF_INFERNO)
REGISTER_BUFF(SWAPPER) {
this.m_name = _("Swapper");
this.netname = "swapper";
+ this.m_icon = "buff_swapper";
this.m_skin = 17;
this.m_color = '0.63 0.36 1';
}
BUFF_SPAWNFUNCS(swapper, BUFF_SWAPPER)
+BUFF_SPAWNFUNC_Q3COMPAT(holdable_teleporter, BUFF_SWAPPER)
REGISTER_BUFF(MAGNET) {
this.m_name = _("Magnet");
this.netname = "magnet";
+ this.m_icon = "buff_magnet";
this.m_skin = 18;
this.m_color = '1 0.95 0.18';
}
REGISTER_BUFF(LUCK) {
this.m_name = _("Luck");
this.netname = "luck";
+ this.m_icon = "buff_luck";
this.m_skin = 19;
this.m_color = '1 0.23 0.44';
}
REGISTER_BUFF(FLIGHT) {
this.m_name = _("Flight");
this.netname = "flight";
+ this.m_icon = "buff_flight";
this.m_skin = 11;
this.m_color = '0.23 0.44 1';
}
BUFF_SPAWNFUNCS(flight, BUFF_FLIGHT)
-BUFF_SPAWNFUNC_Q3TA_COMPAT(flight, BUFF_FLIGHT)
+BUFF_SPAWNFUNC_Q3COMPAT(item_flight, BUFF_FLIGHT)
REGISTER_RADARICON(Buff, 1);
#endif
- REGISTRY(Buffs, BITS(5))
- REGISTER_REGISTRY(Buffs)
- REGISTRY_CHECK(Buffs)
-
#define REGISTER_BUFF(id) \
- REGISTER(Buffs, BUFF_##id, m_id, NEW(Buff))
+ REGISTER(StatusEffect, BUFF_##id, m_id, NEW(Buff))
- #include <common/items/item/pickup.qh>
- CLASS(Buff, Pickup)
+ #include <common/mutators/mutator/status_effects/_mod.qh>
+ CLASS(Buff, StatusEffects)
/** bit index */
ATTRIB(Buff, m_itemid, int, 0);
ATTRIB(Buff, netname, string, "buff");
+ ATTRIB(Buff, m_icon, string, "buff");
ATTRIB(Buff, m_color, vector, '1 1 1');
ATTRIB(Buff, m_name, string, "Buff");
ATTRIB(Buff, m_skin, int, 0);
+ ATTRIB(Buff, m_lifetime, float, 60);
ATTRIB(Buff, m_sprite, string, "");
METHOD(Buff, display, void(entity this, void(string name, string icon) returns)) {
- returns(this.m_name, sprintf("/gfx/hud/%s/buff_%s", cvar_string("menu_skin"), this.netname));
+ returns(this.m_name, sprintf("/gfx/hud/%s/%s", cvar_string("menu_skin"), this.m_icon));
}
#ifdef SVQC
METHOD(Buff, m_time, float(Buff this))
ENDCLASS(Buff)
STATIC_INIT(REGISTER_BUFFS) {
- FOREACH(Buffs, true, {
+ FOREACH(StatusEffect, it.instanceOfBuff, {
it.m_itemid = BIT(it.m_id - 1);
it.m_sprite = strzone(strcat("buff-", it.netname));
});
}
#ifdef SVQC
+ .entity buffdef;
void buff_Init(entity ent);
void buff_Init_Compat(entity ent, entity replacement);
#define BUFF_SPAWNFUNC(e, b, t) spawnfunc(item_buff_##e) { \
- STAT(BUFFS, this) = b.m_itemid; \
+ this.buffdef = b; \
- this.team = t; \
+ if(teamplay) \
+ this.team_forced = t; \
buff_Init(this); \
}
#define BUFF_SPAWNFUNCS(e, b) \
BUFF_SPAWNFUNC(e##_team2, b, NUM_TEAM_2) \
BUFF_SPAWNFUNC(e##_team3, b, NUM_TEAM_3) \
BUFF_SPAWNFUNC(e##_team4, b, NUM_TEAM_4)
- #define BUFF_SPAWNFUNC_Q3TA_COMPAT(o, r) spawnfunc(item_##o) { buff_Init_Compat(this, r); }
+ #define BUFF_SPAWNFUNC_Q3COMPAT(o, r) spawnfunc(o) { buff_Init_Compat(this, r); }
#else
#define BUFF_SPAWNFUNC(e, b, t)
#define BUFF_SPAWNFUNCS(e, b)
- #define BUFF_SPAWNFUNC_Q3TA_COMPAT(o, r)
+ #define BUFF_SPAWNFUNC_Q3COMPAT(o, r)
#endif
string Buff_UndeprecateName(string buffname);
- entity buff_FirstFromFlags(int _buffs);
- REGISTER_BUFF(Null);
- BUFF_SPAWNFUNCS(random, BUFF_Null)
+ BUFF_SPAWNFUNCS(random, NULL)
- REGISTRY_DEFINE_GET(Buffs, BUFF_Null)
#include "all.inc"
vector buff_GlowColor(entity buff)
{
- //if(buff.team) { return Team_ColorRGB(buff.team); }
+ //if(buff.team_forced) { return Team_ColorRGB(buff.team_forced); }
return buff.m_color;
}
// buff item
bool buff_Waypoint_visible_for_player(entity this, entity player, entity view)
{
- if(!this.owner.buff_active && !this.owner.buff_activetime)
+ if(!this.owner.buff_active && !this.owner.buff_activetime || !this.owner.buffdef)
return false;
- if (STAT(BUFFS, view))
+ entity heldbuff = buff_FirstFromFlags(view); // TODO: cache this information so it isn't performing a loop every frame
+ if (heldbuff)
{
- return CS_CVAR(view).cvar_cl_buffs_autoreplace == false || STAT(BUFFS, view) != STAT(BUFFS, this.owner);
+ return CS_CVAR(view).cvar_cl_buffs_autoreplace == false || heldbuff != this.owner.buffdef;
}
return WaypointSprite_visible_for_player(this, player, view);
{
if(autocvar_g_buffs_waypoint_distance <= 0) return;
- entity buff = buff_FirstFromFlags(STAT(BUFFS, e));
+ entity buff = e.buffdef;
- entity wp = WaypointSprite_Spawn(WP_Buff, 0, autocvar_g_buffs_waypoint_distance, e, '0 0 1' * e.maxs.z, NULL, e.team, e, buff_waypoint, true, RADARICON_Buff);
+ entity wp = WaypointSprite_Spawn(WP_Buff, 0, autocvar_g_buffs_waypoint_distance, e, '0 0 1' * e.maxs.z, NULL, e.team_forced, e, buff_waypoint, true, RADARICON_Buff);
wp.wp_extra = buff.m_id;
WaypointSprite_UpdateTeamRadar(e.buff_waypoint, RADARICON_Buff, e.glowmod);
e.buff_waypoint.waypointsprite_visible_for_player = buff_Waypoint_visible_for_player;
if(!IS_PLAYER(toucher))
return; // incase mutator changed toucher
- if((this.team && DIFF_TEAM(toucher, this))
+ if((this.team_forced && toucher.team != this.team_forced)
|| (STAT(FROZEN, toucher))
|| (toucher.vehicle)
+ || (!this.buffdef) // TODO: error out or maybe reset type if this occurs?
|| (time < PS(toucher).buff_shield)
)
{
return;
}
- if (STAT(BUFFS, toucher))
+ entity heldbuff = buff_FirstFromFlags(toucher);
+ entity thebuff = this.buffdef;
+
+ if (heldbuff)
{
- if (CS_CVAR(toucher).cvar_cl_buffs_autoreplace && STAT(BUFFS, toucher) != STAT(BUFFS, this))
+ if (CS_CVAR(toucher).cvar_cl_buffs_autoreplace && heldbuff != thebuff)
{
// TODO: lost-gained notification for this case
- int buffid = buff_FirstFromFlags(STAT(BUFFS, toucher)).m_id;
+ int buffid = heldbuff.m_id;
Send_Notification(NOTIF_ONE, toucher, MSG_INFO, INFO_ITEM_BUFF_LOST, toucher.netname, buffid);
if(!IS_INDEPENDENT_PLAYER(toucher))
Send_Notification(NOTIF_ALL_EXCEPT, toucher, MSG_INFO, INFO_ITEM_BUFF_LOST, toucher.netname, buffid);
- STAT(BUFFS, toucher) = 0;
//sound(toucher, CH_TRIGGER, SND_BUFF_LOST, VOL_BASE, ATTN_NORM);
}
else { return; } // do nothing
this.owner = toucher;
this.buff_active = false;
this.lifetime = 0;
- entity thebuff = buff_FirstFromFlags(STAT(BUFFS, this));
Send_Notification(NOTIF_ONE, toucher, MSG_MULTI, ITEM_BUFF_GOT, thebuff.m_id);
if(!IS_INDEPENDENT_PLAYER(toucher))
Send_Notification(NOTIF_ALL_EXCEPT, toucher, MSG_INFO, INFO_ITEM_BUFF, toucher.netname, thebuff.m_id);
Send_Effect(EFFECT_ITEM_PICKUP, CENTER_OR_VIEWOFS(this), '0 0 0', 1);
sound(toucher, CH_TRIGGER, SND_SHIELD_RESPAWN, VOL_BASE, ATTN_NORM);
- STAT(BUFFS, toucher) |= (STAT(BUFFS, this));
- STAT(LAST_PICKUP, toucher) = time;
- float bufftime = ((this.count) ? this.count : thebuff.m_time(thebuff));
+ float oldtime = StatusEffects_gettime(thebuff, toucher);
+ float bufftime = ((this.buffs_finished) ? this.buffs_finished : thebuff.m_time(thebuff));
+
+ buff_RemoveAll(toucher, STATUSEFFECT_REMOVE_NORMAL); // remove previous buffs so that a new one may be added
if(bufftime)
- STAT(BUFF_TIME, toucher) = min(time + bufftime, max(STAT(BUFF_TIME, toucher), time) + bufftime);
+ StatusEffects_apply(thebuff, toucher, min(time + bufftime, max(oldtime, time) + bufftime), 0);
+ else
+ StatusEffects_apply(thebuff, toucher, time + 999, 0); // HACK: zero timer means "infinite"!
+
+ STAT(LAST_PICKUP, toucher) = time;
}
float buff_Available(entity buff)
{
- if (buff == BUFF_Null)
+ if (!buff)
return false;
if (buff == BUFF_AMMO && ((start_items & IT_UNLIMITED_AMMO) || cvar("g_melee_only")))
return false;
void buff_NewType(entity ent)
{
RandomSelection_Init();
- FOREACH(Buffs, buff_Available(it),
+ FOREACH(StatusEffect, it.instanceOfBuff && buff_Available(it),
{
// if it's already been chosen, give it a lower priority
float myseencount = (it.buff_seencount > 0) ? it.buff_seencount : 1; // no division by zero please!
RandomSelection_AddEnt(it, max(0.2, 1 / myseencount), 1);
});
entity newbuff = RandomSelection_chosen_ent;
+ if(!newbuff)
+ return;
newbuff.buff_seencount += 1; // lower chances of seeing this buff again soon
- STAT(BUFFS, ent) = newbuff.m_itemid;
+ ent.buffdef = newbuff;
+ }
+
+ void buff_RemoveAll(entity actor, int removal_type)
+ {
+ if(!actor.statuseffects)
+ return;
+ FOREACH(StatusEffect, it.instanceOfBuff,
+ {
+ it.m_remove(it, actor, removal_type);
+ });
+ }
+
+ entity buff_FirstFromFlags(entity actor)
+ {
+ if(!actor.statuseffects)
+ return NULL;
+ FOREACH(StatusEffect, it.instanceOfBuff && it.m_active(it, actor), { return it; });
+ return NULL;
}
void buff_Think(entity this)
if(this.buff_waypoint && autocvar_g_buffs_waypoint_distance <= 0)
WaypointSprite_Kill(this.buff_waypoint);
- if(STAT(BUFFS, this) != this.oldbuffs)
+ if(this.buffdef != this.oldbuffs)
{
- entity buff = buff_FirstFromFlags(STAT(BUFFS, this));
+ entity buff = this.buffdef;
this.color = buff.m_color;
this.glowmod = buff_GlowColor(buff);
this.skin = buff.m_skin;
WaypointSprite_UpdateBuildFinished(this.buff_waypoint, time + this.buff_activetime - frametime);
}
- this.oldbuffs = STAT(BUFFS, this);
+ this.oldbuffs = this.buffdef;
}
if(!game_stopped)
}
if(!this.buff_active && !this.buff_activetime)
- if(!this.owner || STAT(FROZEN, this.owner) || IS_DEAD(this.owner) || !this.owner.iscreature || this.owner.vehicle || !(STAT(BUFFS, this.owner) & STAT(BUFFS, this)) || this.pickup_anyway > 0 || (this.pickup_anyway >= 0 && autocvar_g_buffs_pickup_anyway))
+ if(!this.owner || STAT(FROZEN, this.owner) || IS_DEAD(this.owner) || !this.owner.iscreature || this.owner.vehicle
+ || this.pickup_anyway > 0 || (this.pickup_anyway >= 0 && autocvar_g_buffs_pickup_anyway) || this.buffdef != buff_FirstFromFlags(this.owner))
{
buff_SetCooldown(this, autocvar_g_buffs_cooldown_respawn + frametime);
this.owner = NULL;
if(this.buff_active)
{
- if(this.team && !this.buff_waypoint)
+ if(this.team_forced && !this.buff_waypoint)
buff_Waypoint_Spawn(this);
if(this.lifetime && time >= this.lifetime)
bool buff_Customize(entity this, entity client)
{
entity player = WaypointSprite_getviewentity(client);
- if(!this.buff_active || (this.team_forced && player.team != this.team_forced))
- if((!this.buff_active || !this.buffdef) || (this.team && DIFF_TEAM(player, this)))
++ if((!this.buff_active || !this.buffdef) || (this.team_forced && player.team != this.team_forced))
{
this.alpha = 0.3;
if(this.effects & EF_FULLBRIGHT) { this.effects &= ~(EF_FULLBRIGHT); }
{
if(!cvar("g_buffs")) { delete(this); return; }
- entity buff = buff_FirstFromFlags(STAT(BUFFS, this));
- if(!teamplay && this.team) { this.team = 0; }
-
+ entity buff = this.buffdef;
- if(!STAT(BUFFS, this) || !buff_Available(buff))
+ if(!buff || !buff_Available(buff))
buff_NewType(this);
this.classname = "item_buff";
IL_PUSH(g_items, this);
setthink(this, buff_Think);
settouch(this, buff_Touch);
+ setmodel(this, MDL_BUFF);
+ setsize(this, BUFF_MIN, BUFF_MAX);
this.reset = buff_Reset;
this.nextthink = time + 0.1;
this.gravity = 1;
this.pflags = PFLAGS_FULLDYNAMIC;
this.dtor = buff_Delete;
+ if(!this.buffs_finished)
+ this.buffs_finished = this.count; // legacy support
+
if(this.spawnflags & 1)
this.noalign = true;
if(this.noalign)
set_movetype(this, MOVETYPE_NONE); // reset by random location
- setmodel(this, MDL_BUFF);
- setsize(this, BUFF_MIN, BUFF_MAX);
-
if(cvar("g_buffs_random_location") || (this.spawnflags & 64))
buff_Respawn(this);
}
void buff_Init_Compat(entity ent, entity replacement)
{
- if (ent.spawnflags & 2)
- ent.team = NUM_TEAM_1;
- else if (ent.spawnflags & 4)
- ent.team = NUM_TEAM_2;
+ if (teamplay)
+ {
+ if (ent.spawnflags & 2)
+ ent.team_forced = NUM_TEAM_1;
+ else if (ent.spawnflags & 4)
+ ent.team_forced = NUM_TEAM_2;
+ }
- STAT(BUFFS, ent) = replacement.m_itemid;
+ ent.buffdef = replacement;
buff_Init(ent);
}
if(frag_deathtype == DEATH_BUFF.m_id) { return; }
- if(STAT(BUFFS, frag_target) & BUFF_RESISTANCE.m_itemid)
+ if(StatusEffects_active(BUFF_RESISTANCE, frag_target))
{
float reduced = frag_damage * autocvar_g_buffs_resistance_blockpercent;
frag_damage = bound(0, frag_damage - reduced, frag_damage);
}
- if(STAT(BUFFS, frag_target) & BUFF_SPEED.m_itemid)
+ if(StatusEffects_active(BUFF_SPEED, frag_target))
if(frag_target != frag_attacker)
frag_damage *= autocvar_g_buffs_speed_damage_take;
- if(STAT(BUFFS, frag_target) & BUFF_MEDIC.m_itemid)
+ if(StatusEffects_active(BUFF_MEDIC, frag_target))
if((GetResource(frag_target, RES_HEALTH) - frag_damage) <= 0)
if(!ITEM_DAMAGE_NEEDKILL(frag_deathtype))
if(frag_attacker)
if(random() <= autocvar_g_buffs_medic_survive_chance)
frag_damage = max(5, GetResource(frag_target, RES_HEALTH) - autocvar_g_buffs_medic_survive_health);
- if(STAT(BUFFS, frag_target) & BUFF_JUMP.m_itemid)
+ if(StatusEffects_active(BUFF_JUMP, frag_target))
if(frag_deathtype == DEATH_FALL.m_id)
frag_damage = 0;
- if(STAT(BUFFS, frag_target) & BUFF_VENGEANCE.m_itemid)
+ if(StatusEffects_active(BUFF_VENGEANCE, frag_target))
if(frag_attacker)
if(frag_attacker != frag_target)
if(!ITEM_DAMAGE_NEEDKILL(frag_deathtype))
dmgent.nextthink = time + 0.1;
}
- if(STAT(BUFFS, frag_target) & BUFF_BASH.m_itemid)
+ if(StatusEffects_active(BUFF_BASH, frag_target))
if(frag_attacker != frag_target)
frag_force = '0 0 0';
- if(STAT(BUFFS, frag_attacker) & BUFF_BASH.m_itemid)
+ if(StatusEffects_active(BUFF_BASH, frag_attacker))
if(frag_force)
{
if(frag_attacker == frag_target)
frag_force *= autocvar_g_buffs_bash_force;
}
- if(STAT(BUFFS, frag_attacker) & BUFF_DISABILITY.m_itemid)
+ if(StatusEffects_active(BUFF_DISABILITY, frag_attacker))
if(frag_target != frag_attacker)
frag_target.buff_disability_time = time + autocvar_g_buffs_disability_slowtime;
- if(STAT(BUFFS, frag_target) & BUFF_INFERNO.m_itemid)
+ if(StatusEffects_active(BUFF_INFERNO, frag_target))
{
if(frag_deathtype == DEATH_FIRE.m_id)
frag_damage = 0;
frag_damage *= 0.5; // TODO: cvarize?
}
- if(STAT(BUFFS, frag_attacker) & BUFF_LUCK.m_itemid)
+ if(StatusEffects_active(BUFF_LUCK, frag_attacker))
if(frag_attacker != frag_target)
if(autocvar_g_buffs_luck_damagemultiplier > 0)
if(random() <= autocvar_g_buffs_luck_chance)
frag_damage *= autocvar_g_buffs_luck_damagemultiplier;
- if(STAT(BUFFS, frag_attacker) & BUFF_INFERNO.m_itemid)
+ if(StatusEffects_active(BUFF_INFERNO, frag_attacker))
if(frag_target != frag_attacker) {
float btime = buff_Inferno_CalculateTime(
frag_damage,
{
entity frag_attacker = M_ARGV(1, entity);
entity frag_target = M_ARGV(2, entity);
- if(!(STAT(BUFFS, frag_attacker) & BUFF_VAMPIRE.m_itemid))
+ if(!StatusEffects_active(BUFF_VAMPIRE, frag_attacker))
return;
float health_take = bound(0, M_ARGV(4, float), GetResource(frag_target, RES_HEALTH));
entity player = M_ARGV(0, entity);
buffs_BuffModel_Remove(player);
- player.oldbuffs = 0;
+ player.oldbuffs = NULL;
// reset timers here to prevent them continuing after re-spawn
player.buff_disability_time = 0;
player.buff_disability_effect_time = 0;
entity player = M_ARGV(0, entity);
// these automatically reset, no need to worry
- if(STAT(BUFFS, player) & BUFF_SPEED.m_itemid)
+ if(StatusEffects_active(BUFF_SPEED, player))
STAT(MOVEVARS_HIGHSPEED, player) *= autocvar_g_buffs_speed_speed;
if(time < player.buff_disability_time)
entity player = M_ARGV(0, entity);
// these automatically reset, no need to worry
- if(STAT(BUFFS, player) & BUFF_JUMP.m_itemid)
+ if(StatusEffects_active(BUFF_JUMP, player))
STAT(MOVEVARS_JUMPVELOCITY, player) = autocvar_g_buffs_jump_height;
}
{
entity frag_target = M_ARGV(2, entity);
- if(STAT(BUFFS, frag_target))
+ entity heldbuff = buff_FirstFromFlags(frag_target);
+ if(heldbuff)
{
- int buffid = buff_FirstFromFlags(STAT(BUFFS, frag_target)).m_id;
+ int buffid = heldbuff.m_id;
if(!IS_INDEPENDENT_PLAYER(frag_target))
Send_Notification(NOTIF_ALL_EXCEPT, frag_target, MSG_INFO, INFO_ITEM_BUFF_LOST, frag_target.netname, buffid);
- STAT(BUFFS, frag_target) = 0;
- STAT(BUFF_TIME, frag_target) = 0;
buffs_BuffModel_Remove(frag_target);
}
entity player = M_ARGV(0, entity);
- if(STAT(BUFFS, player))
+ entity heldbuff = buff_FirstFromFlags(player);
+ if(heldbuff)
{
- int buffid = buff_FirstFromFlags(STAT(BUFFS, player)).m_id;
+ int buffid = heldbuff.m_id;
Send_Notification(NOTIF_ONE, player, MSG_MULTI, ITEM_BUFF_DROP, buffid);
if(!IS_INDEPENDENT_PLAYER(player))
Send_Notification(NOTIF_ALL_EXCEPT, player, MSG_INFO, INFO_ITEM_BUFF_LOST, player.netname, buffid);
- STAT(BUFFS, player) = 0;
- STAT(BUFF_TIME, player) = 0;
+ buff_RemoveAll(player, STATUSEFFECT_REMOVE_NORMAL);
PS(player).buff_shield = time + max(0, autocvar_g_buffs_pickup_delay);
sound(player, CH_TRIGGER, SND_BUFF_LOST, VOL_BASE, ATTN_NORM);
return true;
if(MUTATOR_RETURNVALUE || game_stopped) return;
entity player = M_ARGV(0, entity);
- if(STAT(BUFFS, player) & BUFF_SWAPPER.m_itemid)
+ if(StatusEffects_active(BUFF_SWAPPER, player))
{
float best_distance = autocvar_g_buffs_swapper_range;
entity closest = NULL;
sound(closest, CH_TRIGGER, SND_KA_RESPAWN, VOL_BASE, ATTEN_NORM);
// TODO: add a counter to handle how many times one can teleport, and a delay to prevent spam
- STAT(BUFFS, player) = 0;
+ buff_RemoveAll(player, STATUSEFFECT_REMOVE_NORMAL);
return true;
}
}
// if you have the invisibility powerup, sprites ALWAYS are restricted to your team
// but only apply this to real players, not to spectators
- if((wp.owner.flags & FL_CLIENT) && (STAT(BUFFS, wp.owner) & BUFF_INVISIBLE.m_itemid) && (e == player))
+ if((wp.owner.flags & FL_CLIENT) && (e == player) && StatusEffects_active(BUFF_INVISIBLE, wp.owner))
if(DIFF_TEAM(wp.owner, e))
return true;
}
{
entity player = M_ARGV(1, entity);
- if(STAT(BUFFS, player) & BUFF_SPEED.m_itemid)
+ if(StatusEffects_active(BUFF_SPEED, player))
M_ARGV(0, float) *= autocvar_g_buffs_speed_rate;
if(time < player.buff_disability_time)
{
entity player = M_ARGV(1, entity);
- if(STAT(BUFFS, player) & BUFF_SPEED.m_itemid)
+ if(StatusEffects_active(BUFF_SPEED, player))
M_ARGV(0, float) *= autocvar_g_buffs_speed_weaponspeed;
if(time < player.buff_disability_time)
{
entity player = M_ARGV(0, entity);
- if(game_stopped || IS_DEAD(player) || frametime || !IS_PLAYER(player)) return;
+ if(game_stopped || IS_DEAD(player) || !IS_PLAYER(player)) return;
- if(STAT(BUFFS, player) & BUFF_FLIGHT.m_itemid)
+ if(StatusEffects_active(BUFF_FLIGHT, player))
{
if(!PHYS_INPUT_BUTTON_CROUCH(player))
player.buff_flight_crouchheld = false;
// 2: notify carrier as well
int buff_lost = 0;
- if(STAT(BUFF_TIME, player) && STAT(BUFFS, player))
- if(time >= STAT(BUFF_TIME, player))
- {
- STAT(BUFF_TIME, player) = 0;
+ entity heldbuff = buff_FirstFromFlags(player);
+ float bufftime = StatusEffects_gettime(heldbuff, player);
+ if(heldbuff && bufftime && time >= bufftime)
buff_lost = 2;
- }
if(STAT(FROZEN, player)) { buff_lost = 1; }
- if(buff_lost)
+ if(buff_lost && heldbuff)
{
- if(STAT(BUFFS, player))
+ int buffid = heldbuff.m_id;
+ if(buff_lost == 2)
{
- int buffid = buff_FirstFromFlags(STAT(BUFFS, player)).m_id;
- if(buff_lost == 2)
- {
- Send_Notification(NOTIF_ONE, player, MSG_MULTI, ITEM_BUFF_DROP, buffid); // TODO: special timeout message?
- sound(player, CH_TRIGGER, SND_BUFF_LOST, VOL_BASE, ATTN_NORM);
- }
- else if(!IS_INDEPENDENT_PLAYER(player))
- Send_Notification(NOTIF_ALL_EXCEPT, player, MSG_INFO, INFO_ITEM_BUFF_LOST, player.netname, buffid);
- STAT(BUFFS, player) = 0;
- PS(player).buff_shield = time + max(0, autocvar_g_buffs_pickup_delay); // always put in a delay, even if small
+ Send_Notification(NOTIF_ONE, player, MSG_MULTI, ITEM_BUFF_DROP, buffid); // TODO: special timeout message?
+ sound(player, CH_TRIGGER, SND_BUFF_LOST, VOL_BASE, ATTN_NORM);
}
+ else if(!IS_INDEPENDENT_PLAYER(player))
+ Send_Notification(NOTIF_ALL_EXCEPT, player, MSG_INFO, INFO_ITEM_BUFF_LOST, player.netname, buffid);
+ buff_RemoveAll(player, STATUSEFFECT_REMOVE_TIMEOUT); // TODO: remove only the currently active buff?
+ heldbuff = NULL;
+ PS(player).buff_shield = time + max(0, autocvar_g_buffs_pickup_delay); // always put in a delay, even if small
}
- if(STAT(BUFFS, player) & BUFF_MAGNET.m_itemid)
+ if(StatusEffects_active(BUFF_MAGNET, player))
{
vector pickup_size;
IL_EACH(g_items, it.itemdef,
{
- if(STAT(BUFFS, it))
+ if(it.buffdef)
pickup_size = '1 1 1' * autocvar_g_buffs_magnet_range_buff;
else
pickup_size = '1 1 1' * autocvar_g_buffs_magnet_range_item;
});
}
- if(STAT(BUFFS, player) & BUFF_AMMO.m_itemid)
+ if(StatusEffects_active(BUFF_AMMO, player))
{
for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
{
}
}
- if((STAT(BUFFS, player) & BUFF_INVISIBLE.m_itemid) && (player.oldbuffs & BUFF_INVISIBLE.m_itemid))
+ if(!player.vehicle && StatusEffects_active(BUFF_INVISIBLE, player) && player.oldbuffs == BUFF_INVISIBLE)
player.alpha = ((autocvar_g_buffs_invisible_alpha) ? autocvar_g_buffs_invisible_alpha : -1); // powerups reset alpha, so we must enforce this (TODO)
- #define BUFF_ONADD(b) if ( (STAT(BUFFS, player) & (b).m_itemid) && !(player.oldbuffs & (b).m_itemid))
- #define BUFF_ONREM(b) if (!(STAT(BUFFS, player) & (b).m_itemid) && (player.oldbuffs & (b).m_itemid))
+ #define BUFF_ONADD(b) if ( (heldbuff == (b)) && (player.oldbuffs != (b)))
+ #define BUFF_ONREM(b) if ( (heldbuff != (b)) && (player.oldbuffs == (b)))
- if(STAT(BUFFS, player) != player.oldbuffs)
+ if(heldbuff != player.oldbuffs)
{
- entity buff = buff_FirstFromFlags(STAT(BUFFS, player));
- float bufftime = buff != BUFF_Null ? buff.m_time(buff) : 0;
- if(STAT(BUFF_TIME, player) <= time) // if the player still has a buff countdown, don't reset it!
- STAT(BUFF_TIME, player) = (bufftime) ? time + bufftime : 0;
+ bufftime = heldbuff ? heldbuff.m_time(heldbuff) : 0;
+ if(StatusEffects_gettime(heldbuff, player) <= time) // if the player still has a buff countdown, don't reset it!
+ {
+ player.statuseffects.statuseffect_time[heldbuff.m_id] = (bufftime) ? time + bufftime : 0;
+ StatusEffects_update(player);
+ }
BUFF_ONADD(BUFF_AMMO)
{
player.buff_ammo_prev_infitems = (player.items & IT_UNLIMITED_AMMO);
player.items |= IT_UNLIMITED_AMMO;
- if(STAT(BUFFS, player) & BUFF_AMMO.m_itemid)
+ if(StatusEffects_active(BUFF_AMMO, player))
{
for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
{
else
player.items &= ~IT_UNLIMITED_AMMO;
- if(STAT(BUFFS, player) & BUFF_AMMO.m_itemid)
+ if(StatusEffects_active(BUFF_AMMO, player))
{
for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
{
BUFF_ONADD(BUFF_INVISIBLE)
{
- if(time < STAT(STRENGTH_FINISHED, player) && MUTATOR_IS_ENABLED(mutator_instagib))
+ if(StatusEffects_active(STATUSEFFECT_Strength, player) && MUTATOR_IS_ENABLED(mutator_instagib))
player.buff_invisible_prev_alpha = default_player_alpha; // we don't want to save the powerup's alpha, as player may lose the powerup while holding the buff
else
player.buff_invisible_prev_alpha = player.alpha;
- player.alpha = autocvar_g_buffs_invisible_alpha;
+ if(!player.vehicle)
+ player.alpha = autocvar_g_buffs_invisible_alpha;
}
BUFF_ONREM(BUFF_INVISIBLE)
{
- if(time < STAT(STRENGTH_FINISHED, player) && MUTATOR_IS_ENABLED(mutator_instagib))
- player.alpha = autocvar_g_instagib_invis_alpha;
- else
- player.alpha = player.buff_invisible_prev_alpha;
+ if(!player.vehicle)
+ {
+ if(StatusEffects_active(STATUSEFFECT_Strength, player) && MUTATOR_IS_ENABLED(mutator_instagib))
+ player.alpha = autocvar_g_instagib_invis_alpha;
+ else
+ player.alpha = player.buff_invisible_prev_alpha;
+ }
}
BUFF_ONADD(BUFF_FLIGHT)
BUFF_ONREM(BUFF_FLIGHT)
player.gravity = ((player.trigger_gravity_check) ? player.trigger_gravity_check.enemy.gravity : player.buff_flight_oldgravity);
- player.oldbuffs = STAT(BUFFS, player);
- if(STAT(BUFFS, player))
+ player.oldbuffs = heldbuff;
+ if(heldbuff)
{
if(!player.buff_model)
buffs_BuffModel_Spawn(player);
- player.buff_model.color = buff.m_color;
- player.buff_model.glowmod = buff_GlowColor(player.buff_model);
- player.buff_model.skin = buff.m_skin;
+ player.buff_model.color = heldbuff.m_color;
+ player.buff_model.glowmod = buff_GlowColor(heldbuff);
+ player.buff_model.skin = heldbuff.m_skin;
player.effects |= EF_NOSHADOW;
}
#undef BUFF_ONREM
}
- MUTATOR_HOOKFUNCTION(buffs, SpectateCopy)
- {
- entity spectatee = M_ARGV(0, entity);
- entity client = M_ARGV(1, entity);
-
- STAT(BUFFS, client) = STAT(BUFFS, spectatee);
- STAT(BUFF_TIME, client) = STAT(BUFF_TIME, spectatee);
- }
-
MUTATOR_HOOKFUNCTION(buffs, PlayerRegen)
{
entity player = M_ARGV(0, entity);
- if(STAT(BUFFS, player) & BUFF_MEDIC.m_itemid)
+ if(StatusEffects_active(BUFF_MEDIC, player))
{
M_ARGV(2, float) = autocvar_g_buffs_medic_rot; // rot_mod
M_ARGV(4, float) = M_ARGV(1, float) = autocvar_g_buffs_medic_max; // limit_mod = max_mod
M_ARGV(2, float) = autocvar_g_buffs_medic_regen; // regen_mod
}
- if(STAT(BUFFS, player) & BUFF_SPEED.m_itemid)
+ if(StatusEffects_active(BUFF_SPEED, player))
M_ARGV(2, float) = autocvar_g_buffs_speed_regen; // regen_mod
}
.float buff_effect_delay;
// buff definitions
-.float buff_active;
+.bool buff_active;
.float buff_activetime;
-.float buff_activetime_updated;
+.bool buff_activetime_updated;
.entity buff_waypoint;
- .int oldbuffs; // for updating effects
+ .entity oldbuffs; // for updating effects
.float buff_shield; // delay for players to keep them from spamming buff pickups
.entity buff_model; // controls effects (TODO: make csqc)
.float cvar_cl_buffs_autoreplace;
float buff_Available(entity buff);
+
+ void buff_RemoveAll(entity actor, int removal_type);
+
+ entity buff_FirstFromFlags(entity actor);
#ifdef SVQC
#include <server/client.qh>
+#include <server/compat/quake3.qh>
#include <server/main.qh>
#include <common/gamemodes/sv_rules.qh>
#include <common/mapobjects/teleporters.qh>
REGISTER_STAT(WEAPONRATEFACTOR, float, W_WeaponRateFactor(this))
REGISTER_STAT(GAME_STOPPED, int, game_stopped)
REGISTER_STAT(GAMESTARTTIME, float, game_starttime)
- REGISTER_STAT(STRENGTH_FINISHED, float)
- REGISTER_STAT(INVINCIBLE_FINISHED, float)
/** arc heat in [0,1] */
REGISTER_STAT(PRESSED_KEYS, int)
/** this stat could later contain some other bits of info, like, more server-side particle config */
REGISTER_STAT(HIT_TIME, float)
REGISTER_STAT(DAMAGE_DEALT_TOTAL, int)
REGISTER_STAT(TYPEHIT_TIME, float)
- REGISTER_STAT(SUPERWEAPONS_FINISHED, float)
REGISTER_STAT(AIR_FINISHED, float)
REGISTER_STAT(VEHICLESTAT_HEALTH, int)
REGISTER_STAT(VEHICLESTAT_SHIELD, int)
REGISTER_STAT(ROUNDSTARTTIME, float, round_starttime)
REGISTER_STAT(MONSTERS_TOTAL, int)
REGISTER_STAT(MONSTERS_KILLED, int)
- REGISTER_STAT(BUFFS, int)
REGISTER_STAT(NADE_BONUS, float)
REGISTER_STAT(NADE_BONUS_TYPE, int)
REGISTER_STAT(NADE_BONUS_SCORE, float)
REGISTER_STAT(FROZEN, int)
REGISTER_STAT(REVIVE_PROGRESS, float)
REGISTER_STAT(ROUNDLOST, int)
- REGISTER_STAT(BUFF_TIME, float)
REGISTER_STAT(CTF_FLAGSTATUS, int)
REGISTER_STAT(CAPTURE_PROGRESS, float)
REGISTER_STAT(ENTRAP_ORB, float)
#endif
REGISTER_STAT(SLICK_APPLYGRAVITY, bool, autocvar_sv_slick_applygravity)
-#ifdef SVQC
-bool autocvar_sv_q3defragcompat;
-#endif
-REGISTER_STAT(Q3DEFRAGCOMPAT, bool, autocvar_sv_q3defragcompat)
+REGISTER_STAT(Q3COMPAT, int, q3compat)
#ifdef SVQC
#include "physics/movetypes/movetypes.qh"
if (a >= 24)
{
a -= 24;
- return '0 0 1' * (2 ** a);
+ return '0 0 1' * BIT(a);
}
- return '0 1 0' * (2 ** a);
+ return '0 1 0' * BIT(a);
}
- return '1 0 0' * (2 ** a);
+ return '1 0 0' * BIT(a);
}
#ifdef SVQC
void WriteWepSet(float dst, WepSet w)
}
}
+entity GetAmmoItem(int ammotype)
+{
+ switch (ammotype)
+ {
+ case RES_SHELLS: return ITEM_Shells;
+ case RES_BULLETS: return ITEM_Bullets;
+ case RES_ROCKETS: return ITEM_Rockets;
+ case RES_CELLS: return ITEM_Cells;
+ case RES_PLASMA: return ITEM_Plasma;
+ case RES_FUEL: return ITEM_JetpackFuel;
+ }
+ LOG_WARNF("Invalid ammo type %d ", ammotype);
+ return NULL;
+ // WEAPONTODO: use this generic func to reduce duplication ?
+ // GetAmmoPicture GetAmmoName notif_arg_item_wepammo ammo_pickupevalfunc ?
+}
+
#ifdef CSQC
int GetAmmoTypeFromNum(int i)
{
noref bool require_spawnfunc_prefix;
.bool spawnfunc_checked;
/** Not for production use, provides access to a dump of the entity's fields when it is parsed from map data */
-//noref string __fullspawndata;
+noref string __fullspawndata;
+.string fullspawndata;
// Optional type checking; increases compile time too much to be enabled by default
#if 0
FIELD_SCALAR(fld, noise2) \
FIELD_SCALAR(fld, noise3) \
FIELD_SCALAR(fld, noise) \
+ FIELD_SCALAR(fld, notcpm) \
+ FIELD_SCALAR(fld, notfree) \
+ FIELD_SCALAR(fld, notta) \
+ FIELD_SCALAR(fld, notteam) \
+ FIELD_SCALAR(fld, notvq3) \
FIELD_SCALAR(fld, phase) \
FIELD_SCALAR(fld, platmovetype) \
FIELD_SCALAR(fld, race_place) \
}
}
+ // this function simply avoids expanding IL_NEW during compilation
+ // for each spawning entity
+ void g_spawn_queue_spawn() { g_spawn_queue = IL_NEW(); }
+
noref bool __spawnfunc_first;
#define spawnfunc(id) \
if (__spawnfunc_expecting > 1) { __spawnfunc_expecting = 0; } \
else if (__spawnfunc_expecting) { \
/* engine call */ \
- if (!g_spawn_queue) { g_spawn_queue = IL_NEW(); } \
+ if (!g_spawn_queue) g_spawn_queue_spawn(); \
__spawnfunc_expecting = 0; \
this = __spawnfunc_expect; \
__spawnfunc_expect = NULL; \
this.classname = #id; \
if (!this.spawnfunc_checked) { \
_checkWhitelisted(this, #id); \
+ if (__fullspawndata) { \
+ /* not supported in old DP */ \
+ /* must be read inside the real spawnfunc */ \
+ this.fullspawndata = __fullspawndata; \
+ } \
this.spawnfunc_checked = true; \
if (this) { \
/* not worldspawn, delay spawn */ \
return nearest;
}
+ // blacklist of entities that WarpZone_FindRadius doesn't care about
bool WarpZoneLib_BadEntity(entity e)
{
if (is_pure(e)) return true;
string s = e.classname;
- //if (s == "net_linked") return true; // actually some real entities are linked without classname, fail
- if (s == "") return true;
+ switch(s)
+ {
+ case "weaponentity":
+ case "exteriorweaponentity":
+ case "sprite_waypoint":
+ case "spawnfunc":
+ case "weaponchild":
+ case "chatbubbleentity":
+ //case "net_linked": // actually some real entities are linked without classname, fail
+ case "":
+ return true;
+ }
if (startsWith(s, "target_")) return true;
bool WarpZoneLib_ExactTrigger_Touch(entity this, entity toucher)
{
vector emin = toucher.absmin, emax = toucher.absmax;
- // the engine offsets absolute bounding boxes by a single quake unit
- // we must undo that here to allow accurate touching
- emin += '1 1 1';
- emax -= '1 1 1';
+ if(STAT(Q3COMPAT))
+ {
+ // DP's tracebox enlarges absolute bounding boxes by a single quake unit
+ // we must undo that here to allow accurate touching
+ emin += '1 1 1';
+ emax -= '1 1 1';
+ }
return !WarpZoneLib_BoxTouchesBrush(emin, emax, this, toucher);
}
#include <common/mutators/mutator/instagib/sv_instagib.qh>
#include <common/mutators/mutator/nades/nades.qh>
#include <common/mutators/mutator/overkill/oknex.qh>
+ #include <common/mutators/mutator/status_effects/_mod.qh>
#include <common/mutators/mutator/waypoints/all.qh>
#include <common/net_linked.qh>
#include <common/net_notice.qh>
this.scale = 0;
this.fade_time = 0;
this.pain_finished = 0;
- STAT(STRENGTH_FINISHED, this) = 0;
- STAT(INVINCIBLE_FINISHED, this) = 0;
- STAT(SUPERWEAPONS_FINISHED, this) = 0;
STAT(AIR_FINISHED, this) = 0;
//this.dphitcontentsmask = 0;
this.dphitcontentsmask = DPCONTENTS_SOLID;
this.punchangle = '0 0 0';
this.punchvector = '0 0 0';
this.oldvelocity = this.velocity;
- this.fire_endtime = -1;
this.event_damage = func_null;
this.event_heal = func_null;
SetPlayerTeam(this, -1, TEAM_CHANGE_SPECTATOR);
this.frags = FRAGS_SPECTATOR;
}
+
+ bot_relinkplayerlist();
+
if (CS(this).just_joined)
CS(this).just_joined = false;
}
PS(this).dual_weapons = '0 0 0';
- STAT(SUPERWEAPONS_FINISHED, this) = (STAT(WEAPONS, this) & WEPSET_SUPERWEAPONS) ? time + autocvar_g_balance_superweapons_time : 0;
-
this.items = start_items;
this.spawnshieldtime = time + autocvar_g_spawnshieldtime;
this.respawn_flags = 0;
this.respawn_time = 0;
STAT(RESPAWN_TIME, this) = 0;
- bool q3dfcompat = autocvar_sv_q3defragcompat && autocvar_sv_q3defragcompat_changehitbox;
- this.scale = ((q3dfcompat) ? 0.9 : autocvar_sv_player_scale);
+ this.scale = ((q3compat && autocvar_sv_q3compat_changehitbox) ? 0.9 : autocvar_sv_player_scale);
this.fade_time = 0;
this.pain_finished = 0;
this.pushltime = 0;
this.punchangle = '0 0 0';
this.punchvector = '0 0 0';
- STAT(STRENGTH_FINISHED, this) = 0;
- STAT(INVINCIBLE_FINISHED, this) = 0;
- this.fire_endtime = -1;
STAT(REVIVE_PROGRESS, this) = 0;
this.revival_time = 0;
- // TODO: we can't set these in the PlayerSpawn hook since the target code is called before it!
- STAT(BUFFS, this) = 0;
- STAT(BUFF_TIME, this) = 0;
-
STAT(AIR_FINISHED, this) = 0;
this.waterlevel = WATERLEVEL_NONE;
this.watertype = CONTENT_EMPTY;
}
});
+ Unfreeze(this, false);
+
+ MUTATOR_CALLHOOK(PlayerSpawn, this, spot);
+
{
string s = spot.target;
if(g_assault || g_race) // TODO: make targeting work in assault & race without this hack
spot.target = s;
}
- Unfreeze(this, false);
-
- MUTATOR_CALLHOOK(PlayerSpawn, this, spot);
-
if (autocvar_spawn_debug)
{
sprint(this, strcat("spawnpoint origin: ", vtos(spot.origin), "\n"));
for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
{
.entity weaponentity = weaponentities[slot];
+ entity w_ent = this.(weaponentity);
if(slot == 0 || autocvar_g_weaponswitch_debug == 1)
- this.(weaponentity).m_switchweapon = w_getbestweapon(this, weaponentity);
+ w_ent.m_switchweapon = w_getbestweapon(this, weaponentity);
else
- this.(weaponentity).m_switchweapon = WEP_Null;
- this.(weaponentity).m_weapon = WEP_Null;
- this.(weaponentity).weaponname = "";
- this.(weaponentity).m_switchingweapon = WEP_Null;
- this.(weaponentity).cnt = -1;
+ w_ent.m_switchweapon = WEP_Null;
+ w_ent.m_weapon = WEP_Null;
+ w_ent.weaponname = "";
+ w_ent.m_switchingweapon = WEP_Null;
+ w_ent.cnt = -1;
}
MUTATOR_CALLHOOK(PlayerWeaponSelect, this);
} else if (IS_PLAYER(this)) {
PutPlayerInServer(this);
}
+
+ bot_relinkplayerlist();
}
// TODO do we need all these fields, or should we stop autodetecting runtime
else
this.modelflags &= ~MF_ROCKET;
- this.effects &= ~(EF_RED | EF_BLUE | EF_ADDITIVE | EF_FULLBRIGHT | EF_FLAME | EF_NODEPTHTEST);
+ this.effects &= ~(EF_RED | EF_BLUE | EF_ADDITIVE | EF_FULLBRIGHT | EF_NODEPTHTEST);
if (IS_DEAD(this))
player_powerups_remove_all(this);
// add a way to see what the items were BEFORE all of these checks for the mutator hook
int items_prev = this.items;
- Fire_ApplyDamage(this);
- Fire_ApplyEffect(this);
-
if (!MUTATOR_IS_ENABLED(mutator_instagib))
{
if (this.items & ITEM_Strength.m_itemid)
{
- play_countdown(this, STAT(STRENGTH_FINISHED, this), SND_POWEROFF);
+ play_countdown(this, StatusEffects_gettime(STATUSEFFECT_Strength, this), SND_POWEROFF);
this.effects = this.effects | (EF_BLUE | EF_ADDITIVE | EF_FULLBRIGHT);
- if (time > STAT(STRENGTH_FINISHED, this))
+ if (time > StatusEffects_gettime(STATUSEFFECT_Strength, this))
{
this.items = this.items - (this.items & ITEM_Strength.m_itemid);
//Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_POWERDOWN_STRENGTH, this.netname);
}
else
{
- if (time < STAT(STRENGTH_FINISHED, this))
+ if (time < StatusEffects_gettime(STATUSEFFECT_Strength, this))
{
this.items = this.items | ITEM_Strength.m_itemid;
if(!g_cts)
}
if (this.items & ITEM_Shield.m_itemid)
{
- play_countdown(this, STAT(INVINCIBLE_FINISHED, this), SND_POWEROFF);
+ play_countdown(this, StatusEffects_gettime(STATUSEFFECT_Shield, this), SND_POWEROFF);
this.effects = this.effects | (EF_RED | EF_ADDITIVE | EF_FULLBRIGHT);
- if (time > STAT(INVINCIBLE_FINISHED, this))
+ if (time > StatusEffects_gettime(STATUSEFFECT_Shield, this))
{
this.items = this.items - (this.items & ITEM_Shield.m_itemid);
//Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_POWERDOWN_SHIELD, this.netname);
}
else
{
- if (time < STAT(INVINCIBLE_FINISHED, this))
+ if (time < StatusEffects_gettime(STATUSEFFECT_Shield, this))
{
this.items = this.items | ITEM_Shield.m_itemid;
if(!g_cts)
{
if (!(STAT(WEAPONS, this) & WEPSET_SUPERWEAPONS))
{
- STAT(SUPERWEAPONS_FINISHED, this) = 0;
+ StatusEffects_remove(STATUSEFFECT_Superweapons, this, STATUSEFFECT_REMOVE_NORMAL);
this.items = this.items - (this.items & IT_SUPERWEAPON);
//Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SUPERWEAPON_LOST, this.netname);
Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_SUPERWEAPON_LOST);
}
else
{
- play_countdown(this, STAT(SUPERWEAPONS_FINISHED, this), SND_POWEROFF);
- if (time > STAT(SUPERWEAPONS_FINISHED, this))
+ play_countdown(this, StatusEffects_gettime(STATUSEFFECT_Superweapons, this), SND_POWEROFF);
+ if (time > StatusEffects_gettime(STATUSEFFECT_Superweapons, this))
{
this.items = this.items - (this.items & IT_SUPERWEAPON);
STAT(WEAPONS, this) &= ~WEPSET_SUPERWEAPONS;
}
else if(STAT(WEAPONS, this) & WEPSET_SUPERWEAPONS)
{
- if (time < STAT(SUPERWEAPONS_FINISHED, this) || (this.items & IT_UNLIMITED_SUPERWEAPONS))
+ if (time < StatusEffects_gettime(STATUSEFFECT_Superweapons, this) || (this.items & IT_UNLIMITED_SUPERWEAPONS))
{
this.items = this.items | IT_SUPERWEAPON;
if(!(this.items & IT_UNLIMITED_SUPERWEAPONS))
}
else
{
- STAT(SUPERWEAPONS_FINISHED, this) = 0;
+ if(StatusEffects_active(STATUSEFFECT_Superweapons, this))
+ StatusEffects_remove(STATUSEFFECT_Superweapons, this, STATUSEFFECT_REMOVE_TIMEOUT);
STAT(WEAPONS, this) &= ~WEPSET_SUPERWEAPONS;
}
}
- else
+ else if(StatusEffects_active(STATUSEFFECT_Superweapons, this)) // cheaper to check than to update each frame!
{
- STAT(SUPERWEAPONS_FINISHED, this) = 0;
+ StatusEffects_remove(STATUSEFFECT_Superweapons, this, STATUSEFFECT_REMOVE_CLEAR);
}
}
this.items = spectatee.items;
STAT(LAST_PICKUP, this) = STAT(LAST_PICKUP, spectatee);
STAT(HIT_TIME, this) = STAT(HIT_TIME, spectatee);
- STAT(STRENGTH_FINISHED, this) = STAT(STRENGTH_FINISHED, spectatee);
- STAT(INVINCIBLE_FINISHED, this) = STAT(INVINCIBLE_FINISHED, spectatee);
- STAT(SUPERWEAPONS_FINISHED, this) = STAT(SUPERWEAPONS_FINISHED, spectatee);
STAT(AIR_FINISHED, this) = STAT(AIR_FINISHED, spectatee);
STAT(PRESSED_KEYS, this) = STAT(PRESSED_KEYS, spectatee);
STAT(WEAPONS, this) = STAT(WEAPONS, spectatee);
return free_slots;
}
- /**
- * Checks whether the client is an observer or spectator, if so, he will get kicked after
- * g_maxplayers_spectator_blocktime seconds
- */
- void checkSpectatorBlock(entity this)
- {
- if(IS_SPEC(this) || IS_OBSERVER(this))
- if(!this.caplayer)
- if(IS_REAL_CLIENT(this))
- {
- if( time > (CS(this).spectatortime + autocvar_g_maxplayers_spectator_blocktime) ) {
- Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_QUIT_KICK_SPECTATING);
- dropclient(this);
- }
- }
- }
-
void PrintWelcomeMessage(entity this)
{
if(CS(this).motd_actived_time == 0)
TRANSMUTE(Observer, this);
PutClientInServer(this);
}
+ else
+ this.would_spectate = false; // unable to spectate anyone
if (is_spec)
CS(this).impulse = 0;
} else if (is_spec) {
}
}
else {
- int preferred_movetype = ((!PHYS_INPUT_BUTTON_USE(this) ? CS_CVAR(this).cvar_cl_clippedspectating : !CS_CVAR(this).cvar_cl_clippedspectating) ? MOVETYPE_FLY_WORLDONLY : MOVETYPE_NOCLIP);
+ bool wouldclip = CS_CVAR(this).cvar_cl_clippedspectating;
+ if (PHYS_INPUT_BUTTON_USE(this))
+ wouldclip = !wouldclip;
+ int preferred_movetype = (wouldclip ? MOVETYPE_FLY_WORLDONLY : MOVETYPE_NOCLIP);
set_movetype(this, preferred_movetype);
}
} else { // jump pressed
if (frametime) {
// physics frames: update anticheat stuff
anticheat_prethink(this);
- }
- if (blockSpectators && frametime) {
// WORKAROUND: only use dropclient in server frames (frametime set).
// Never use it in cl_movement frames (frametime zero).
- checkSpectatorBlock(this);
+ if (blockSpectators && IS_REAL_CLIENT(this)
+ && (IS_SPEC(this) || IS_OBSERVER(this)) && !this.caplayer
+ && time > (CS(this).spectatortime + autocvar_g_maxplayers_spectator_blocktime))
+ {
+ if (dropclient_schedule(this))
+ Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_QUIT_KICK_SPECTATING);
+ }
}
zoomstate_set = false;
{
Player_Physics(this);
- if (autocvar_sv_maxidle > 0)
+ if (autocvar_sv_maxidle > 0 || (IS_PLAYER(this) && autocvar_sv_maxidle_playertospectator > 0))
if (frametime) // WORKAROUND: only use dropclient in server frames (frametime set). Never use it in cl_movement frames (frametime zero).
if (IS_REAL_CLIENT(this))
- if (IS_PLAYER(this) || autocvar_sv_maxidle_spectatorsareidle)
+ if (IS_PLAYER(this) || autocvar_sv_maxidle_alsokickspectators)
+ if (!intermission_running) // NextLevel() kills all centerprints after setting this true
{
int totalClients = 0;
- if(autocvar_sv_maxidle_slots > 0)
+ if(autocvar_sv_maxidle > 0 && autocvar_sv_maxidle_slots > 0)
{
FOREACH_CLIENT(IS_REAL_CLIENT(it) || autocvar_sv_maxidle_slots_countbots,
{
});
}
- if (autocvar_sv_maxidle_slots > 0 && (maxclients - totalClients) > autocvar_sv_maxidle_slots)
+ if (autocvar_sv_maxidle > 0 && autocvar_sv_maxidle_slots > 0 && (maxclients - totalClients) > autocvar_sv_maxidle_slots)
{ /* do nothing */ }
else if (time - CS(this).parm_idlesince < 1) // instead of (time == this.parm_idlesince) to support sv_maxidle <= 10
{
}
else
{
- float timeleft = ceil(autocvar_sv_maxidle - (time - CS(this).parm_idlesince));
- if (timeleft == min(10, autocvar_sv_maxidle - 1)) { // - 1 to support sv_maxidle <= 10
- if (!CS(this).idlekick_lasttimeleft)
+ float maxidle_time = autocvar_sv_maxidle;
+ if (IS_PLAYER(this) && autocvar_sv_maxidle_playertospectator > 0)
+ maxidle_time = autocvar_sv_maxidle_playertospectator;
+ float timeleft = ceil(maxidle_time - (time - CS(this).parm_idlesince));
+ float countdown_time = max(min(10, maxidle_time - 1), ceil(maxidle_time * 0.33)); // - 1 to support maxidle_time <= 10
+ if (timeleft == countdown_time && !CS(this).idlekick_lasttimeleft)
+ {
+ if (IS_PLAYER(this) && autocvar_sv_maxidle_playertospectator > 0)
+ Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_MOVETOSPEC_IDLING, timeleft);
+ else
Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_DISCONNECT_IDLING, timeleft);
}
if (timeleft <= 0) {
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_QUIT_KICK_IDLING, this.netname);
- dropclient(this);
+ if (IS_PLAYER(this) && autocvar_sv_maxidle_playertospectator > 0)
+ {
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_MOVETOSPEC_IDLING, this.netname, maxidle_time);
+ if (this.caplayer)
+ this.caplayer = 0;
+ PutObserverInServer(this);
+ }
+ else
+ {
+ if (dropclient_schedule(this))
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_QUIT_KICK_IDLING, this.netname, maxidle_time);
+ }
return;
}
- else if (timeleft <= 10) {
+ else if (timeleft <= countdown_time) {
if (timeleft != CS(this).idlekick_lasttimeleft)
- Send_Notification(NOTIF_ONE, this, MSG_ANNCE, Announcer_PickNumber(CNT_IDLE, timeleft));
+ play2(this, SND(TALK2));
CS(this).idlekick_lasttimeleft = timeleft;
}
}
#include <common/mapobjects/trigger/counter.qh>
#include <common/mapobjects/triggers.qh>
#include <common/mutators/mutator/buffs/buffs.qh>
+ #include <common/mutators/mutator/buffs/sv_buffs.qh>
+ #include <common/mutators/mutator/status_effects/_mod.qh>
#include <common/notifications/all.qh>
#include <common/stats.qh>
#include <common/weapons/_all.qh>
#include <server/resources.qh>
#include <server/world.qh>
-//***********************
-//QUAKE 3 ENTITIES - So people can play quake3 maps with the xonotic weapons
-//***********************
+/***********************
+ * QUAKE 3 ENTITIES - So people can play quake3 maps with the xonotic weapons
+ ***********************
+
+ * Map entities NOT handled in this file:
+ holdable_invulnerability Q3TA buffs mutator
+ holdable_kamikaze Q3TA buffs mutator
+ holdable_teleporter Q3A buffs mutator
+ item_ammoregen Q3TA buffs mutator
+ item_doubler Q3TA buffs mutator
+ item_guard Q3TA buffs mutator
+ item_scout Q3TA buffs mutator
+ item_armor_jacket CPMA quake2.qc
+ item_flight Q3A buffs mutator
+ item_haste Q3A buffs mutator
+ item_health Q3A quake.qc
+ item_health_large Q3A items.qc
+ item_health_small Q3A health.qh
+ item_health_mega Q3A health.qh
+ item_invis Q3A buffs mutator
+ item_quad Q3A items.qc
+ item_regen Q3A buffs mutator
+ weapon_machinegun Q3A machinegun.qh
+ weapon_grenadelauncher Q3A mortar.qh
+ weapon_rocketlauncher Q3A devastator.qh
+ CTF spawnfuncs handled in sv_ctf.qc
+
+ NOTE: for best experience, you need to swap MGs with SGs in the map or it won't have a MG
+*/
+
+// SG -> MG || SG
+SPAWNFUNC_Q3_COND(weapon_shotgun, ammo_shells, (q3compat & Q3COMPAT_ARENA), WEP_MACHINEGUN, WEP_SHOTGUN)
+
+// MG -> SG || MG
+// Technically we should replace weapon_machinegun with WEP_SHOTGUN if Q3COMPAT_ARENA, but it almost never occurs on Q3 maps
+SPAWNFUNC_Q3AMMO_COND(ammo_bullets, (q3compat & Q3COMPAT_ARENA), WEP_SHOTGUN, WEP_MACHINEGUN)
-// NOTE: for best experience, you need to swap MGs with SGs in the map or it won't have a MG
+// GL -> Mortar
+SPAWNFUNC_Q3AMMO(ammo_grenades, WEP_MORTAR)
-// SG -> SG
-SPAWNFUNC_ITEM(ammo_shells, ITEM_Shells)
+// Team Arena Proximity Launcher -> Mortar
+// It's more accurate to spawn Mine Layer but players prefer Mortar, and weapon_grenadelauncher is usually disabled by "notta" and weapon_prox_launcher placed at the same origin
+SPAWNFUNC_Q3(weapon_prox_launcher, ammo_mines, WEP_MORTAR)
-// MG -> MG
-SPAWNFUNC_ITEM(ammo_bullets, ITEM_Bullets)
+// Team Arena Chaingun -> HLAC
+SPAWNFUNC_Q3(weapon_chaingun, ammo_belt, WEP_HLAC)
-// GL -> Mortar
-SPAWNFUNC_ITEM(ammo_grenades, ITEM_Rockets)
+// Quake Live Heavy Machine Gun -> HLAC
+SPAWNFUNC_Q3(weapon_hmg, ammo_hmg, WEP_HLAC)
-// Mines -> Rockets
-SPAWNFUNC_WEAPON(weapon_prox_launcher, WEP_MINE_LAYER)
-SPAWNFUNC_ITEM(ammo_mines, ITEM_Rockets)
+// Team Arena Nailgun -> Crylink || Quake Nailgun -> Electro
+SPAWNFUNC_Q3_COND(weapon_nailgun, ammo_nails, cvar("sv_mapformat_is_quake3"), WEP_CRYLINK, WEP_ELECTRO)
-// LG -> Lightning
-SPAWNFUNC_WEAPON(weapon_lightning, WEP_ELECTRO)
-SPAWNFUNC_ITEM(ammo_lightning, ITEM_Cells)
+// LG -> Electro
+SPAWNFUNC_Q3(weapon_lightning, ammo_lightning, WEP_ELECTRO)
// Plasma -> Hagar
-SPAWNFUNC_WEAPON(weapon_plasmagun, WEP_HAGAR)
-SPAWNFUNC_ITEM(ammo_cells, ITEM_Rockets)
+SPAWNFUNC_Q3(weapon_plasmagun, ammo_cells, WEP_HAGAR)
// Rail -> Vortex
-SPAWNFUNC_WEAPON(weapon_railgun, WEP_VORTEX)
-SPAWNFUNC_ITEM(ammo_slugs, ITEM_Cells)
+SPAWNFUNC_Q3(weapon_railgun, ammo_slugs, WEP_VORTEX)
-// BFG -> Crylink
-SPAWNFUNC_WEAPON(weapon_bfg, WEP_CRYLINK)
-SPAWNFUNC_ITEM(ammo_bfg, ITEM_Cells)
+// BFG -> Crylink || Fireball
+SPAWNFUNC_Q3_COND(weapon_bfg, ammo_bfg, cvar_string("g_mod_balance") == "XDF", WEP_CRYLINK, WEP_FIREBALL)
+ // FIXME: WEP_FIREBALL has no ammo_type field so ammo_bfg is deleted by SPAWNFUNC_BODY
// grappling hook -> hook
SPAWNFUNC_WEAPON(weapon_grapplinghook, WEP_HOOK)
// RL -> RL
-SPAWNFUNC_ITEM(ammo_rockets, ITEM_Rockets)
+SPAWNFUNC_Q3AMMO(ammo_rockets, WEP_DEVASTATOR)
+
+// Gauntlet -> Tuba
+SPAWNFUNC_ITEM(weapon_gauntlet, WEP_TUBA)
// Armor
SPAWNFUNC_ITEM(item_armor_body, ITEM_ArmorMega)
SPAWNFUNC_ITEM(item_armor_combat, ITEM_ArmorBig)
SPAWNFUNC_ITEM(item_armor_shard, ITEM_ArmorSmall)
+SPAWNFUNC_ITEM(item_armor_green, ITEM_ArmorMedium) // CCTF
+
+// Battle Suit
SPAWNFUNC_ITEM(item_enviro, ITEM_Shield)
// medkit -> armor (we have no holdables)
if (!(this.spawnflags & 8))
{
- STAT(STRENGTH_FINISHED, actor) = 0;
- STAT(INVINCIBLE_FINISHED, actor) = 0;
- if(STAT(BUFFS, actor)) // TODO: make a dropbuffs function to handle this
+ StatusEffects_remove(STATUSEFFECT_Strength, actor, STATUSEFFECT_REMOVE_NORMAL);
+ StatusEffects_remove(STATUSEFFECT_Shield, actor, STATUSEFFECT_REMOVE_NORMAL);
+ entity heldbuff = buff_FirstFromFlags(actor);
+ if(heldbuff) // TODO: make a dropbuffs function to handle this
{
- int buffid = buff_FirstFromFlags(STAT(BUFFS, actor)).m_id;
+ int buffid = heldbuff.m_id;
Send_Notification(NOTIF_ONE, actor, MSG_MULTI, ITEM_BUFF_DROP, buffid);
sound(actor, CH_TRIGGER, SND_BUFF_LOST, VOL_BASE, ATTN_NORM);
if(!IS_INDEPENDENT_PLAYER(actor))
Send_Notification(NOTIF_ALL_EXCEPT, actor, MSG_INFO, INFO_ITEM_BUFF_LOST, actor.netname, buffid);
- STAT(BUFFS, actor) = 0;
- STAT(BUFF_TIME, actor) = 0;
+ buff_RemoveAll(actor, STATUSEFFECT_REMOVE_NORMAL);
}
}
InitializeEntity(this, target_init_verify, INITPRIO_FINDTARGET);
}
-// weapon give ent from defrag
+// weapon give ent from Q3
void target_give_init(entity this)
{
IL_EACH(g_items, it.targetname == this.target,
{
- if (it.classname == "weapon_devastator") {
- SetResourceExplicit(this, RES_ROCKETS, GetResource(this, RES_ROCKETS) + it.count * WEP_CVAR_PRI(devastator, ammo)); // WEAPONTODO
- this.netname = cons(this.netname, "devastator");
- }
- else if (it.classname == "weapon_vortex") {
- SetResourceExplicit(this, RES_CELLS, GetResource(this, RES_CELLS) + it.count * WEP_CVAR_PRI(vortex, ammo)); // WEAPONTODO
- this.netname = cons(this.netname, "vortex");
- }
- else if (it.classname == "weapon_electro") {
- SetResourceExplicit(this, RES_CELLS, GetResource(this, RES_CELLS) + it.count * WEP_CVAR_PRI(electro, ammo)); // WEAPONTODO
- this.netname = cons(this.netname, "electro");
- }
- else if (it.classname == "weapon_hagar") {
- SetResourceExplicit(this, RES_ROCKETS, GetResource(this, RES_ROCKETS) + it.count * WEP_CVAR_PRI(hagar, ammo)); // WEAPONTODO
- this.netname = cons(this.netname, "hagar");
- }
- else if (it.classname == "weapon_crylink") {
- SetResourceExplicit(this, RES_CELLS, GetResource(this, RES_CELLS) + it.count * WEP_CVAR_PRI(crylink, ammo)); // WEAPONTODO
- this.netname = cons(this.netname, "crylink");
- }
- else if (it.classname == "weapon_mortar") {
- SetResourceExplicit(this, RES_ROCKETS, GetResource(this, RES_ROCKETS) + it.count * WEP_CVAR_PRI(mortar, ammo)); // WEAPONTODO
- this.netname = cons(this.netname, "mortar");
- }
- else if (it.classname == "weapon_shotgun") {
- SetResourceExplicit(this, RES_SHELLS, GetResource(this, RES_SHELLS) + it.count * WEP_CVAR_PRI(shotgun, ammo)); // WEAPONTODO
- this.netname = cons(this.netname, "shotgun");
- }
- else if (it.classname == "item_armor_mega")
- SetResourceExplicit(this, RES_ARMOR, 100);
- else if (it.classname == "item_health_mega")
- SetResourceExplicit(this, RES_HEALTH, 200);
- else if (it.classname == "item_buff") {
+ if (it.classname == "item_buff")
+ {
- entity buff = buff_FirstFromFlags(STAT(BUFFS, it));
+ entity buff = it.buffdef;
this.netname = cons(this.netname, buff.netname);
- STAT(BUFF_TIME, this) += it.count;
- this.buffs_finished = it.count;
++ this.buffs_finished += it.count;
+ }
+ else
+ {
+ if (it.ammo_rockets)
+ this.ammo_rockets += it.ammo_rockets;
+ else if (it.ammo_cells)
+ this.ammo_cells += it.ammo_cells;
+ else if (it.ammo_shells)
+ this.ammo_shells += it.ammo_shells;
+ else if (it.ammo_nails)
+ this.ammo_nails += it.ammo_nails;
+ else if (it.invincible_finished)
+ this.invincible_finished += it.invincible_finished;
+ else if (it.strength_finished)
+ this.strength_finished += it.strength_finished;
+ else if (it.health)
+ this.health += it.health;
+ else if (it.armorvalue)
+ this.armorvalue += it.armorvalue;
+
+ this.netname = cons(this.netname, it.netname);
}
//remove(it); // removing ents in init functions causes havoc, workaround:
- setthink(it, SUB_Remove);
- it.nextthink = time;
+ setthink(it, SUB_Remove);
+ it.nextthink = time;
});
this.spawnflags = 2;
this.spawnfunc_checked = true;
this.use = fragsfilter_use;
}
-//spawnfunc(item_flight) /* handled by buffs mutator */
-//spawnfunc(item_doubler) /* handled by buffs mutator */
-//spawnfunc(item_haste) /* handled by buffs mutator */
-//spawnfunc(item_health) /* handled in t_quake.qc */
-//spawnfunc(item_health_large) /* handled in items.qc */
-//spawnfunc(item_health_small) /* handled in items.qc */
-//spawnfunc(item_health_mega) /* handled in items.qc */
-//spawnfunc(item_invis) /* handled by buffs mutator */
-//spawnfunc(item_regen) /* handled by buffs mutator */
-
-// CTF spawnfuncs handled in mutators/gamemode_ctf.qc now
-
-.float notteam;
-.float notsingle;
-.float notfree;
-.float notq3a;
-.float notta;
+.bool notteam;
+.bool notsingle;
+.bool notfree;
+.bool notta;
+.bool notvq3;
+.bool notcpm;
.string gametype;
bool DoesQ3ARemoveThisEntity(entity this)
{
// Q3 style filters (DO NOT USE, THIS IS COMPAT ONLY)
- if(this.notq3a)
- if(!teamplay || g_tdm || g_ctf)
+ // DeFRaG mappers use "notcpm" or "notvq3" to disable an entity in CPM or VQ3 physics
+ // Xonotic is usually played with a CPM-based physics so we default to CPM mode
+ if(cvar_string("g_mod_physics") == "Q3")
+ {
+ if(this.notvq3)
return true;
+ }
+ else if(this.notcpm)
+ return true;
+ // Q3 mappers use "notq3a" or "notta" to disable an entity in Q3A or Q3TA
+ // Xonotic has ~equivalent features to Team Arena
if(this.notta)
- if (!(!teamplay || g_tdm || g_ctf))
- return true;
+ return true;
if(this.notsingle)
if(maxclients == 1)
if(this.gametype)
{
string gametypename;
- // static char *gametypeNames[] = {"ffa", "tournament", "single", "team", "ctf", "oneflag", "obelisk", "harvester", "teamtournament"}
+ // From ioq3 g_spawn.c: static char *gametypeNames[] = {"ffa", "tournament", "single", "team", "ctf", "oneflag", "obelisk", "harvester"};
gametypename = "ffa";
if(teamplay)
gametypename = "team";
gametypename = "tournament";
if(maxclients == 1)
gametypename = "single";
- // we do not have the other types (obelisk, harvester, teamtournament)
+ // we do not have the other types (obelisk, harvester)
if(strstrofs(this.gametype, gametypename, 0) < 0)
return true;
}
return false;
}
+
+int GetAmmoConsumptionQ3(string netname)
+// Returns ammo consumed per shot by the primary/default fire mode
+// Returns 0 if the netname has no ammo cvar
+{
+ switch (netname)
+ {
+ case "arc": return autocvar_g_balance_arc_beam_ammo;
+ case "devastator": return autocvar_g_balance_devastator_ammo;
+ case "machinegun": return autocvar_g_balance_machinegun_sustained_ammo;
+ case "minelayer": return autocvar_g_balance_minelayer_ammo;
+ case "seeker": return autocvar_g_balance_seeker_tag_ammo;
+ default: return cvar(strcat("g_balance_", netname, "_primary_ammo"));
+ }
+}
+
#include <common/monsters/_mod.qh>
#include <common/mutators/mutator/buffs/buffs.qh>
#include <common/mutators/mutator/buffs/sv_buffs.qh>
+ #include <common/mutators/mutator/status_effects/_mod.qh>
#include <common/notifications/all.qh>
#include <common/util.qh>
#include <common/weapons/_all.qh>
if (item.strength_finished)
{
pickedup = true;
- STAT(STRENGTH_FINISHED, player) = max(STAT(STRENGTH_FINISHED, player), time) + item.strength_finished;
+ StatusEffects_apply(STATUSEFFECT_Strength, player, max(StatusEffects_gettime(STATUSEFFECT_Strength, player), time) + item.strength_finished, 0);
}
if (item.invincible_finished)
{
pickedup = true;
- STAT(INVINCIBLE_FINISHED, player) = max(STAT(INVINCIBLE_FINISHED, player), time) + item.invincible_finished;
+ StatusEffects_apply(STATUSEFFECT_Shield, player, max(StatusEffects_gettime(STATUSEFFECT_Shield, player), time) + item.invincible_finished, 0);
}
if (item.superweapons_finished)
{
pickedup = true;
- STAT(SUPERWEAPONS_FINISHED, player) = max(STAT(SUPERWEAPONS_FINISHED, player), time) + item.superweapons_finished;
+ StatusEffects_apply(STATUSEFFECT_Superweapons, player, max(StatusEffects_gettime(STATUSEFFECT_Superweapons, player), time) + item.superweapons_finished, 0);
}
// always eat teamed entities
if(autocvar_spawn_debug >= 2)
{
- // why not flags & fl_item?
- FOREACH_ENTITY_RADIUS(this.origin, 3, it.is_item, {
- LOG_TRACE("XXX Found duplicated item: ", itemname, vtos(this.origin));
- LOG_TRACE(" vs ", it.netname, vtos(it.origin));
- error("Mapper sucks.");
- });
+ // why not flags & fl_item?
+ FOREACH_ENTITY_RADIUS(this.origin, 3, it.is_item, {
+ LOG_TRACE("XXX Found duplicated item: ", itemname, vtos(this.origin));
+ LOG_TRACE(" vs ", it.netname, vtos(it.origin));
+ error("Mapper sucks.");
+ });
this.is_item = true;
}
weaponsInMap |= WepSet_FromWeapon(REGISTRY_GET(Weapons, weaponid));
- if ( def.instanceOfPowerup
- || def.instanceOfWeaponPickup
+ if ( def.instanceOfPowerup
+ || def.instanceOfWeaponPickup
|| (def.instanceOfHealth && def != ITEM_HealthSmall)
|| (def.instanceOfArmor && def != ITEM_ArmorSmall)
|| (itemid & (IT_KEY1 | IT_KEY2))
this.bot_pickupevalfunc = pickupevalfunc;
this.bot_pickupbasevalue = pickupbasevalue;
this.mdl = this.model ? this.model : strzone(this.item_model_ent.model_str());
- this.netname = itemname;
+ this.netname = (def.m_weapon) ? def.m_weapon.netname : def.netname;
settouch(this, Item_Touch);
setmodel(this, MDL_Null); // precision set below
//this.effects |= EF_LOWPRECISION;
void StartItem(entity this, GameItem def)
{
- def = def.m_spawnfunc_hookreplace(def, this);
- if (def.spawnflags & ITEM_FLAG_MUTATORBLOCKED)
- {
- delete(this);
- return;
- }
- this.classname = def.m_canonical_spawnfunc;
- _StartItem(
- this,
- this.itemdef = def,
- def.m_respawntime(), // defaultrespawntime
- def.m_respawntimejitter() // defaultrespawntimejitter
+ def = def.m_spawnfunc_hookreplace(def, this);
+
+ if (def.spawnflags & ITEM_FLAG_MUTATORBLOCKED)
+ {
+ delete(this);
+ return;
+ }
+
+ this.classname = def.m_canonical_spawnfunc;
+
+ _StartItem(
+ this,
+ this.itemdef = def,
+ def.m_respawntime(), // defaultrespawntime
+ def.m_respawntimejitter() // defaultrespawntimejitter
);
}
else if(argv(j) == "fuel_regen") this.items |= ITEM_JetpackRegen.m_itemid;
else
{
- FOREACH(Buffs, it != BUFF_Null,
+ FOREACH(StatusEffect, it.instanceOfBuff,
{
string s = Buff_UndeprecateName(argv(j));
if(s == it.netname)
{
- STAT(BUFFS, this) |= (it.m_itemid);
- if(!STAT(BUFF_TIME, this))
- STAT(BUFF_TIME, this) = it.m_time(it);
+ this.buffdef = it;
+ if(!this.buffs_finished)
+ this.buffs_finished = it.m_time(it);
break;
}
});
res = GetResource(this, RES_FUEL); if(res != 0) str = sprintf("%s %s%d %s", str, valueprefix, max(0, res), "fuel");
res = GetResource(this, RES_HEALTH); if(res != 0) str = sprintf("%s %s%d %s", str, valueprefix, max(0, res), "health");
res = GetResource(this, RES_ARMOR); if(res != 0) str = sprintf("%s %s%d %s", str, valueprefix, max(0, res), "armor");
- // HACK: buffs share a single timer, so we need to include enabled buffs AFTER disabled ones to avoid loss
- FOREACH(Buffs, it != BUFF_Null && !(STAT(BUFFS, this) & it.m_itemid), str = sprintf("%s %s%d %s", str, valueprefix, max(0, STAT(BUFF_TIME, this)), it.netname));
- FOREACH(Buffs, it != BUFF_Null && (STAT(BUFFS, this) & it.m_itemid), str = sprintf("%s %s%d %s", str, valueprefix, max(0, STAT(BUFF_TIME, this)), it.netname));
+ FOREACH(StatusEffect, it.instanceOfBuff, str = sprintf("%s %s%d %s", str, valueprefix, this.buffs_finished * boolean(this.buffdef == it), it.netname));
FOREACH(Weapons, it != WEP_Null, str = sprintf("%s %s%d %s", str, itemprefix, !!(STAT(WEAPONS, this) & (it.m_wepset)), it.netname));
}
this.netname = strzone(str);
bool GiveBuff(entity e, Buff thebuff, int op, int val)
{
- bool had_buff = (STAT(BUFFS, e) & thebuff.m_itemid);
- float new_buff_time = ((had_buff) ? STAT(BUFF_TIME, e) : 0);
+ bool had_buff = StatusEffects_active(thebuff, e);
+ float new_buff_time = ((had_buff) ? StatusEffects_gettime(thebuff, e) : 0);
switch (op)
{
case OP_SET:
}
if(new_buff_time <= 0)
{
- if(had_buff)
- STAT(BUFF_TIME, e) = new_buff_time;
- STAT(BUFFS, e) &= ~thebuff.m_itemid;
+ StatusEffects_remove(thebuff, e, STATUSEFFECT_REMOVE_TIMEOUT);
}
else
{
- STAT(BUFF_TIME, e) = new_buff_time;
- STAT(BUFFS, e) = thebuff.m_itemid; // NOTE: replaces any existing buffs on the player!
+ buff_RemoveAll(e, STATUSEFFECT_REMOVE_CLEAR); // clear old buffs on the player first!
+ StatusEffects_apply(thebuff, e, new_buff_time, 0);
}
- bool have_buff = (STAT(BUFFS, e) & thebuff.m_itemid);
+ bool have_buff = StatusEffects_active(thebuff, e);
return (had_buff != have_buff);
}
return SetResourceExplicit(e, res_type, new_val);
}
+ bool GiveStatusEffect(entity e, StatusEffects this, int op, float val)
+ {
+ bool had_eff = StatusEffects_active(this, e);
+ float new_eff_time = ((had_eff) ? StatusEffects_gettime(this, e) : 0);
+ switch (op)
+ {
+ case OP_SET:
+ new_eff_time = val;
+ break;
+ case OP_MIN:
+ new_eff_time = max(new_eff_time, val);
+ break;
+ case OP_MAX:
+ new_eff_time = min(new_eff_time, val);
+ break;
+ case OP_PLUS:
+ new_eff_time += val;
+ break;
+ case OP_MINUS:
+ new_eff_time -= val;
+ break;
+ }
+ if(new_eff_time <= 0)
+ StatusEffects_remove(this, e, STATUSEFFECT_REMOVE_TIMEOUT);
+ else
+ StatusEffects_apply(this, e, new_eff_time, 0);
+ bool have_eff = StatusEffects_active(this, e);
+ return (had_eff != have_eff);
+ }
float GiveItems(entity e, float beginarg, float endarg)
{
}
}
- STAT(STRENGTH_FINISHED, e) = max(0, STAT(STRENGTH_FINISHED, e) - time);
- STAT(INVINCIBLE_FINISHED, e) = max(0, STAT(INVINCIBLE_FINISHED, e) - time);
- STAT(SUPERWEAPONS_FINISHED, e) = max(0, STAT(SUPERWEAPONS_FINISHED, e) - time);
- STAT(BUFF_TIME, e) = max(0, STAT(BUFF_TIME, e) - time);
+ if(e.statuseffects)
+ {
+ FOREACH(StatusEffect, true,
+ {
+ e.statuseffects.statuseffect_time[it.m_id] = max(0, e.statuseffects.statuseffect_time[it.m_id] - time);
+ });
+ }
PREGIVE(e, items);
PREGIVE_WEAPONS(e);
- PREGIVE(e, stat_STRENGTH_FINISHED);
- PREGIVE(e, stat_INVINCIBLE_FINISHED);
- PREGIVE(e, stat_SUPERWEAPONS_FINISHED);
+ PREGIVE_STATUSEFFECT(e, STATUSEFFECT_Strength);
+ PREGIVE_STATUSEFFECT(e, STATUSEFFECT_Shield);
+ //PREGIVE_STATUSEFFECT(e, STATUSEFFECT_Superweapons);
PREGIVE_RESOURCE(e, RES_BULLETS);
PREGIVE_RESOURCE(e, RES_CELLS);
PREGIVE_RESOURCE(e, RES_PLASMA);
continue;
case "ALL":
got += GiveBit(e, items, ITEM_JetpackRegen.m_itemid, op, val);
- got += GiveValue(e, stat_STRENGTH_FINISHED, op, val);
- got += GiveValue(e, stat_INVINCIBLE_FINISHED, op, val);
- got += GiveValue(e, stat_SUPERWEAPONS_FINISHED, op, val);
+ got += GiveStatusEffect(e, STATUSEFFECT_Strength, op, val);
+ got += GiveStatusEffect(e, STATUSEFFECT_Shield, op, val);
+ got += GiveStatusEffect(e, STATUSEFFECT_Superweapons, op, val);
got += GiveBit(e, items, IT_UNLIMITED_AMMO | IT_UNLIMITED_SUPERWEAPONS, op, val);
case "all":
got += GiveBit(e, items, ITEM_Jetpack.m_itemid, op, val);
case "allweapons":
FOREACH(Weapons, it != WEP_Null && !(it.spawnflags & (WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK)), got += GiveWeapon(e, it.m_id, op, val));
//case "allbuffs": // all buffs makes a player god, do not want!
- //FOREACH(Buffs, it != BUFF_Null, got += GiveBuff(e, it.m_itemid, op, val));
+ //FOREACH(StatusEffect, it.instanceOfBuff, got += GiveBuff(e, it, op, val));
case "allammo":
got += GiveResourceValue(e, RES_CELLS, op, val);
got += GiveResourceValue(e, RES_PLASMA, op, val);
got += GiveBit(e, items, ITEM_JetpackRegen.m_itemid, op, val);
break;
case "strength":
- got += GiveValue(e, stat_STRENGTH_FINISHED, op, val);
+ got += GiveStatusEffect(e, STATUSEFFECT_Strength, op, val);
break;
case "invincible":
- got += GiveValue(e, stat_INVINCIBLE_FINISHED, op, val);
+ got += GiveStatusEffect(e, STATUSEFFECT_Shield, op, val);
break;
case "superweapons":
- got += GiveValue(e, stat_SUPERWEAPONS_FINISHED, op, val);
+ got += GiveStatusEffect(e, STATUSEFFECT_Superweapons, op, val);
break;
case "cells":
got += GiveResourceValue(e, RES_CELLS, op, val);
got += GiveResourceValue(e, RES_FUEL, op, val);
break;
default:
- FOREACH(Buffs, it != BUFF_Null && buff_Available(it) && Buff_UndeprecateName(cmd) == it.netname,
+ FOREACH(StatusEffect, it.instanceOfBuff && buff_Available(it) && Buff_UndeprecateName(cmd) == it.netname,
{
got += GiveBuff(e, it, op, val);
break;
if(STAT(WEAPONS, e) & (it.m_wepset))
it.wr_init(it);
});
- POSTGIVE_VALUE(e, stat_STRENGTH_FINISHED, 1, SND_POWERUP, SND_POWEROFF);
- POSTGIVE_VALUE(e, stat_INVINCIBLE_FINISHED, 1, SND_Shield, SND_POWEROFF);
- //POSTGIVE_VALUE(e, stat_SUPERWEAPONS_FINISHED, 1, SND_Null, SND_Null);
+ POSTGIVE_STATUSEFFECT(e, STATUSEFFECT_Strength, 1, SND_POWERUP, SND_POWEROFF);
+ POSTGIVE_STATUSEFFECT(e, STATUSEFFECT_Shield, 1, SND_POWERUP, SND_POWEROFF);
POSTGIVE_RESOURCE(e, RES_BULLETS, 0, SND_ITEMPICKUP, SND_Null);
POSTGIVE_RESOURCE(e, RES_CELLS, 0, SND_ITEMPICKUP, SND_Null);
POSTGIVE_RESOURCE(e, RES_PLASMA, 0, SND_ITEMPICKUP, SND_Null);
POSTGIVE_RES_ROT(e, RES_ARMOR, 1, pauserotarmor_finished, autocvar_g_balance_pause_armor_rot, pauseregen_finished, autocvar_g_balance_pause_health_regen, SND_ARMOR25, SND_Null);
POSTGIVE_RES_ROT(e, RES_HEALTH, 1, pauserothealth_finished, autocvar_g_balance_pause_health_rot, pauseregen_finished, autocvar_g_balance_pause_health_regen, SND_MEGAHEALTH, SND_Null);
- if(STAT(SUPERWEAPONS_FINISHED, e) <= 0)
+ if(!StatusEffects_active(STATUSEFFECT_Superweapons, e))
+ {
if(!g_weaponarena && (STAT(WEAPONS, e) & WEPSET_SUPERWEAPONS))
- STAT(SUPERWEAPONS_FINISHED, e) = autocvar_g_balance_superweapons_time;
+ StatusEffects_apply(STATUSEFFECT_Superweapons, e, autocvar_g_balance_superweapons_time, 0);
+ }
- if(STAT(STRENGTH_FINISHED, e) <= 0)
- STAT(STRENGTH_FINISHED, e) = 0;
- else
- STAT(STRENGTH_FINISHED, e) += time;
- if(STAT(INVINCIBLE_FINISHED, e) <= 0)
- STAT(INVINCIBLE_FINISHED, e) = 0;
- else
- STAT(INVINCIBLE_FINISHED, e) += time;
- if(STAT(SUPERWEAPONS_FINISHED, e) <= 0)
- STAT(SUPERWEAPONS_FINISHED, e) = 0;
- else
- STAT(SUPERWEAPONS_FINISHED, e) += time;
- if(STAT(BUFF_TIME, e) <= 0)
- STAT(BUFF_TIME, e) = 0;
- else
- STAT(BUFF_TIME, e) += time;
+ if(e.statuseffects)
+ {
+ FOREACH(StatusEffect, true,
+ {
+ if(e.statuseffects.statuseffect_time[it.m_id] <= 0)
+ e.statuseffects.statuseffect_time[it.m_id] = 0;
+ else
+ e.statuseffects.statuseffect_time[it.m_id] += time;
+ });
+
+ StatusEffects_update(e);
+ }
for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
{
.float max_armorvalue;
.float pickup_anyway;
+.int count;
.float scheduledrespawntime;
.float respawntime;
#define PREGIVE_WEAPONS(e) WepSet save_weapons; save_weapons = STAT(WEAPONS, e)
#define PREGIVE(e,f) float save_##f; save_##f = (e).f
+ #define PREGIVE_STATUSEFFECT(e,f) float save_##f = StatusEffects_gettime((f), (e))
#define PREGIVE_RESOURCE(e,f) float save_##f = GetResource((e), (f))
#define POSTGIVE_WEAPON(e,b,snd_incr,snd_decr) GiveSound((e), !!(save_weapons & WepSet_FromWeapon(b)), !!(STAT(WEAPONS, e) & WepSet_FromWeapon(b)), 0, snd_incr, snd_decr)
#define POSTGIVE_BIT(e,f,b,snd_incr,snd_decr) GiveSound((e), save_##f & (b), (e).f & (b), 0, snd_incr, snd_decr)
+ #define POSTGIVE_STATUSEFFECT(e,f,t,snd_incr,snd_decr) GiveSound((e), save_##f, StatusEffects_gettime((f), (e)), t, snd_incr, snd_decr)
#define POSTGIVE_RESOURCE(e,f,t,snd_incr,snd_decr) GiveSound((e), save_##f, GetResource((e), (f)), t, snd_incr, snd_decr)
#define POSTGIVE_RES_ROT(e,f,t,rotfield,rottime,regenfield,regentime,snd_incr,snd_decr) GiveRot((e),save_##f,GetResource((e),(f)),rotfield,rottime,regenfield,regentime);GiveSound((e),save_##f,GetResource((e),(f)),t,snd_incr,snd_decr)
#define POSTGIVE_VALUE(e,f,t,snd_incr,snd_decr) GiveSound((e), save_##f, (e).f, t, snd_incr, snd_decr)
#include <server/weapons/csqcprojectile.qh>
#include <server/world.qh>
+ void dropclient_do(entity this)
+ {
+ if (this.owner)
+ dropclient(this.owner);
+ delete(this);
+ }
+ /**
+ * Schedules dropclient for a player and returns true;
+ * if dropclient is already scheduled (for that player) it does nothing and returns false.
+ *
+ * NOTE: this function exists only to allow sending a message to the kicked player with
+ * Send_Notification, which doesn't work if called together with dropclient
+ */
+ bool dropclient_schedule(entity this)
+ {
+ bool scheduled = false;
+ FOREACH_ENTITY_CLASS("dropclient_handler", true,
+ {
+ if(it.owner == this)
+ {
+ scheduled = true;
+ break; // can't use return here, compiler shows a warning
+ }
+ });
+ if (scheduled)
+ return false;
+
+ entity e = new_pure(dropclient_handler);
+ setthink(e, dropclient_do);
+ e.owner = this;
+ e.nextthink = time + 0.1;
+ return true;
+ }
+
void CreatureFrame_hotliquids(entity this)
{
if (this.contents_damagetime >= time)
return;
}
- if (DoesQ3ARemoveThisEntity(this)) {
+ if (q3compat && DoesQ3ARemoveThisEntity(this)) {
delete(this);
return;
}
}
}
+string GetField_fullspawndata(entity e, string f, ...)
+/* Retrieves the value of a map entity field from fullspawndata
+ * This bypasses field value changes made by the engine,
+ * eg string-to-float and escape sequence substitution.
+ *
+ * Avoids the need to declare fields just to read them once :)
+ *
+ * Returns the last instance of the field to match DarkPlaces behaviour.
+ * Path support: converts \ to / and tests the file if a third (bool, true) arg is passed.
+ * Returns string_null if the entity does not have the field, or the file is not in the VFS.
+ *
+ * FIXME: entities with //comments are not supported.
+ */
+{
+ string v = string_null;
+
+ if (!e.fullspawndata)
+ {
+ LOG_WARNF("^1EDICT %s (classname %s) has no fullspawndata, engine lacks support?", ftos(num_for_edict(e)), e.classname);
+ return v;
+ }
+
+ if (strstrofs(e.fullspawndata, "//", 0) >= 0)
+ {
+ // tokenize and tokenize_console return early if "//" is reached,
+ // which can leave an odd number of tokens and break key:value pairing.
+ LOG_WARNF("^1EDICT %s fullspawndata contains unsupported //comment^7%s", ftos(num_for_edict(e)), e.fullspawndata);
+ return v;
+ }
+
+ //print(sprintf("%s(EDICT %s, FIELD %s)\n", __FUNC__, ftos(num_for_edict(e)), f));
+ //print(strcat("FULLSPAWNDATA:", e.fullspawndata, "\n"));
+
+ // tokenize treats \ as an escape, but tokenize_console returns the required literal
+ for (int t = tokenize_console(e.fullspawndata) - 3; t > 0; t -= 2)
+ {
+ //print(sprintf("\tTOKEN %s:%s\t%s:%s\n", ftos(t), ftos(t + 1), argv(t), argv(t + 1)));
+ if (argv(t) == f)
+ {
+ v = argv(t + 1);
+ break;
+ }
+ }
+
+ //print(strcat("RESULT: ", v, "\n\n"));
+
+ if (v && ...(0, bool) == true)
+ {
+ v = strreplace("\\", "/", v);
+ if (whichpack(v) == "")
+ return string_null;
+ }
+
+ return v;
+}
+
void WarpZone_PostInitialize_Callback()
{
// create waypoint links for warpzones
#include <server/weapons/common.qh>
#include <server/world.qh>
+ .string stored_netname; // TODO: store this information independently of race-based gamemodes
+
string uid2name(string myuid)
{
string s = db_get(ServerProgsDB, strcat("/uid2name/", myuid));
IntrusiveList g_race_targets;
IntrusiveList g_racecheckpoints;
- STATIC_INIT(g_race)
- {
- g_race_targets = IL_NEW();
- g_racecheckpoints = IL_NEW();
- }
void race_InitSpectator()
{
float race_readTime(string map, float pos)
{
- string rr = ((g_cts) ? CTS_RECORD : ((g_ctf) ? CTF_RECORD : RACE_RECORD));
-
- return stof(db_get(ServerProgsDB, strcat(map, rr, "time", ftos(pos))));
+ return stof(db_get(ServerProgsDB, strcat(map, record_type, "time", ftos(pos))));
}
string race_readUID(string map, float pos)
{
- string rr = ((g_cts) ? CTS_RECORD : ((g_ctf) ? CTF_RECORD : RACE_RECORD));
-
- return db_get(ServerProgsDB, strcat(map, rr, "crypto_idfp", ftos(pos)));
+ return db_get(ServerProgsDB, strcat(map, record_type, "crypto_idfp", ftos(pos)));
}
float race_readPos(string map, float t)
void race_writeTime(string map, float t, string myuid)
{
- string rr = ((g_cts) ? CTS_RECORD : ((g_ctf) ? CTF_RECORD : RACE_RECORD));
-
float newpos;
newpos = race_readPos(map, t);
// player improved his existing record, only have to iterate on ranks between new and old recs
for (i = prevpos; i > newpos; --i)
{
- db_put(ServerProgsDB, strcat(map, rr, "time", ftos(i)), ftos(race_readTime(map, i - 1)));
- db_put(ServerProgsDB, strcat(map, rr, "crypto_idfp", ftos(i)), race_readUID(map, i - 1));
+ db_put(ServerProgsDB, strcat(map, record_type, "time", ftos(i)), ftos(race_readTime(map, i - 1)));
+ db_put(ServerProgsDB, strcat(map, record_type, "crypto_idfp", ftos(i)), race_readUID(map, i - 1));
}
}
else
{
float other_time = race_readTime(map, i - 1);
if (other_time) {
- db_put(ServerProgsDB, strcat(map, rr, "time", ftos(i)), ftos(other_time));
- db_put(ServerProgsDB, strcat(map, rr, "crypto_idfp", ftos(i)), race_readUID(map, i - 1));
+ db_put(ServerProgsDB, strcat(map, record_type, "time", ftos(i)), ftos(other_time));
+ db_put(ServerProgsDB, strcat(map, record_type, "crypto_idfp", ftos(i)), race_readUID(map, i - 1));
}
}
}
// store new time itself
- db_put(ServerProgsDB, strcat(map, rr, "time", ftos(newpos)), ftos(t));
- db_put(ServerProgsDB, strcat(map, rr, "crypto_idfp", ftos(newpos)), myuid);
+ db_put(ServerProgsDB, strcat(map, record_type, "time", ftos(newpos)), ftos(t));
+ db_put(ServerProgsDB, strcat(map, record_type, "crypto_idfp", ftos(newpos)), myuid);
}
string race_readName(string map, float pos)
{
- string rr = ((g_cts) ? CTS_RECORD : ((g_ctf) ? CTF_RECORD : RACE_RECORD));
+ return uid2name(db_get(ServerProgsDB, strcat(map, record_type, "crypto_idfp", ftos(pos))));
+ }
- return uid2name(db_get(ServerProgsDB, strcat(map, rr, "crypto_idfp", ftos(pos))));
+ void race_checkAndWriteName(entity player)
+ {
+ if(CS_CVAR(player).cvar_cl_allow_uidtracking == 1 && CS_CVAR(player).cvar_cl_allow_uid2name == 1)
+ {
+ if (!player.stored_netname)
+ player.stored_netname = strzone(uid2name(player.crypto_idfp));
+ if(player.stored_netname != player.netname)
+ {
+ db_put(ServerProgsDB, strcat("/uid2name/", player.crypto_idfp), player.netname);
+ strcpy(player.stored_netname, player.netname);
+ }
+ }
}
WriteInt24_t(msg, race_readTime(GetMapname(), 1));
}
-
void race_send_speedaward(float msg)
{
// send the best speed of the round
WriteByte(msg, m);
}
- void race_SendRankings(float pos, float prevpos, float del, float msg)
+ void race_SendRanking(float pos, float prevpos, float del, float msg)
{
WriteHeader(msg, TE_CSQC_RACE);
WriteByte(msg, RACE_NET_SERVER_RANKINGS);
WriteInt24_t(msg, race_readTime(GetMapname(), pos));
}
+ void race_SpeedAwardFrame(entity player)
+ {
+ if (IS_OBSERVER(player))
+ return;
+
+ if(vdist(player.velocity - player.velocity_z * '0 0 1', >, speedaward_speed))
+ {
+ speedaward_speed = vlen(player.velocity - player.velocity_z * '0 0 1');
+ speedaward_holder = player.netname;
+ speedaward_uid = player.crypto_idfp;
+ speedaward_lastupdate = time;
+ }
+ if (speedaward_speed > speedaward_lastsent && (time - speedaward_lastupdate > 1 || intermission_running))
+ {
+ race_send_speedaward(MSG_ALL);
+ speedaward_lastsent = speedaward_speed;
+ if (speedaward_speed > speedaward_alltimebest && speedaward_uid != "")
+ {
+ speedaward_alltimebest = speedaward_speed;
+ speedaward_alltimebest_holder = speedaward_holder;
+ speedaward_alltimebest_uid = speedaward_uid;
+ db_put(ServerProgsDB, strcat(GetMapname(), record_type, "speed/speed"), ftos(speedaward_alltimebest));
+ db_put(ServerProgsDB, strcat(GetMapname(), record_type, "speed/crypto_idfp"), speedaward_alltimebest_uid);
+ race_send_speedaward_alltimebest(MSG_ALL);
+ }
+ }
+ }
+
+ void race_SendAll(entity player, bool only_rankings)
+ {
+ if(!IS_REAL_CLIENT(player))
+ return;
+
+ msg_entity = player;
+ if (!only_rankings)
+ {
+ race_send_recordtime(MSG_ONE);
+ race_send_speedaward(MSG_ONE);
+
+ speedaward_alltimebest = stof(db_get(ServerProgsDB, strcat(GetMapname(), record_type, "speed/speed")));
+ speedaward_alltimebest_holder = uid2name(db_get(ServerProgsDB, strcat(GetMapname(), record_type, "speed/crypto_idfp")));
+ race_send_speedaward_alltimebest(MSG_ONE);
+ }
+
+ int m = min(RANKINGS_CNT, autocvar_g_cts_send_rankings_cnt);
+ race_send_rankings_cnt(MSG_ONE);
+ for (int i = 1; i <= m; ++i)
+ race_SendRanking(i, 0, 0, MSG_ONE);
+ }
+
void race_SendStatus(float id, entity e)
{
if(!IS_REAL_CLIENT(e))
race_send_recordtime(MSG_ALL);
}
- race_SendRankings(newpos, player_prevpos, 0, MSG_ALL);
+ race_SendRanking(newpos, player_prevpos, 0, MSG_ALL);
strcpy(rankings_reply, getrankings());
if(newpos == player_prevpos)
void race_deleteTime(string map, float pos)
{
- string rr = ((g_cts) ? CTS_RECORD : ((g_ctf) ? CTF_RECORD : RACE_RECORD));
-
for(int i = pos; i <= RANKINGS_CNT; ++i)
{
string therank = ftos(i);
if (i == RANKINGS_CNT)
{
- db_remove(ServerProgsDB, strcat(map, rr, "time", therank));
- db_remove(ServerProgsDB, strcat(map, rr, "crypto_idfp", therank));
+ db_remove(ServerProgsDB, strcat(map, record_type, "time", therank));
+ db_remove(ServerProgsDB, strcat(map, record_type, "crypto_idfp", therank));
}
else
{
- db_put(ServerProgsDB, strcat(map, rr, "time", therank), ftos(race_readTime(GetMapname(), i+1)));
- db_put(ServerProgsDB, strcat(map, rr, "crypto_idfp", therank), race_readUID(GetMapname(), i+1));
+ db_put(ServerProgsDB, strcat(map, record_type, "time", therank), ftos(race_readTime(GetMapname(), i+1)));
+ db_put(ServerProgsDB, strcat(map, record_type, "crypto_idfp", therank), race_readUID(GetMapname(), i+1));
}
}
- race_SendRankings(pos, 0, 1, MSG_ALL);
+ race_SendRanking(pos, 0, 1, MSG_ALL);
if(pos == 1)
race_send_recordtime(MSG_ALL);
return false;
}
+void defrag_waypointsprites(entity targeted, entity checkpoint)
+{
+ for(entity t = findchain(target, targeted.targetname); t; t = t.chain)
+ {
+ if(t.modelindex)
+ {
+ entity s = WP_RaceStart;
+
+ if(checkpoint.classname == "target_checkpoint")
+ s = WP_RaceCheckpoint;
+ else if(checkpoint.classname == "target_stopTimer")
+ s = WP_RaceFinish;
+
+ vector o = (t.absmin + t.absmax) * 0.5;
+
+ WaypointSprite_SpawnFixed(s, o, t, sprite, RADARICON_NONE);
+
+ t.sprite.realowner = checkpoint;
+ t.sprite.waypointsprite_visible_for_player = race_waypointsprite_visible_for_player;
+ }
+
+ if(t.targetname)
+ defrag_waypointsprites(t, checkpoint);
+ }
+}
+
void trigger_race_checkpoint_verify(entity this)
{
- static bool have_verified;
+ static bool have_verified;
if (have_verified) return;
have_verified = true;
pl_race_place = 0;
if (!Spawn_FilterOutBadSpots(this, findchain(classname, "info_player_deathmatch"), 0, false, true)) {
error(strcat("Checkpoint ", ftos(i), " misses a spawnpoint with race_place==", ftos(pl_race_place), " (used for respawning in race) - bailing out"));
- }
+ }
if (i == 0) {
// qualifying only
pl_race_place = race_lowest_place_spawn;
if (!Spawn_FilterOutBadSpots(this, findchain(classname, "info_player_deathmatch"), 0, false, true)) {
error(strcat("Checkpoint ", ftos(i), " misses a spawnpoint with race_place==", ftos(pl_race_place), " (used for qualifying) - bailing out"));
- }
+ }
// race only (initial spawn)
g_race_qualifying = 0;
pl_race_place = p;
if (!Spawn_FilterOutBadSpots(this, findchain(classname, "info_player_deathmatch"), 0, false, true)) {
error(strcat("Checkpoint ", ftos(i), " misses a spawnpoint with race_place==", ftos(pl_race_place), " (used for initially spawning in race) - bailing out"));
- }
+ }
}
}
}
pl_race_place = race_lowest_place_spawn;
if (!Spawn_FilterOutBadSpots(this, findchain(classname, "info_player_deathmatch"), 0, false, true)) {
error(strcat("Checkpoint 0 misses a spawnpoint with race_place==", ftos(pl_race_place), " (used for qualifying) - bailing out"));
- }
+ }
} else {
pl_race_checkpoint = race_NextCheckpoint(0);
g_race_qualifying = 1;
for (entity cp = NULL; (cp = find(cp, classname, "target_checkpoint"));) {
if (argv(0) == cp.targetname) {
cp.race_checkpoint = stof(argv(1));
- }
- }
+ }
+ }
}
fclose(fh);
}
g_race_qualifying = qual;
- IL_EACH(g_race_targets, it.classname == "target_checkpoint" || it.classname == "target_startTimer" || it.classname == "target_stopTimer",
- {
- if(it.targetname == "" || !it.targetname) // somehow this is a case...
- continue;
- entity cpt = it;
- FOREACH_ENTITY_STRING(target, cpt.targetname,
- {
- vector org = (it.absmin + it.absmax) * 0.5;
- if(cpt.race_checkpoint == 0)
- WaypointSprite_SpawnFixed(WP_RaceStart, org, it, sprite, RADARICON_NONE);
- else
- WaypointSprite_SpawnFixed(WP_RaceCheckpoint, org, it, sprite, RADARICON_NONE);
-
- it.sprite.realowner = cpt;
- it.sprite.waypointsprite_visible_for_player = race_waypointsprite_visible_for_player;
- });
- });
-
if (race_timed_checkpoint) {
if (defrag_ents) {
IL_EACH(g_race_targets, it.classname == "target_checkpoint" || it.classname == "target_startTimer" || it.classname == "target_stopTimer",
{
- entity cpt = it;
- if(it.classname == "target_startTimer" || it.classname == "target_stopTimer") {
- if(it.targetname == "" || !it.targetname) // somehow this is a case...
- continue;
- FOREACH_ENTITY_STRING(target, cpt.targetname, {
- if(it.sprite)
- WaypointSprite_UpdateSprites(it.sprite, ((cpt.classname == "target_startTimer") ? WP_RaceStart : WP_RaceFinish), WP_Null, WP_Null);
- });
- }
+ defrag_waypointsprites(it, it);
+
if(it.classname == "target_checkpoint") {
if(it.race_checkpoint == -2)
defragcpexists = -1; // something's wrong with the defrag cp file or it has not been written yet, set defragcpexists to -1 so that it will be rewritten when someone finishes
for (entity cp = NULL; (cp = find(cp, classname, "target_checkpoint"));) {
if (cp.race_checkpoint > largest_cp_id) {
largest_cp_id = cp.race_checkpoint;
- }
- }
+ }
+ }
for (entity cp = NULL; (cp = find(cp, classname, "target_stopTimer"));) {
cp.race_checkpoint = largest_cp_id + 1; // finish line
- }
+ }
race_highest_checkpoint = largest_cp_id + 1;
race_timed_checkpoint = largest_cp_id + 1;
} else {
for (entity cp = NULL; (cp = find(cp, classname, "target_stopTimer"));) {
cp.race_checkpoint = 255; // finish line
- }
+ }
race_highest_checkpoint = 255;
race_timed_checkpoint = 255;
}
{
if (it.race_checkpoint == 0) {
WaypointSprite_UpdateSprites(it.sprite, WP_RaceStart, WP_Null, WP_Null);
- } else if (it.race_checkpoint == race_timed_checkpoint) {
+ } else if (it.race_checkpoint == race_timed_checkpoint) {
WaypointSprite_UpdateSprites(it.sprite, WP_RaceFinish, WP_Null, WP_Null);
}
- });
+ });
}
}
- if (defrag_ents) {
+ if (defrag_ents) { /* The following hack shall be removed when per-player trigger_multiple.wait is implemented for cts */
for (entity trigger = NULL; (trigger = find(trigger, classname, "trigger_multiple")); ) {
for (entity targ = NULL; (targ = find(targ, targetname, trigger.target)); ) {
if (targ.classname == "target_checkpoint" || targ.classname == "target_startTimer" || targ.classname == "target_stopTimer") {
this.sprite.waypointsprite_visible_for_player = race_waypointsprite_visible_for_player;
this.spawn_evalfunc = trigger_race_checkpoint_spawn_evalfunc;
+ if (!g_racecheckpoints)
+ g_racecheckpoints = IL_NEW();
IL_PUSH(g_racecheckpoints, this);
+ // trigger_race_checkpoint_verify checks this list too
+ if (!g_race_targets)
+ g_race_targets = IL_NEW();
+
InitializeEntity(this, trigger_race_checkpoint_verify, INITPRIO_FINDTARGET);
}
race_timed_checkpoint = 1;
+ if (!g_race_targets)
+ g_race_targets = IL_NEW();
IL_PUSH(g_race_targets, this);
+ // trigger_race_checkpoint_verify checks this list too
+ if (!g_racecheckpoints)
+ g_racecheckpoints = IL_NEW();
+
InitializeEntity(this, trigger_race_checkpoint_verify, INITPRIO_FINDTARGET);
}
/// \brief Indicates that the player is not allowed to join a team.
const int TEAM_NOT_ALLOWED = -1;
-.float team_forced; // can be a team number to force a team, or 0 for default action, or -1 for forced spectator
-
.int m_team_balance_state; ///< Holds the state of the team balance entity.
.entity m_team_balance_team[NUM_TEAMS]; ///< ???
entity g_team_entities[NUM_TEAMS]; ///< Holds global team entities.
- STATIC_INIT(g_team_entities)
+ void Team_InitTeams()
{
+ if (g_team_entities[0])
+ return;
for (int i = 0; i < NUM_TEAMS; ++i)
{
- g_team_entities[i] = new_pure();
+ g_team_entities[i] = new_pure(team_entity);
}
}
bool SetPlayerTeam(entity player, int team_index, int type)
{
int old_team_index = Entity_GetTeamIndex(player);
+
if (!Player_SetTeamIndex(player, team_index))
- {
return false;
- }
+
LogTeamChange(player.playerid, player.team, type);
+
if (team_index != old_team_index)
{
- PlayerScore_Clear(player);
- if (team_index != -1)
- {
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(
- player.team, INFO_JOIN_PLAY_TEAM), player.netname);
- }
- else
- {
- if (!CS(player).just_joined)
- {
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_QUIT_SPECTATE,
- player.netname);
- }
- }
KillPlayerForTeamChange(player);
+ PlayerScore_Clear(player);
+ CS(player).parm_idlesince = time;
+
if (!IS_BOT_CLIENT(player))
- {
TeamBalance_AutoBalanceBots();
- }
+
+ if (team_index != -1)
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(player.team, INFO_JOIN_PLAY_TEAM), player.netname);
}
- else if (team_index == -1)
+
+ if (team_index == -1)
{
- if (!CS(player).just_joined && player.frags != FRAGS_SPECTATOR)
+ if (autocvar_sv_maxidle_playertospectator > 0 && CS(player).idlekick_lasttimeleft)
+ {
+ // this done here so it happens even when manually speccing during the countdown
+ Kill_Notification(NOTIF_ONE_ONLY, player, MSG_CENTER, CPID_IDLING);
+ CS(player).idlekick_lasttimeleft = 0;
+ }
+ else if (!CS(player).just_joined && player.frags != FRAGS_SPECTATOR)
+ {
Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_QUIT_SPECTATE, player.netname);
+ }
}
+
return true;
}
bool lockteams;
+.int team_forced; // can be a team number to force a team, or 0 for default action, or -1 for forced spectator
+
// ========================== Global teams API ================================
+ void Team_InitTeams();
+
/// \brief Returns the global team entity at the given index.
/// \param[in] index Index of the team.
/// \return Global team entity at the given index.
BADCVAR("g_chatsounds");
BADCVAR("g_ca_point_leadlimit");
BADCVAR("g_ca_point_limit");
+ BADCVAR("g_ca_spectate_enemies");
BADCVAR("g_ctf_captimerecord_always");
BADCVAR("g_ctf_flag_glowtrails");
BADCVAR("g_ctf_dynamiclights");
BADPREFIX("skill_");
BADPREFIX("sv_allow_");
BADPREFIX("sv_cullentities_");
- BADPREFIX("sv_maxidle_");
+ BADPREFIX("sv_maxidle");
BADPREFIX("sv_minigames_");
BADPREFIX("sv_radio_");
BADPREFIX("sv_timeout_");
BADCVAR("sv_defaultplayercolors");
BADCVAR("sv_defaultplayermodel");
BADCVAR("sv_defaultplayerskin");
- BADCVAR("sv_maxidle");
BADCVAR("sv_maxrate");
BADCVAR("sv_motd");
BADCVAR("sv_public");
cvar_changes_init(); // do this very early now so it REALLY matches the server config
+ // default to RACE_RECORD, can be overwritten by gamemodes
+ record_type = RACE_RECORD;
+
// needs to be done so early because of the constants they create
static_init();
MapInfo_Enumerate();
MapInfo_FilterGametype(MapInfo_CurrentGametype(), MapInfo_CurrentFeatures(), MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags(), 1);
- if(fexists(strcat("scripts/", mapname, ".arena")))
- cvar_settemp("sv_q3acompat_machineshotgunswap", "1");
-
- if(fexists(strcat("scripts/", mapname, ".defi")))
- cvar_settemp("sv_q3defragcompat", "1");
+ q3compat = BITSET(q3compat, Q3COMPAT_ARENA, fexists(strcat("scripts/", mapname, ".arena")));
+ q3compat = BITSET(q3compat, Q3COMPAT_DEFI, fexists(strcat("scripts/", mapname, ".defi")));
if(whichpack(strcat("maps/", mapname, ".cfg")) != "")
{
set sv_logscores_bots 0 "exclude bots by default"
// spam (frag/capture) log
- set sv_eventlog 0 "the master switch for efficiency reasons"
- set sv_eventlog_console 1 "print event log entries to the console as well"
- set sv_eventlog_files 0 "save the event log to individual files instead of the main server log"
+ set sv_eventlog 0 "enable event logging"
+ set sv_eventlog_console 1 "print event log entries to the dedicated console as well"
+ set sv_eventlog_files 0 "save the event log to individual files"
set sv_eventlog_files_timestamps 1 "include timestamps in the log file names"
set sv_eventlog_files_counter 0 "internal counter cvar, do not modify"
set sv_eventlog_files_nameprefix xonotic "prefix of individual log file names"
set sv_foginterval 1 "force enable fog in regular intervals"
set sv_maxidle 0 "kick players idle for more than this amount of time in seconds"
- set sv_maxidle_spectatorsareidle 0 "when sv_maxidle is not 0, assume spectators are idle too"
+ set sv_maxidle_alsokickspectators 1 "when sv_maxidle is > 0, kick idle spectators as well as players"
set sv_maxidle_slots 0 "when not 0, only kick idlers when this many or less player slots are available"
set sv_maxidle_slots_countbots 1 "count bots as player slots"
+ set sv_maxidle_playertospectator 60 "move players idle for more than this amount of time in seconds to spectators (sv_maxidle timer starts again after sv_maxidle_playertospectator has moved a player to spectators)"
+
sv_allowdownloads_inarchive 1 // for csprogs.dat
sv_allowdownloads 0 // download protocol is evil
sv_gameplayfix_gravityunaffectedbyticrate 1
sv_gameplayfix_nogravityonground 1
-set sv_q3acompat_machineshotgunswap 0 "shorthand for swapping machinegun and shotgun (for Q3A map compatibility in mapinfo files)"
-set sv_q3defragcompat 0 "toggle for some compatibility hacks (for Q3DF map compatibility)"
+set sv_q3compat_changehitbox 0 "use Q3 player hitbox dimensions and camera height on Q3 maps (maps with an entry in a .arena or .defi file)
set g_movement_highspeed 1 "multiplier scale for movement speed (applies to sv_maxspeed and sv_maxairspeed, also applies to air acceleration when g_movement_highspeed_q3_compat is set to 0)"
set g_movement_highspeed_q3_compat 0 "apply speed modifiers to air movement in a more Q3-compatible way (only apply speed buffs and g_movement_highspeed to max air speed, not to acceleration)"