]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge branch 'master' into bones_was_here/q3compat
authorbones_was_here <bones_was_here@xa.org.au>
Wed, 23 Sep 2020 14:16:56 +0000 (00:16 +1000)
committerbones_was_here <bones_was_here@xa.org.au>
Wed, 23 Sep 2020 14:16:56 +0000 (00:16 +1000)
33 files changed:
qcsrc/common/items/item.qh
qcsrc/common/items/item/ammo.qc
qcsrc/common/items/item/powerup.qh
qcsrc/common/mapobjects/func/button.qc
qcsrc/common/mapobjects/func/door.qc
qcsrc/common/mapobjects/func/plat.qc
qcsrc/common/mapobjects/trigger/hurt.qc
qcsrc/common/mapobjects/trigger/jumppads.qc
qcsrc/common/mapobjects/trigger/multi.qc
qcsrc/common/mutators/mutator/buffs/all.inc
qcsrc/common/mutators/mutator/buffs/buffs.qh
qcsrc/common/physics/player.qc
qcsrc/common/stats.qh
qcsrc/common/weapons/all.qc
qcsrc/common/weapons/weapon.qh
qcsrc/common/weapons/weapon/shockwave.qc
qcsrc/common/weapons/weapon/shotgun.qc
qcsrc/common/weapons/weapon/shotgun.qh
qcsrc/lib/spawnfunc.qh
qcsrc/lib/warpzone/common.qc
qcsrc/server/autocvars.qh
qcsrc/server/client.qc
qcsrc/server/compat/quake.qc
qcsrc/server/compat/quake3.qc
qcsrc/server/compat/quake3.qh
qcsrc/server/items/items.qc
qcsrc/server/items/spawning.qc
qcsrc/server/main.qc
qcsrc/server/miscfunctions.qc
qcsrc/server/race.qc
qcsrc/server/weapons/spawning.qc
qcsrc/server/world.qc
xonotic-server.cfg

index 5c6ec494a3d33bd92536aa5a701d7d8da2971514..26f89cad5f45f598e7f13ae119db9eadab882ae5 100644 (file)
@@ -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); \
index 739d94aca1e8b3fb9954ce8a278570f96c6363cf..48ca0ddab6947f2dd320a96d5644983f4a055ee4 100644 (file)
@@ -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
index bf4727a79fdba674faedc71be9c98414754914a2..921cc0b2f2c23e85610ae4c0f14d1202f6ee16ba 100644 (file)
@@ -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) {
index 423ac5e7b6d927f9fa2f8cdd42cf9797fe6b2f56..c716b400c86985e1260faa8aeaad2f85ab8bbbb1 100644 (file)
@@ -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);
index 11196ab38d7f2bbcce2418a3bfd3f201be84bed4..173c9d9cdec4b23f949d1e44055be6b91a3a065f 100644 (file)
@@ -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;
index 64275a357aaa216e28a34c528da312aaabd441dc..d001fe299dd835821f9815e59aed842b35e551eb 100644 (file)
@@ -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);
index 8c21c509c5643419bb937d1d4170a81d711f12df..20c08feb1c052a7737eeedb602c9744c1795ad97 100644 (file)
@@ -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 == "")
index 5273179adfeb047faeea7f8d7c7ab518cebc9d60..3b9276d6e74683cb3e4a165a2d338928656c6fcc 100644 (file)
@@ -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!
index 9ce5f52cea877d9a1f556c9fa1ee88b06027fe08..0b0efc21c284d1e82cb6f253ccde21d30c6bdc3e 100644 (file)
@@ -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;
index 5b53b3d651c2d76e49ac25e6778fc24a781e7fde..3b3804b84eb5dab7475e7e2e1789e2ed2faa7ea0 100644 (file)
@@ -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)
index 91f56066f61b0f7a9eed9532cdff15c9ffb295a5..c0ccf5803278a2aab53db5accfa180e50d3011ae 100644 (file)
@@ -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);
index 5e9460c83109e4aa399c7199792a63c2cc0a32b8..e238c40c229a1dfd3680993f765ba30639294e8d 100644 (file)
@@ -56,13 +56,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
index cda98d54d73f75b7432ae1769ea3c738a27cf840..ffc0cfacfbf14acb31196cac9e9c3996c1d5e8d2 100644 (file)
@@ -3,6 +3,7 @@
 #ifdef SVQC
 #include <server/autocvars.qh>
 #include <server/client.qh>
+#include <server/compat/quake3.qh>
 #include <common/mapobjects/trigger/secret.qh>
 #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"
