]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/server/sv_main.qc
Merge branch 'martin-t/cvar-prefixes' into 'master'
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / sv_main.qc
index dd6ad7260f19eb41232ab4a31987ac264d42cca9..9be3c18f6017183f8d81319e0cdb75e4a2d16e51 100644 (file)
@@ -1,15 +1,17 @@
+#include "sv_main.qh"
 
 #include "anticheat.qh"
 #include "g_hook.qh"
+#include "g_damage.qh"
 #include "g_world.qh"
 
-#include "bot/bot.qh"
-#include "bot/waypoints.qh"
+#include "bot/api.qh"
 
 #include "command/common.qh"
 
-#include "mutators/all.qh"
+#include <server/mutators/_mod.qh>
 #include "weapons/csqcprojectile.qh"
+#include <server/compat/quake3.qh>
 
 #include "../common/constants.qh"
 #include "../common/deathtypes/all.qh"
@@ -18,7 +20,8 @@
 #include "../common/util.qh"
 
 #include "../common/vehicles/all.qh"
-#include "../common/weapons/all.qh"
+#include <common/monsters/sv_monsters.qh>
+#include <common/weapons/_all.qh>
 
 #include "../lib/csqcmodel/sv_model.qh"
 
 .float lastground;
 .int state;
 
-void CreatureFrame ()
-{SELFPARAM();
-       float dm;
-
-       for(entity e = world; (e = findfloat(e, damagedbycontents, true)); )
+void CreatureFrame_hotliquids(entity this)
+{
+       if (this.dmgtime < time)
        {
-               setself(e);
-               if (self.movetype == MOVETYPE_NOCLIP) { continue; }
+               this.dmgtime = time + autocvar_g_balance_contents_damagerate;
 
-               float vehic = IS_VEHICLE(self);
-               float projectile = (self.flags & FL_PROJECTILE);
-               float monster = IS_MONSTER(self);
-
-               if (self.watertype <= CONTENT_WATER && self.waterlevel > 0) // workaround a retarded bug made by id software :P (yes, it's that old of a bug)
+               if (this.flags & FL_PROJECTILE)
                {
-                       if (!(self.flags & FL_INWATER))
-                       {
-                               self.flags |= FL_INWATER;
-                               self.dmgtime = 0;
-                       }
-
-                       if(!vehic && !projectile && !monster) // vehicles, monsters and projectiles don't drown
+                       if (this.watertype == CONTENT_LAVA)
+                               Damage (this, NULL, NULL, autocvar_g_balance_contents_projectiledamage * autocvar_g_balance_contents_damagerate * this.waterlevel, DEATH_LAVA.m_id, DMG_NOWEP, this.origin, '0 0 0');
+                       else if (this.watertype == CONTENT_SLIME)
+                               Damage (this, NULL, NULL, autocvar_g_balance_contents_projectiledamage * autocvar_g_balance_contents_damagerate * this.waterlevel, DEATH_SLIME.m_id, DMG_NOWEP, this.origin, '0 0 0');
+               }
+               else
+               {
+                       if (this.watertype == CONTENT_LAVA)
                        {
-                               if (self.waterlevel != WATERLEVEL_SUBMERGED)
+                               if (this.watersound_finished < time)
                                {
-                                       if(self.air_finished < time)
-                                               PlayerSound(self, playersound_gasp, CH_PLAYER, VOICETYPE_PLAYERSOUND);
-                                       self.air_finished = time + autocvar_g_balance_contents_drowndelay;
-                                       self.dmg = 2;
-                               }
-                               else if (self.air_finished < time)
-                               {       // drown!
-                                       if (!self.deadflag)
-                                       if (self.pain_finished < time)
-                                       {
-                                               Damage (self, world, world, autocvar_g_balance_contents_playerdamage_drowning * autocvar_g_balance_contents_damagerate, DEATH_DROWN.m_id, self.origin, '0 0 0');
-                                               self.pain_finished = time + 0.5;
-                                       }
+                                       this.watersound_finished = time + 0.5;
+                                       sound (this, CH_PLAYER_SINGLE, SND_LAVA, VOL_BASE, ATTEN_NORM);
                                }
+                               Damage (this, NULL, NULL, autocvar_g_balance_contents_playerdamage_lava * autocvar_g_balance_contents_damagerate * this.waterlevel, DEATH_LAVA.m_id, DMG_NOWEP, this.origin, '0 0 0');
+                               if(autocvar_g_balance_contents_playerdamage_lava_burn)
+                                       Fire_AddDamage(this, NULL, autocvar_g_balance_contents_playerdamage_lava_burn * this.waterlevel, autocvar_g_balance_contents_playerdamage_lava_burn_time * this.waterlevel, DEATH_LAVA.m_id);
                        }
-
-                       if (self.dmgtime < time)
+                       else if (this.watertype == CONTENT_SLIME)
                        {
-                               self.dmgtime = time + autocvar_g_balance_contents_damagerate;
-
-                               if (projectile)
+                               if (this.watersound_finished < time)
                                {
-                                       if (self.watertype == CONTENT_LAVA)
-                                       {
-                                               Damage (self, world, world, autocvar_g_balance_contents_projectiledamage * autocvar_g_balance_contents_damagerate * self.waterlevel, DEATH_LAVA.m_id, self.origin, '0 0 0');
-                                       }
-                                       else if (self.watertype == CONTENT_SLIME)
-                                       {
-                                               Damage (self, world, world, autocvar_g_balance_contents_projectiledamage * autocvar_g_balance_contents_damagerate * self.waterlevel, DEATH_SLIME.m_id, self.origin, '0 0 0');
-                                       }
-                               }
-                               else
-                               {
-                                       if (self.watertype == CONTENT_LAVA)
-                                       {
-                                               if (self.watersound_finished < time)
-                                               {
-                                                       self.watersound_finished = time + 0.5;
-                                                       sound (self, CH_PLAYER_SINGLE, SND_LAVA, VOL_BASE, ATTEN_NORM);
-                                               }
-                                               Damage (self, world, world, autocvar_g_balance_contents_playerdamage_lava * autocvar_g_balance_contents_damagerate * self.waterlevel, DEATH_LAVA.m_id, self.origin, '0 0 0');
-                                               if(autocvar_g_balance_contents_playerdamage_lava_burn)
-                                                       Fire_AddDamage(self, world, autocvar_g_balance_contents_playerdamage_lava_burn * self.waterlevel, autocvar_g_balance_contents_playerdamage_lava_burn_time * self.waterlevel, DEATH_LAVA.m_id);
-                                       }
-                                       else if (self.watertype == CONTENT_SLIME)
-                                       {
-                                               if (self.watersound_finished < time)
-                                               {
-                                                       self.watersound_finished = time + 0.5;
-                                                       sound (self, CH_PLAYER_SINGLE, SND_SLIME, VOL_BASE, ATTEN_NORM);
-                                               }
-                                               Damage (self, world, world, autocvar_g_balance_contents_playerdamage_slime * autocvar_g_balance_contents_damagerate * self.waterlevel, DEATH_SLIME.m_id, self.origin, '0 0 0');
-                                       }
+                                       this.watersound_finished = time + 0.5;
+                                       sound (this, CH_PLAYER_SINGLE, SND_SLIME, VOL_BASE, ATTEN_NORM);
                                }
+                               Damage (this, NULL, NULL, autocvar_g_balance_contents_playerdamage_slime * autocvar_g_balance_contents_damagerate * this.waterlevel, DEATH_SLIME.m_id, DMG_NOWEP, this.origin, '0 0 0');
                        }
                }
-               else
+       }
+}
+
+void CreatureFrame_Liquids(entity this)
+{
+       if (this.watertype <= CONTENT_WATER && this.waterlevel > 0) // workaround a retarded bug made by id software :P (yes, it's that old of a bug)
+       {
+               if (!(this.flags & FL_INWATER))
                {
-                       if (self.flags & FL_INWATER)
-                       {
-                               // play leave water sound
-                               self.flags &= ~FL_INWATER;
-                               self.dmgtime = 0;
-                       }
-                       self.air_finished = time + 12;
-                       self.dmg = 2;
+                       this.flags |= FL_INWATER;
+                       this.dmgtime = 0;
                }
 
-               if(!vehic && !projectile) // vehicles don't get falling damage
+               CreatureFrame_hotliquids(this);
+       }
+       else
+       {
+               if (this.flags & FL_INWATER)
                {
-                       // check for falling damage
-                       float velocity_len = vlen(self.velocity);
-                       if(!self.hook.state)
-                       {
-                               dm = vlen(self.oldvelocity) - velocity_len; // dm is now the velocity DECREASE. Velocity INCREASE should never cause a sound or any damage.
-                               if (self.deadflag)
-                                       dm = (dm - autocvar_g_balance_falldamage_deadminspeed) * autocvar_g_balance_falldamage_factor;
-                               else
-                                       dm = min((dm - autocvar_g_balance_falldamage_minspeed) * autocvar_g_balance_falldamage_factor, autocvar_g_balance_falldamage_maxdamage);
-                               if (dm > 0)
-                                       Damage (self, world, world, dm, DEATH_FALL.m_id, self.origin, '0 0 0');
-                       }
+                       // play leave water sound
+                       this.flags &= ~FL_INWATER;
+                       this.dmgtime = 0;
+               }
+               this.air_finished = time + 12;
+       }
+}
 
-                       if(autocvar_g_maxspeed > 0 && velocity_len > autocvar_g_maxspeed)
-                               Damage (self, world, world, 100000, DEATH_SHOOTING_STAR.m_id, self.origin, '0 0 0');
-                       // play stupid sounds
-                       if (g_footsteps)
-                       if (!gameover)
-                       if (self.flags & FL_ONGROUND)
-                       if (!self.crouch)
-                       if (velocity_len > autocvar_sv_maxspeed * 0.6)
-                       if (!self.deadflag)
-                       if (time < self.lastground + 0.2)
-                       {
-                               if((time > self.nextstep) || (time < (self.nextstep - 10.0)))
-                               {
-                                       self.nextstep = time + 0.3 + random() * 0.1;
-                                       trace_dphitq3surfaceflags = 0;
-                                       tracebox(self.origin, self.mins, self.maxs, self.origin - '0 0 1', MOVE_NOMONSTERS, self);
-                                       /*
-                                       if(trace_fraction == 1)
-                                               dprint("nohit\n");
-                                       else
-                                               dprint(ftos(trace_dphitq3surfaceflags), "\n");
-                                       */
-                                       if (!(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOSTEPS))
-                                       {
-                                               if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_METALSTEPS)
-                                                       GlobalSound(self, GS_STEP_METAL, CH_PLAYER, VOICETYPE_PLAYERSOUND);
-                                               else
-                                                       GlobalSound(self, GS_STEP, CH_PLAYER, VOICETYPE_PLAYERSOUND);
-                                       }
-                               }
-                       }
+void CreatureFrame_FallDamage(entity this)
+{
+       if(!IS_VEHICLE(this) && !(this.flags & FL_PROJECTILE)) // vehicles don't get falling damage
+       if(this.velocity || this.oldvelocity) // moving or has moved
+       {
+               // check for falling damage
+               float velocity_len = vlen(this.velocity);
+               bool have_hook = false;
+               for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
+           {
+               .entity weaponentity = weaponentities[slot];
+               if(this.(weaponentity).hook && this.(weaponentity).hook.state)
+               {
+                       have_hook = true;
+                       break;
+               }
+           }
+               if(!have_hook)
+               {
+                       float dm = vlen(this.oldvelocity) - velocity_len; // dm is now the velocity DECREASE. Velocity INCREASE should never cause a sound or any damage.
+                       if (IS_DEAD(this))
+                               dm = (dm - autocvar_g_balance_falldamage_deadminspeed) * autocvar_g_balance_falldamage_factor;
+                       else
+                               dm = min((dm - autocvar_g_balance_falldamage_minspeed) * autocvar_g_balance_falldamage_factor, autocvar_g_balance_falldamage_maxdamage);
+                       if (dm > 0)
+                               Damage (this, NULL, NULL, dm, DEATH_FALL.m_id, DMG_NOWEP, this.origin, '0 0 0');
                }
 
-        self.oldvelocity = self.velocity;
+               if(autocvar_g_maxspeed > 0 && velocity_len > autocvar_g_maxspeed)
+                       Damage (this, NULL, NULL, 100000, DEATH_SHOOTING_STAR.m_id, DMG_NOWEP, this.origin, '0 0 0');
        }
-       setself(this);
 }
 
