From: bones_was_here Date: Wed, 30 Sep 2020 17:20:40 +0000 (+1000) Subject: Merge branch 'master' into bones_was_here/q3compat X-Git-Tag: xonotic-v0.8.5~352^2~22 X-Git-Url: http://git.xonotic.org/?a=commitdiff_plain;h=837c32688ad1f1548e7d4cf4339691181c2bb6ff;hp=b91b5fdf11727a5cf30abfea6967fe5f487697f7;p=xonotic%2Fxonotic-data.pk3dir.git Merge branch 'master' into bones_was_here/q3compat --- diff --git a/qcsrc/common/items/item.qh b/qcsrc/common/items/item.qh index 5c6ec494a..26f89cad5 100644 --- a/qcsrc/common/items/item.qh +++ b/qcsrc/common/items/item.qh @@ -65,7 +65,7 @@ const int ITS_GLOW = BIT(6); .float invincible_finished; // ditto #define spawnfunc_body(item) \ - if (!Item_IsDefinitionAllowed(item)) \ + if (!(item) || !Item_IsDefinitionAllowed(item)) \ { \ startitem_failed = true; \ delete(this); \ diff --git a/qcsrc/common/items/item/ammo.qc b/qcsrc/common/items/item/ammo.qc index 739d94aca..48ca0ddab 100644 --- a/qcsrc/common/items/item/ammo.qc +++ b/qcsrc/common/items/item/ammo.qc @@ -1,14 +1,2 @@ #include "ammo.qh" -#ifdef SVQC - -METHOD(Shells, m_spawnfunc_hookreplace, GameItem(Shells this, entity e)) -{ - if (autocvar_sv_q3acompat_machineshotgunswap && !Item_IsLoot(e)) - { - return ITEM_Bullets; - } - return this; -} - -#endif diff --git a/qcsrc/common/items/item/powerup.qh b/qcsrc/common/items/item/powerup.qh index bf4727a79..921cc0b2f 100644 --- a/qcsrc/common/items/item/powerup.qh +++ b/qcsrc/common/items/item/powerup.qh @@ -14,6 +14,7 @@ CLASS(Powerup, Pickup) ATTRIB(Powerup, m_itemflags, int, FL_POWERUP); ATTRIB(Powerup, m_respawntime, float(), GET(g_pickup_respawntime_powerup)); ATTRIB(Powerup, m_respawntimejitter, float(), GET(g_pickup_respawntimejitter_powerup)); + ATTRIB(Powerup, count, float); #endif ENDCLASS(Powerup) @@ -27,7 +28,7 @@ float autocvar_g_balance_powerup_strength_time; void powerup_strength_init(Pickup this, entity item) { if(!item.strength_finished) - item.strength_finished = autocvar_g_balance_powerup_strength_time; + item.strength_finished = (item.count) ? item.count : autocvar_g_balance_powerup_strength_time; } #endif REGISTER_ITEM(Strength, Powerup) { @@ -65,7 +66,7 @@ float autocvar_g_balance_powerup_invincible_time; void powerup_shield_init(Pickup this, entity item) { if(!item.invincible_finished) - item.invincible_finished = autocvar_g_balance_powerup_invincible_time; + item.invincible_finished = (item.count) ? item.count : autocvar_g_balance_powerup_invincible_time; } #endif REGISTER_ITEM(Shield, Powerup) { diff --git a/qcsrc/common/mapobjects/func/button.qc b/qcsrc/common/mapobjects/func/button.qc index 423ac5e7b..c716b400c 100644 --- a/qcsrc/common/mapobjects/func/button.qc +++ b/qcsrc/common/mapobjects/func/button.qc @@ -206,8 +206,8 @@ spawnfunc(func_button) if (!this.lip) this.lip = 4; - if(this.wait == -1 && autocvar_sv_q3defragcompat) - this.wait = 0.1; // compatibility for q3df: "instant" return + if(this.wait < 0 && q3compat) + this.wait = 0.1; // compatibility for q3: -1 = return immediately if(this.noise != "") precache_sound(this.noise); diff --git a/qcsrc/common/mapobjects/func/door.qc b/qcsrc/common/mapobjects/func/door.qc index 11196ab38..173c9d9cd 100644 --- a/qcsrc/common/mapobjects/func/door.qc +++ b/qcsrc/common/mapobjects/func/door.qc @@ -653,12 +653,25 @@ void door_init_shared(entity this) } // TODO: other soundpacks - if (this.sounds > 0) + if (this.sounds > 0 || q3compat) { + // Doors in Q3 always have sounds (they're hard coded in Q3 engine) this.noise2 = "plats/medplat1.wav"; this.noise1 = "plats/medplat2.wav"; } + if (q3compat) + { + // CPMA adds these fields for overriding the engine sounds + string s = GetField_fullspawndata(this, "sound_start", true); + string e = GetField_fullspawndata(this, "sound_end", true); + + if (s) + this.noise2 = strzone(s); + if (e) + this.noise1 = strzone(e); + } + // sound when door stops moving if(this.noise1 && this.noise1 != "") { @@ -737,7 +750,7 @@ spawnfunc(func_door) } else if (!this.speed) { - if (autocvar_sv_q3defragcompat) + if (q3compat) this.speed = 400; else this.speed = 100; diff --git a/qcsrc/common/mapobjects/func/plat.qc b/qcsrc/common/mapobjects/func/plat.qc index 64275a357..d001fe299 100644 --- a/qcsrc/common/mapobjects/func/plat.qc +++ b/qcsrc/common/mapobjects/func/plat.qc @@ -76,8 +76,9 @@ spawnfunc(func_plat) this.noise1 = "plats/plat2.wav"; } - if (this.sounds == 2) + if (this.sounds == 2 || q3compat) { + // Plats in Q3 always have sounds (they're hard coded in Q3 engine) this.noise = "plats/medplat1.wav"; this.noise1 = "plats/medplat2.wav"; } @@ -88,6 +89,18 @@ spawnfunc(func_plat) if (this.sound2) this.noise1 = this.sound2; + if (q3compat) + { + // CPMA adds these fields for overriding the engine sounds + string s = GetField_fullspawndata(this, "sound_start", true); + string e = GetField_fullspawndata(this, "sound_end", true); + + if (s) + this.noise = strzone(s); + if (e) + this.noise1 = strzone(e); + } + if(this.noise && this.noise != "") { precache_sound(this.noise); diff --git a/qcsrc/common/mapobjects/trigger/hurt.qc b/qcsrc/common/mapobjects/trigger/hurt.qc index 8c21c509c..20c08feb1 100644 --- a/qcsrc/common/mapobjects/trigger/hurt.qc +++ b/qcsrc/common/mapobjects/trigger/hurt.qc @@ -11,6 +11,8 @@ void trigger_hurt_use(entity this, entity actor, entity trigger) .float triggerhurttime; void trigger_hurt_touch(entity this, entity toucher) { + if (!toucher.takedamage) + return; if (this.active != ACTIVE_ACTIVE) return; @@ -21,11 +23,10 @@ void trigger_hurt_touch(entity this, entity toucher) // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu) if (toucher.iscreature) { - if (toucher.takedamage) - if (toucher.triggerhurttime < time) + if (time >= toucher.triggerhurttime + ((q3compat && !(this.spawnflags & HURT_SLOW)) ? autocvar_sys_ticrate : 1)) { EXACTTRIGGER_TOUCH(this, toucher); - toucher.triggerhurttime = time + ((autocvar_sv_q3defragcompat && !(this.spawnflags & HURT_SLOW)) ? 0.1 : 1); + toucher.triggerhurttime = time; entity own; own = this.enemy; @@ -40,14 +41,9 @@ void trigger_hurt_touch(entity this, entity toucher) } else if(toucher.damagedbytriggers) { - if(toucher.takedamage) - { - EXACTTRIGGER_TOUCH(this, toucher); - Damage(toucher, this, this, this.dmg, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, toucher.origin, '0 0 0'); - } + EXACTTRIGGER_TOUCH(this, toucher); + Damage(toucher, this, this, this.dmg, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, toucher.origin, '0 0 0'); } - - return; } /*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ? @@ -66,7 +62,7 @@ spawnfunc(trigger_hurt) this.use = trigger_hurt_use; this.enemy = world; // I hate you all if (!this.dmg) - this.dmg = ((autocvar_sv_q3defragcompat) ? 5 : 10000); + this.dmg = ((q3compat) ? 5 : 10000); if (this.message == "") this.message = "was in the wrong place"; if (this.message2 == "") diff --git a/qcsrc/common/mapobjects/trigger/jumppads.qc b/qcsrc/common/mapobjects/trigger/jumppads.qc index 5273179ad..3b9276d6e 100644 --- a/qcsrc/common/mapobjects/trigger/jumppads.qc +++ b/qcsrc/common/mapobjects/trigger/jumppads.qc @@ -135,11 +135,8 @@ bool jumppad_push(entity this, entity targ) return false; vector org = targ.origin; -#ifdef SVQC - if(autocvar_sv_q3defragcompat) -#elif defined(CSQC) - if(STAT(Q3DEFRAGCOMPAT)) -#endif + + if(STAT(Q3COMPAT)) { org.z += targ.mins_z; org.z += 1; // off by 1! diff --git a/qcsrc/common/mapobjects/trigger/multi.qc b/qcsrc/common/mapobjects/trigger/multi.qc index 9ce5f52ce..0b0efc21c 100644 --- a/qcsrc/common/mapobjects/trigger/multi.qc +++ b/qcsrc/common/mapobjects/trigger/multi.qc @@ -52,6 +52,7 @@ void multi_trigger(entity this) { // we can't just delete(this) here, because this is a touch function // called while C code is looping through area links... settouch(this, func_null); + this.use = func_null; } } @@ -133,6 +134,7 @@ void multi_reset(entity this) setthink(this, func_null); this.nextthink = 0; this.team = this.team_saved; + this.use = multi_use; } /*QUAKED spawnfunc_trigger_multiple (.5 .5 .5) ? notouch @@ -167,7 +169,7 @@ spawnfunc(trigger_multiple) this.wait = 0; this.use = multi_use; - if(this.wait == -1 && autocvar_sv_q3defragcompat) + if(this.wait == -1 && (q3compat & Q3COMPAT_DEFI)) this.wait = 0.1; // compatibility for q3df: "instant" return EXACTTRIGGER_INIT; diff --git a/qcsrc/common/mutators/mutator/buffs/all.inc b/qcsrc/common/mutators/mutator/buffs/all.inc index 5b53b3d65..3b3804b84 100644 --- a/qcsrc/common/mutators/mutator/buffs/all.inc +++ b/qcsrc/common/mutators/mutator/buffs/all.inc @@ -2,12 +2,17 @@ string Buff_UndeprecateName(string buffname) { 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; } } @@ -19,7 +24,7 @@ REGISTER_BUFF(AMMO) { 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"); @@ -28,7 +33,7 @@ REGISTER_BUFF(RESISTANCE) { 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"); @@ -37,8 +42,7 @@ REGISTER_BUFF(SPEED) { 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"); @@ -47,8 +51,8 @@ REGISTER_BUFF(MEDIC) { 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"); @@ -57,7 +61,7 @@ REGISTER_BUFF(BASH) { 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"); @@ -66,6 +70,7 @@ REGISTER_BUFF(VAMPIRE) { 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"); @@ -82,6 +87,7 @@ REGISTER_BUFF(VENGEANCE) { 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"); @@ -90,7 +96,7 @@ REGISTER_BUFF(JUMP) { 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"); @@ -99,7 +105,7 @@ REGISTER_BUFF(INVISIBLE) { 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"); @@ -108,6 +114,7 @@ REGISTER_BUFF(INFERNO) { 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"); @@ -116,6 +123,7 @@ REGISTER_BUFF(SWAPPER) { 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"); @@ -140,4 +148,4 @@ REGISTER_BUFF(FLIGHT) { 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) diff --git a/qcsrc/common/mutators/mutator/buffs/buffs.qh b/qcsrc/common/mutators/mutator/buffs/buffs.qh index 91f56066f..c0ccf5803 100644 --- a/qcsrc/common/mutators/mutator/buffs/buffs.qh +++ b/qcsrc/common/mutators/mutator/buffs/buffs.qh @@ -58,11 +58,11 @@ STATIC_INIT(REGISTER_BUFFS) { 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); diff --git a/qcsrc/common/physics/player.qc b/qcsrc/common/physics/player.qc index 319a6bf64..172465802 100644 --- a/qcsrc/common/physics/player.qc +++ b/qcsrc/common/physics/player.qc @@ -55,13 +55,13 @@ void Physics_UpdateStats(entity this) : 0; STAT(MOVEVARS_AIRSPEEDLIMIT_NONQW, this) = Physics_ClientOption(this, "airspeedlimit_nonqw", autocvar_sv_airspeedlimit_nonqw) * maxspd_mod; } - bool q3dfcompat = autocvar_sv_q3defragcompat && autocvar_sv_q3defragcompat_changehitbox; // NOTE: these hitboxes are off by 1 due to engine differences - STAT(PL_MIN, this) = (q3dfcompat) ? '-15 -15 -20' : autocvar_sv_player_mins; - STAT(PL_MAX, this) = (q3dfcompat) ? '15 15 36' : autocvar_sv_player_maxs; - STAT(PL_VIEW_OFS, this) = (q3dfcompat) ? '0 0 30' : autocvar_sv_player_viewoffset; - STAT(PL_CROUCH_MIN, this) = (q3dfcompat) ? '-15 -15 -20' : autocvar_sv_player_crouch_mins; - STAT(PL_CROUCH_MAX, this) = (q3dfcompat) ? '15 15 20' : autocvar_sv_player_crouch_maxs; - STAT(PL_CROUCH_VIEW_OFS, this) = (q3dfcompat) ? '0 0 16' : autocvar_sv_player_crouch_viewoffset; + bool q3hb = q3compat && autocvar_sv_q3compat_changehitbox; + STAT(PL_MIN, this) = (q3hb) ? '-15 -15 -20' : autocvar_sv_player_mins; + STAT(PL_MAX, this) = (q3hb) ? '15 15 36' : autocvar_sv_player_maxs; + STAT(PL_VIEW_OFS, this) = (q3hb) ? '0 0 30' : autocvar_sv_player_viewoffset; + STAT(PL_CROUCH_MIN, this) = (q3hb) ? '-15 -15 -20' : autocvar_sv_player_crouch_mins; + STAT(PL_CROUCH_MAX, this) = (q3hb) ? '15 15 20' : autocvar_sv_player_crouch_maxs; + STAT(PL_CROUCH_VIEW_OFS, this) = (q3hb) ? '0 0 16' : autocvar_sv_player_crouch_viewoffset; // old stats // fix some new settings diff --git a/qcsrc/common/stats.qh b/qcsrc/common/stats.qh index cda98d54d..ffc0cfacf 100644 --- a/qcsrc/common/stats.qh +++ b/qcsrc/common/stats.qh @@ -3,6 +3,7 @@ #ifdef SVQC #include #include +#include #include #endif @@ -326,10 +327,7 @@ bool autocvar_sv_slick_applygravity; #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" diff --git a/qcsrc/common/weapons/all.qc b/qcsrc/common/weapons/all.qc index 7c089ba70..5de760d48 100644 --- a/qcsrc/common/weapons/all.qc +++ b/qcsrc/common/weapons/all.qc @@ -234,6 +234,40 @@ string GetAmmoName(int ammotype) } } +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 SVQC +int GetAmmoConsumptionPrimary(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")); + } +} +#endif + #ifdef CSQC int GetAmmoTypeFromNum(int i) { diff --git a/qcsrc/common/weapons/weapon.qh b/qcsrc/common/weapons/weapon.qh index 67f646cbe..916acdf9e 100644 --- a/qcsrc/common/weapons/weapon.qh +++ b/qcsrc/common/weapons/weapon.qh @@ -231,6 +231,12 @@ string GetAmmoPicture(int ammotype); string GetAmmoName(int ammotype); +entity GetAmmoItem(int ammotype); + +#ifdef SVQC +int GetAmmoConsumptionPrimary(string netname); +#endif + #ifdef CSQC int GetAmmoTypeFromNum(int i); int GetAmmoStat(int ammotype); diff --git a/qcsrc/common/weapons/weapon/shockwave.qc b/qcsrc/common/weapons/weapon/shockwave.qc index accb917f6..ea476f7d8 100644 --- a/qcsrc/common/weapons/weapon/shockwave.qc +++ b/qcsrc/common/weapons/weapon/shockwave.qc @@ -3,18 +3,6 @@ REGISTER_NET_TEMP(TE_CSQC_SHOCKWAVEPARTICLE) #ifdef SVQC -// enable when shockwave replaces shotgun -#if 0 -METHOD(Shockwave, m_spawnfunc_hookreplace, Weapon(Shockwave this, entity e)) -{ - //if(autocvar_sv_q3acompat_machineshockwaveswap) // WEAPONTODO - if (autocvar_sv_q3acompat_machineshotgunswap && !Item_IsLoot(e)) - { - return WEP_MACHINEGUN; - } - return this; -} -#endif const float MAX_SHOCKWAVE_HITS = 10; //#define DEBUG_SHOCKWAVE diff --git a/qcsrc/common/weapons/weapon/shotgun.qc b/qcsrc/common/weapons/weapon/shotgun.qc index dcde58a30..bc1ce8947 100644 --- a/qcsrc/common/weapons/weapon/shotgun.qc +++ b/qcsrc/common/weapons/weapon/shotgun.qc @@ -5,15 +5,6 @@ // enable to debug melee range //#define SHOTGUN_MELEEDEBUG -METHOD(Shotgun, m_spawnfunc_hookreplace, Weapon(Shotgun this, entity e)) -{ - if (autocvar_sv_q3acompat_machineshotgunswap && !Item_IsLoot(e)) - { - return WEP_MACHINEGUN; - } - return this; -} - void W_Shotgun_Attack(Weapon thiswep, entity actor, .entity weaponentity, float isprimary, float ammocount, float damage, float bullets, float spread, float solidpenetration, float force, entity bullet_trail_effect) { W_DecreaseAmmo(thiswep, actor, ammocount, weaponentity); diff --git a/qcsrc/common/weapons/weapon/shotgun.qh b/qcsrc/common/weapons/weapon/shotgun.qh index 1c2d28010..d99a371f3 100644 --- a/qcsrc/common/weapons/weapon/shotgun.qh +++ b/qcsrc/common/weapons/weapon/shotgun.qh @@ -56,4 +56,3 @@ CLASS(Shotgun, Weapon) ENDCLASS(Shotgun) REGISTER_WEAPON(SHOTGUN, shotgun, NEW(Shotgun)); -SPAWNFUNC_WEAPON(weapon_shotgun, WEP_SHOTGUN) diff --git a/qcsrc/lib/spawnfunc.qh b/qcsrc/lib/spawnfunc.qh index 321610ad3..939c5a7e5 100644 --- a/qcsrc/lib/spawnfunc.qh +++ b/qcsrc/lib/spawnfunc.qh @@ -7,7 +7,8 @@ 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 @@ -178,6 +179,11 @@ noref bool require_spawnfunc_prefix; 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) \ @@ -279,6 +285,11 @@ noref bool __spawnfunc_first; } \ 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 */ \ diff --git a/qcsrc/lib/warpzone/common.qc b/qcsrc/lib/warpzone/common.qc index 830c56de6..34c7bb2ba 100644 --- a/qcsrc/lib/warpzone/common.qc +++ b/qcsrc/lib/warpzone/common.qc @@ -787,10 +787,13 @@ entity WarpZone_RefSys_SpawnSameRefSys(entity me) 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); } diff --git a/qcsrc/server/autocvars.qh b/qcsrc/server/autocvars.qh index 83ea702ae..28d08befe 100644 --- a/qcsrc/server/autocvars.qh +++ b/qcsrc/server/autocvars.qh @@ -327,7 +327,6 @@ float autocvar_sv_maxspeed; string autocvar_sv_motd; int autocvar_sv_name_maxlength = 64; bool autocvar_sv_precacheplayermodels; -bool autocvar_sv_q3acompat_machineshotgunswap; bool autocvar_sv_servermodelsonly; int autocvar_sv_spectate; float autocvar_sv_spectator_speed_multiplier; @@ -533,7 +532,7 @@ bool autocvar_sv_showspectators; bool autocvar_g_weaponswitch_debug; bool autocvar_g_weaponswitch_debug_alternate; bool autocvar_g_allow_checkpoints; -bool autocvar_sv_q3defragcompat_changehitbox = false; +bool autocvar_sv_q3compat_changehitbox; int autocvar_sv_clones; bool autocvar_g_footsteps; float autocvar_sv_maxidle; diff --git a/qcsrc/server/client.qc b/qcsrc/server/client.qc index d67cd46ae..72bb02b20 100644 --- a/qcsrc/server/client.qc +++ b/qcsrc/server/client.qc @@ -619,8 +619,7 @@ void PutPlayerInServer(entity this) 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; diff --git a/qcsrc/server/compat/quake.qc b/qcsrc/server/compat/quake.qc index 23ae923b0..8c227f9be 100644 --- a/qcsrc/server/compat/quake.qc +++ b/qcsrc/server/compat/quake.qc @@ -4,15 +4,17 @@ #include #include -//*********************** -//QUAKE 1 ENTITIES - So people can play quake1 maps with the xonotic weapons -//*********************** -SPAWNFUNC_WEAPON(weapon_nailgun, WEP_ELECTRO) +/*********************** + * QUAKE 1 ENTITIES - So people can play quake1 maps with the xonotic weapons + *********************** + weapon_nailgun handled in quake3.qc + item_armor1 handled in items.qc +*/ + SPAWNFUNC_WEAPON(weapon_supernailgun, WEP_HAGAR) SPAWNFUNC_WEAPON(weapon_supershotgun, WEP_MACHINEGUN) SPAWNFUNC_ITEM(item_spikes, ITEM_Bullets) -//spawnfunc(item_armor1) {spawnfunc_item_armor_medium(this);} // FIXME: in Quake this is green armor, in Xonotic maps it is an armor shard SPAWNFUNC_ITEM(item_armor2, ITEM_ArmorMega) SPAWNFUNC_ITEM(item_armorInv, ITEM_ArmorMega) // TODO: make sure we actually want this SPAWNFUNC_ITEM_COND(item_health, (this.spawnflags & 2), ITEM_HealthMega, ITEM_HealthMedium) diff --git a/qcsrc/server/compat/quake3.qc b/qcsrc/server/compat/quake3.qc index bda38dcf2..9347077a4 100644 --- a/qcsrc/server/compat/quake3.qc +++ b/qcsrc/server/compat/quake3.qc @@ -13,53 +13,89 @@ #include #include #include -#include -//*********************** -//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) @@ -158,52 +194,42 @@ spawnfunc(target_init) 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)); this.netname = cons(this.netname, buff.netname); - STAT(BUFF_TIME, this) = it.count; + STAT(BUFF_TIME, this) += 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; @@ -247,35 +273,31 @@ spawnfunc(target_fragsFilter) 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) @@ -292,7 +314,7 @@ bool DoesQ3ARemoveThisEntity(entity this) 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"; @@ -304,7 +326,7 @@ bool DoesQ3ARemoveThisEntity(entity this) 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; } diff --git a/qcsrc/server/compat/quake3.qh b/qcsrc/server/compat/quake3.qh index 20e4879d9..d6ca4fd55 100644 --- a/qcsrc/server/compat/quake3.qh +++ b/qcsrc/server/compat/quake3.qh @@ -1,5 +1,42 @@ #pragma once +int q3compat = 0; +#define Q3COMPAT_ARENA BIT(0) +#define Q3COMPAT_DEFI BIT(1) + bool DoesQ3ARemoveThisEntity(entity this); .int fragsfilter_cnt; + +// The ammo spawnfunc knows which weapon will use the ammo so it can look up the type +// and calculate the amount required for the number of shots in the count field. +#define _SPAWNFUNC_Q3AMMO(ammo_classname, xonwep) \ + if(this.count) \ + SetResource(this, xonwep.ammo_type, this.count * GetAmmoConsumptionPrimary(xonwep.netname)); \ + spawnfunc_body(GetAmmoItem(xonwep.ammo_type)); + +// Ammo only, unconditional +#define SPAWNFUNC_Q3AMMO(ammo_classname, xonwep) \ + spawnfunc(ammo_classname) \ + { \ + _SPAWNFUNC_Q3AMMO(ammo_classname, xonwep) \ + } + +// Ammo only, conditional +#define SPAWNFUNC_Q3AMMO_COND(ammo_classname, cond, xonwep1, xonwep0) \ + spawnfunc(ammo_classname) \ + { \ + entity xonwep = (cond) ? xonwep1 : xonwep0; \ + _SPAWNFUNC_Q3AMMO(ammo_classname, xonwep) \ + } + +// Weapon & ammo, unconditional +#define SPAWNFUNC_Q3(weapon_classname, ammo_classname, xonwep) \ + SPAWNFUNC_WEAPON(weapon_classname, xonwep) \ + SPAWNFUNC_Q3AMMO(ammo_classname, xonwep) + +// Weapon & ammo, conditional +#define SPAWNFUNC_Q3_COND(weapon_classname, ammo_classname, cond, xonwep1, xonwep0) \ + SPAWNFUNC_WEAPON_COND(weapon_classname, cond, xonwep1, xonwep0) \ + SPAWNFUNC_Q3AMMO_COND(ammo_classname, cond, xonwep1, xonwep0) + diff --git a/qcsrc/server/items/items.qc b/qcsrc/server/items/items.qc index 41c7eca4e..bf106b180 100644 --- a/qcsrc/server/items/items.qc +++ b/qcsrc/server/items/items.qc @@ -1027,19 +1027,19 @@ void _StartItem(entity this, entity def, float defaultrespawntime, float default 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)) @@ -1056,7 +1056,7 @@ void _StartItem(entity this, entity def, float defaultrespawntime, float default 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; @@ -1112,18 +1112,21 @@ void _StartItem(entity this, entity def, float defaultrespawntime, float default 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 ); } diff --git a/qcsrc/server/items/spawning.qc b/qcsrc/server/items/spawning.qc index d2f1530c6..cbada5224 100644 --- a/qcsrc/server/items/spawning.qc +++ b/qcsrc/server/items/spawning.qc @@ -132,8 +132,7 @@ void Item_SetExpiring(entity item, bool expiring) // Compatibility spawn functions -// FIXME: in Quake this is green armor, in Xonotic maps it is an armor shard -SPAWNFUNC_ITEM(item_armor1, ITEM_ArmorSmall) +SPAWNFUNC_ITEM_COND(item_armor1, cvar("sv_mapformat_is_quake3"), ITEM_ArmorSmall, ITEM_ArmorMedium) SPAWNFUNC_ITEM(item_armor25, ITEM_ArmorMega) diff --git a/qcsrc/server/main.qc b/qcsrc/server/main.qc index 2546362af..49ff3afc6 100644 --- a/qcsrc/server/main.qc +++ b/qcsrc/server/main.qc @@ -338,7 +338,7 @@ void SV_OnEntityPreSpawnFunction(entity this) return; } - if (DoesQ3ARemoveThisEntity(this)) { + if (q3compat && DoesQ3ARemoveThisEntity(this)) { delete(this); return; } @@ -370,6 +370,62 @@ void SV_OnEntityPreSpawnFunction(entity this) } } +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 diff --git a/qcsrc/server/race.qc b/qcsrc/server/race.qc index bc07efd58..f3ff7cfe5 100644 --- a/qcsrc/server/race.qc +++ b/qcsrc/server/race.qc @@ -789,9 +789,35 @@ bool race_waypointsprite_visible_for_player(entity this, entity player, entity v 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; @@ -809,7 +835,7 @@ void trigger_race_checkpoint_verify(entity this) 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 @@ -817,7 +843,7 @@ void trigger_race_checkpoint_verify(entity this) 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; @@ -825,7 +851,7 @@ void trigger_race_checkpoint_verify(entity this) 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")); - } + } } } } @@ -836,7 +862,7 @@ void trigger_race_checkpoint_verify(entity this) 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; @@ -858,8 +884,8 @@ void trigger_race_checkpoint_verify(entity this) for (entity cp = NULL; (cp = find(cp, classname, "target_checkpoint"));) { if (argv(0) == cp.targetname) { cp.race_checkpoint = stof(argv(1)); - } - } + } + } } fclose(fh); } @@ -867,37 +893,12 @@ void trigger_race_checkpoint_verify(entity this) 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 @@ -908,17 +909,17 @@ void trigger_race_checkpoint_verify(entity this) 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; } @@ -927,14 +928,14 @@ void trigger_race_checkpoint_verify(entity this) { 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") { diff --git a/qcsrc/server/weapons/spawning.qc b/qcsrc/server/weapons/spawning.qc index f555a6ea7..dd664986d 100644 --- a/qcsrc/server/weapons/spawning.qc +++ b/qcsrc/server/weapons/spawning.qc @@ -110,15 +110,24 @@ void weapon_defaultspawnfunc(entity this, Weapon wpn) // if we don't already have ammo, give us some ammo if ((wpn.ammo_type != RES_NONE) && !GetResource(this, wpn.ammo_type)) { - switch (wpn.ammo_type) + int ammo = 0; + if (this.count > 0) + ammo = this.count * GetAmmoConsumptionPrimary(wpn.netname); + // WEAPONTODO: magazines of MG, rifle and OK weapons are unaccounted for + else { - case RES_SHELLS: SetResource(this, wpn.ammo_type, cvar("g_pickup_shells_weapon")); break; - case RES_BULLETS: SetResource(this, wpn.ammo_type, cvar("g_pickup_nails_weapon")); break; - case RES_ROCKETS: SetResource(this, wpn.ammo_type, cvar("g_pickup_rockets_weapon")); break; - case RES_CELLS: SetResource(this, wpn.ammo_type, cvar("g_pickup_cells_weapon")); break; - case RES_PLASMA: SetResource(this, wpn.ammo_type, cvar("g_pickup_plasma_weapon")); break; - case RES_FUEL: SetResource(this, wpn.ammo_type, cvar("g_pickup_fuel_weapon")); break; + switch (wpn.ammo_type) + { + case RES_SHELLS: ammo = cvar("g_pickup_shells_weapon"); break; + case RES_BULLETS: ammo = cvar("g_pickup_nails_weapon"); break; + case RES_ROCKETS: ammo = cvar("g_pickup_rockets_weapon"); break; + case RES_CELLS: ammo = cvar("g_pickup_cells_weapon"); break; + case RES_PLASMA: ammo = cvar("g_pickup_plasma_weapon"); break; + case RES_FUEL: ammo = cvar("g_pickup_fuel_weapon"); break; + } } + + SetResource(this, wpn.ammo_type, ammo); } #if 0 // WEAPONTODO diff --git a/qcsrc/server/world.qc b/qcsrc/server/world.qc index 88890fc2f..b9073ea66 100644 --- a/qcsrc/server/world.qc +++ b/qcsrc/server/world.qc @@ -887,11 +887,8 @@ spawnfunc(worldspawn) 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")) != "") { diff --git a/xonotic-server.cfg b/xonotic-server.cfg index 0825e4dd0..39482b851 100644 --- a/xonotic-server.cfg +++ b/xonotic-server.cfg @@ -496,8 +496,7 @@ sv_gameplayfix_consistentplayerprethink 1 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)"