index 7c089ba70c096514f3af93df29aecd695b88ecde..5de760d4854fd81422e9112c571723005e896aa5 100644 (file)
@@ -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)
 {
index 67f646cbe5b328dbbb8d7671cbef65db65189fc9..916acdf9ed97c0cc316685a6bab8eadeb8c3cbf5 100644 (file)
@@ -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);
index accb917f687e460c05108fd8fd91f71e756f3ecb..ea476f7d822b89561107a9711f188fc17c88617c 100644 (file)
@@ -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
index dcde58a303554a79794e6c16fd48b0bd8295652b..bc1ce89479b4390b67dcf02ed87fd0468a02a3bf 100644 (file)
@@ -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);
index 1c2d280102af276cdbca8e18baab892ca9910f61..d99a371f34cbcd593aabb50cd789d136ab320e2e 100644 (file)
@@ -56,4 +56,3 @@ CLASS(Shotgun, Weapon)
 ENDCLASS(Shotgun)
 REGISTER_WEAPON(SHOTGUN, shotgun, NEW(Shotgun));
 
-SPAWNFUNC_WEAPON(weapon_shotgun, WEP_SHOTGUN)
index 321610ad3e30b225615283625993ebf959eb621a..939c5a7e5307daa94e54b2346dcf53efa0467345 100644 (file)
@@ -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 */ \
index 830c56de65f0cca4834131760898e93b9a148006..34c7bb2ba8652e959ee2898c66dedb37a42e0e95 100644 (file)
@@ -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);
 }
 
index 83ea702ae729f91ca1465aa2ad5254510bb3ef8b..28d08befe600655e24b7983b580f58db954e8344 100644 (file)
@@ -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;
index 902172cd0d687753e820df8d2b21e2289db16884..22427fa243aa4a6f72a170834fad14c3331de28c 100644 (file)
@@ -621,8 +621,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;
index d2eb785b584fec3a810ad6d6405aff25413bdd96..0d1e8a6e909e53921180976ed3e5858113c1a70a 100644 (file)
@@ -5,15 +5,17 @@
 #include <server/miscfunctions.qh>
 #include <common/weapons/_all.qh>
 
-//***********************
-//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)
index de97cba569b0b7e0f0d86d7d87535a36267e37fc..f850b84b6080cea22b3fdc52860781728b259630 100644 (file)
 #include <common/mapobjects/trigger/counter.qh>
 #include <common/mutators/mutator/buffs/buffs.qh>
 #include <common/notifications/all.qh>
-#include <common/weapons/_all.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)
@@ -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;
        }
index 20e4879d9a37596290a2a24258a7d69db623e21a..d6ca4fd55f7948ba50eee7b8fa637368b1174c41 100644 (file)
@@ -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)
+
index 861b21eed2b566df931eef6a382f4b848139ec7c..84e0e62cd4f197d1335f36e5094061841643d73a 100644 (file)
@@ -1026,19 +1026,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))
@@ -1055,7 +1055,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;
@@ -1111,18 +1111,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
        );
 }
 
index d2f1530c652bca617368eef6a877b52febe07d62..cbada5224053c523f0b7b5ff547fa04de738e707 100644 (file)
@@ -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)
 
index 0983d4621fad6365d74865f7b070607658efa9c3..3859ffa6f7e0b750452b5098db1fcf1232fa4bc2 100644 (file)
@@ -336,7 +336,7 @@ void SV_OnEntityPreSpawnFunction(entity this)
                return;
        }
 
-       if (DoesQ3ARemoveThisEntity(this)) {
+       if (q3compat && DoesQ3ARemoveThisEntity(this)) {
                delete(this);
                return;
        }
index b748c17a00ccd96fd25ac4efc93f2ee242d7e1a0..7da78ca0212baa6274886ec256f69b53ec00a8b6 100644 (file)
@@ -1329,3 +1329,59 @@ float LostMovetypeFollow(entity ent)
        }
        return 0;
 }
+
+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;
+}
index 5014e02aedb0836af94e33a7405f58c2899a764e..0c5e93954418498d0dc0ae0aed2f2d3659e952f4 100644 (file)
@@ -755,9 +755,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;
 
@@ -775,7 +801,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
@@ -783,7 +809,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;
@@ -791,7 +817,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"));
-                    }
+                                       }
                                }
                        }
                }
@@ -802,7 +828,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;
@@ -824,8 +850,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);
                }
@@ -833,37 +859,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
@@ -874,17 +875,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;
                        }
@@ -893,14 +894,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") {
index 63bc865e29dc47bf7404474dc9234365f08999bd..ec9952fff27039ee857fdc9b036ffbc6d65618e0 100644 (file)
@@ -109,15 +109,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
index 329aab0876c3e337fb53ae93476ff0c35be4d188..452d172a9997b4dd56109737a0452819501b3ce5 100644 (file)
@@ -886,11 +886,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")) != "")
        {
index 0825e4dd086fd1df32316458926d14d223b99fb2..39482b8518d14d6efbbfd9651f9a20bbf08e9a6a 100644 (file)
@@ -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)"