+void CreatureFrame_All()
+{
+       if(game_stopped || time < game_starttime)
+               return;
+
+       IL_EACH(g_damagedbycontents, it.damagedbycontents,
+       {
+               if (it.move_movetype == MOVETYPE_NOCLIP) continue;
+               CreatureFrame_Liquids(it);
+               CreatureFrame_FallDamage(it);
+               it.oldvelocity = it.velocity;
+       });
+}
+
+void Pause_TryPause(bool ispaused)
+{
+       int n = 0;
+       FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), {
+               if (PHYS_INPUT_BUTTON_CHAT(it) != ispaused) return;
+               ++n;
+       });
+       if (!n) return;
+       setpause(ispaused);
+}
+
+void SV_PausedTic(float elapsedtime)
+{
+       if (!server_is_dedicated) Pause_TryPause(false);
+}
 
 /*
 =============
@@ -183,16 +165,21 @@ Called before each frame by the server
 =============
 */
 
-float game_delay;
-float game_delay_last;
+bool game_delay_last;
 
-float RedirectionThink();
+bool autocvar_sv_autopause = false;
+void systems_update();
+void sys_phys_update(entity this, float dt);
 void StartFrame()
 {
-       SELFPARAM();
+    // TODO: if move is more than 50ms, split it into two moves (this matches QWSV behavior and the client prediction)
+    IL_EACH(g_players, IS_FAKE_CLIENT(it), sys_phys_update(it, frametime));
+    IL_EACH(g_players, IS_FAKE_CLIENT(it), PlayerPreThink(it));
+
        execute_next_frame();
+       if (autocvar_sv_autopause && !server_is_dedicated) Pause_TryPause(true);
 
-       remove = remove_unsafely; // not during spawning!
+       delete_fn = remove_unsafely; // not during spawning!
        serverprevtime = servertime;
        servertime = time;
        serverframetime = frametime;
@@ -201,30 +188,27 @@ void StartFrame()
        if(time > client_cefc_accumulatortime + 1)
        {
                float t = client_cefc_accumulator / (time - client_cefc_accumulatortime);
-               LOG_INFO("CEFC time: ", ftos(t * 1000), "ms; ");
                int c_seeing = 0;
                int c_seen = 0;
-               entity cl;
-               FOR_EACH_CLIENT(cl)
-               {
-                       if(IS_REAL_CLIENT(cl))
+               FOREACH_CLIENT(true, {
+                       if(IS_REAL_CLIENT(it))
                                ++c_seeing;
-                       if(IS_PLAYER(cl))
+                       if(IS_PLAYER(it))
                                ++c_seen;
-               }
-               LOG_INFO("CEFC calls per second: ", ftos(c_seeing * (c_seen - 1) / t), "; ");
-               LOG_INFO("CEFC 100% load at: ", ftos(solve_quadratic(t, -t, -1) * '0 1 0'), "\n");
-
+               });
+               LOG_INFO(
+                   "CEFC time: ", ftos(t * 1000), "ms; ",
+            "CEFC calls per second: ", ftos(c_seeing * (c_seen - 1) / t), "; ",
+            "CEFC 100% load at: ", ftos(solve_quadratic(t, -t, -1) * '0 1 0')
+        );
                client_cefc_accumulatortime = time;
                client_cefc_accumulator = 0;
        }
 #endif
 
-       for(entity e = world; (e = findfloat(e, csqcprojectile_clientanimate, 1)); )
-               CSQCProjectile_Check(e);
+       IL_EACH(g_projectiles, it.csqcprojectile_clientanimate, CSQCProjectile_Check(it));
 
-       if(RedirectionThink())
-               return;
+       if (RedirectionThink()) return;
 
        UncustomizeEntitiesRun();
        InitializeEntitiesRun();
@@ -232,31 +216,21 @@ void StartFrame()
        WarpZone_StartFrame();
 
        sys_frametime = autocvar_sys_ticrate * autocvar_slowmo;
-       if(sys_frametime <= 0)
-               sys_frametime = 1.0 / 60.0; // somewhat safe fallback
+       if (sys_frametime <= 0) sys_frametime = 1.0 / 60.0; // somewhat safe fallback
 
        if (timeout_status == TIMEOUT_LEADTIME) // just before the timeout (when timeout_status will be TIMEOUT_ACTIVE)
                orig_slowmo = autocvar_slowmo; // slowmo will be restored after the timeout
 
-       skill = autocvar_skill;
-
        // detect when the pre-game countdown (if any) has ended and the game has started
-       game_delay = (time < game_starttime);
-
-       if(autocvar_sv_eventlog && game_delay_last && !game_delay)
+       bool game_delay = (time < game_starttime);
+       if (autocvar_sv_eventlog && game_delay_last && !game_delay)
                GameLogEcho(":startdelay_ended");
-
        game_delay_last = game_delay;
 
-       CreatureFrame();
+       CreatureFrame_All();
        CheckRules_World();
 
-       // if in warmup stage and limit for warmup is hit start match
-       if(warmup_stage)
-       if(!gameover)
-       if((g_warmup_limit > 0 && time >= g_warmup_limit)
-        || (g_warmup_limit == 0 && autocvar_timelimit != 0 && time >= autocvar_timelimit * 60))
-       {
+       if (warmup_stage && !game_stopped && warmup_limit > 0 && time >= warmup_limit) {
                ReadyRestart();
                return;
        }
@@ -264,13 +238,9 @@ void StartFrame()
        bot_serverframe();
        anticheat_startframe();
        MUTATOR_CALLHOOK(SV_StartFrame);
-       {
-        entity e;
-        FOR_EACH_CLIENT(e)
-        {
-            GlobalStats_update(e);
-        }
-    }
+
+    FOREACH_CLIENT(true, GlobalStats_update(it));
+    IL_EACH(g_players, IS_FAKE_CLIENT(it), PlayerPostThink(it));
 }
 
 .vector originjitter;
@@ -278,173 +248,141 @@ void StartFrame()
 .float anglejitter;
 .string gametypefilter;
 .string cvarfilter;
-float DoesQ3ARemoveThisEntity();
-void SV_OnEntityPreSpawnFunction()
-{SELFPARAM();
-       if (self)
-       if (self.gametypefilter != "")
-       if (!isGametypeInFilter(MapInfo_LoadedGametype, teamplay, have_team_spawns, self.gametypefilter))
-       {
-               remove(self);
-               return;
-       }
-       if(self.cvarfilter != "")
-       {
-               float n, i, o, inv;
-               string s, k, v;
-               inv = 0;
 
-               s = self.cvarfilter;
-               if(substring(s, 0, 1) == "+")
-               {
-                       s = substring(s, 1, -1);
-               }
-               else if(substring(s, 0, 1) == "-")
-               {
-                       inv = 1;
-                       s = substring(s, 1, -1);
-               }
+/**
+ * Evaluate an expression of the form: [+ | -]? [var[op]val | [op]var | val | var] ...
+ * +: all must match. this is the default
+ * -: one must NOT match
+ *
+ * var>x
+ * var<x
+ * var>=x
+ * var<=x
+ * var==x
+ * var!=x
+ * var===x
+ * var!==x
+ */
+bool expr_evaluate(string s)
+{
+    bool ret = false;
+    if (str2chr(s, 0) == '+') {
+        s = substring(s, 1, -1);
+    } else if (str2chr(s, 0) == '-') {
+        ret = true;
+        s = substring(s, 1, -1);
+    }
+    bool expr_fail = false;
+    for (int i = 0, n = tokenize_console(s); i < n; ++i) {
+        int o;
+        string k, v;
+        s = argv(i);
+        #define X(expr) \
+            if (expr) \
+                continue; \
+            expr_fail = true; \
+            break;
+
+        #define BINOP(op, len, expr) \
+            if ((o = strstrofs(s, op, 0)) >= 0) { \
+                k = substring(s, 0, o); \
+                v = substring(s, o + len, -1); \
+                X(expr); \
+            }
+        BINOP(">=", 2, cvar(k) >= stof(v));
+        BINOP("<=", 2, cvar(k) <= stof(v));
+        BINOP(">",  1, cvar(k) >  stof(v));
+        BINOP("<",  1, cvar(k) <  stof(v));
+        BINOP("==", 2, cvar(k) == stof(v));
+        BINOP("!=", 2, cvar(k) != stof(v));
+        BINOP("===", 3, cvar_string(k) == v);
+        BINOP("!==", 3, cvar_string(k) != v);
+        {
+            k = s;
+            bool b = true;
+            if (str2chr(k, 0) == '!') {
+                k = substring(s, 1, -1);
+                b = false;
+            }
+            float f = stof(k);
+            bool isnum = ftos(f) == k;
+            X(boolean(isnum ? f : cvar(k)) == b);
+        }
+        #undef BINOP
+        #undef X
+    }
+    if (!expr_fail) {
+        ret = !ret;
+    }
+    // now ret is true if we want to keep the item, and false if we want to get rid of it
+    return ret;
+}
 
-               n = tokenize_console(s);
-               for(i = 0; i < n; ++i)
-               {
-                       s = argv(i);
-                       // syntax:
-                       // var>x
-                       // var<x
-                       // var>=x
-                       // var<=x
-                       // var==x
-                       // var!=x
-                       // var===x
-                       // var!==x
-                       if((o = strstrofs(s, ">=", 0)) >= 0)
-                       {
-                               k = substring(s, 0, o);
-                               v = substring(s, o+2, -1);
-                               if(cvar(k) < stof(v))
-                                       goto cvar_fail;
-                       }
-                       else if((o = strstrofs(s, "<=", 0)) >= 0)
-                       {
-                               k = substring(s, 0, o);
-                               v = substring(s, o+2, -1);
-                               if(cvar(k) > stof(v))
-                                       goto cvar_fail;
-                       }
-                       else if((o = strstrofs(s, ">", 0)) >= 0)
-                       {
-                               k = substring(s, 0, o);
-                               v = substring(s, o+1, -1);
-                               if(cvar(k) <= stof(v))
-                                       goto cvar_fail;
-                       }
-                       else if((o = strstrofs(s, "<", 0)) >= 0)
-                       {
-                               k = substring(s, 0, o);
-                               v = substring(s, o+1, -1);
-                               if(cvar(k) >= stof(v))
-                                       goto cvar_fail;
-                       }
-                       else if((o = strstrofs(s, "==", 0)) >= 0)
-                       {
-                               k = substring(s, 0, o);
-                               v = substring(s, o+2, -1);
-                               if(cvar(k) != stof(v))
-                                       goto cvar_fail;
-                       }
-                       else if((o = strstrofs(s, "!=", 0)) >= 0)
-                       {
-                               k = substring(s, 0, o);
-                               v = substring(s, o+2, -1);
-                               if(cvar(k) == stof(v))
-                                       goto cvar_fail;
-                       }
-                       else if((o = strstrofs(s, "===", 0)) >= 0)
-                       {
-                               k = substring(s, 0, o);
-                               v = substring(s, o+2, -1);
-                               if(cvar_string(k) != v)
-                                       goto cvar_fail;
-                       }
-                       else if((o = strstrofs(s, "!==", 0)) >= 0)
-                       {
-                               k = substring(s, 0, o);
-                               v = substring(s, o+2, -1);
-                               if(cvar_string(k) == v)
-                                       goto cvar_fail;
-                       }
-                       else if(substring(s, 0, 1) == "!")
-                       {
-                               k = substring(s, 1, -1);
-                               if(cvar(k))
-                                       goto cvar_fail;
-                       }
-                       else
-                       {
-                               k = s;
-                               if (!cvar(k))
-                                       goto cvar_fail;
-                       }
-               }
-               inv = !inv;
-:cvar_fail
-               // now inv is 1 if we want to keep the item, and 0 if we want to get rid of it
-               if (!inv)
-               {
-                       //print("cvarfilter fail\n");
-                       remove(self);
-                       return;
-               }
+void SV_OnEntityPreSpawnFunction(entity this)
+{
+       if (this)
+       if (this.gametypefilter != "")
+       if (!isGametypeInFilter(MapInfo_LoadedGametype, teamplay, have_team_spawns, this.gametypefilter))
+       {
+               goto cleanup;
+       }
+       if (this.cvarfilter != "" && !expr_evaluate(this.cvarfilter)) {
+        goto cleanup;
        }
 
-       if(DoesQ3ARemoveThisEntity())
-       {
-               remove(self);
-               return;
+       if (DoesQ3ARemoveThisEntity(this)) {
+               goto cleanup;
        }
 
+       set_movetype(this, this.movetype);
+
+       if (this.monster_attack) {
+               IL_PUSH(g_monster_targets, this);
+    }
+
        // support special -1 and -2 angle from radiant
-       if (self.angles == '0 -1 0')
-               self.angles = '-90 0 0';
-       else if (self.angles == '0 -2 0')
-               self.angles = '+90 0 0';
-
-       if(self.originjitter.x != 0)
-               self.origin_x = self.origin.x + (random() * 2 - 1) * self.originjitter.x;
-       if(self.originjitter.y != 0)
-               self.origin_y = self.origin.y + (random() * 2 - 1) * self.originjitter.y;
-       if(self.originjitter.z != 0)
-               self.origin_z = self.origin.z + (random() * 2 - 1) * self.originjitter.z;
-       if(self.anglesjitter.x != 0)
-               self.angles_x = self.angles.x + (random() * 2 - 1) * self.anglesjitter.x;
-       if(self.anglesjitter.y != 0)
-               self.angles_y = self.angles.y + (random() * 2 - 1) * self.anglesjitter.y;
-       if(self.anglesjitter.z != 0)
-               self.angles_z = self.angles.z + (random() * 2 - 1) * self.anglesjitter.z;
-       if(self.anglejitter != 0)
-               self.angles_y = self.angles.y + (random() * 2 - 1) * self.anglejitter;
-
-       if(MUTATOR_CALLHOOK(OnEntityPreSpawn))
-       {
-               remove(self);
-               return;
+       if (this.angles == '0 -1 0') {
+               this.angles = '-90 0 0';
+       } else if (this.angles == '0 -2 0') {
+               this.angles = '+90 0 0';
+    }
+
+    #define X(out, in) MACRO_BEGIN \
+        if (in != 0) { out = out + (random() * 2 - 1) * in; } \
+    MACRO_END
+    X(this.origin.x, this.originjitter.x); X(this.origin.y, this.originjitter.y); X(this.origin.z, this.originjitter.z);
+    X(this.angles.x, this.anglesjitter.x); X(this.angles.y, this.anglesjitter.y); X(this.angles.z, this.anglesjitter.z);
+    X(this.angles.y, this.anglejitter);
+    #undef X
+
+       if (MUTATOR_CALLHOOK(OnEntityPreSpawn, this)) {
+               goto cleanup;
        }
+       return;
+LABEL(cleanup)
+    delete(this);
 }
 
 void WarpZone_PostInitialize_Callback()
 {
        // create waypoint links for warpzones
-       entity e;
-       for(e = world; (e = find(e, classname, "trigger_warpzone")); )
-       {
-               vector src, dst;
-               src = (e.absmin + e.absmax) * 0.5;
-               makevectors(e.warpzone_angles);
-               src = src + ((e.warpzone_origin - src) * v_forward) * v_forward + 16 * v_right;
-               dst = (e.enemy.absmin + e.enemy.absmax) * 0.5;
-               makevectors(e.enemy.warpzone_angles);
-               dst = dst + ((e.enemy.warpzone_origin - dst) * v_forward) * v_forward - 16 * v_right;
-               waypoint_spawnforteleporter_v(e, src, dst, 0);
-       }
+       entity tracetest_ent = spawn();
+       setsize(tracetest_ent, PL_MIN_CONST, PL_MAX_CONST);
+       tracetest_ent.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
+       //for(entity e = warpzone_first; e; e = e.warpzone_next)
+       for(entity e = NULL; (e = find(e, classname, "trigger_warpzone")); )
+               waypoint_spawnforteleporter_wz(e, tracetest_ent);
+       delete(tracetest_ent);
+}
+
+/*
+==================
+main
+
+unused but required by the engine
+==================
+*/
+void main ()
+{
+
 }