]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge branch 'master' into Mario/qc_physics_prehax
authorMario <zacjardine@y7mail.com>
Mon, 16 Mar 2015 14:00:45 +0000 (01:00 +1100)
committerMario <zacjardine@y7mail.com>
Mon, 16 Mar 2015 14:00:45 +0000 (01:00 +1100)
192 files changed:
defaultXonotic.cfg
physicsX.cfg
physicsX010.cfg
physicsX07.cfg
physicsXDF.cfg
physicsXDFLight.cfg
qcsrc/client/autocvars.qh
qcsrc/client/bgmscript.qc
qcsrc/client/bgmscript.qh
qcsrc/client/casings.qc
qcsrc/client/command/cl_cmd.qc
qcsrc/client/damage.qc
qcsrc/client/effects.qc
qcsrc/client/laser.qc [deleted file]
qcsrc/client/main.qc
qcsrc/client/miscfunctions.qc
qcsrc/client/miscfunctions.qh
qcsrc/client/movetypes.qc [deleted file]
qcsrc/client/movetypes.qh [deleted file]
qcsrc/client/particles.qc
qcsrc/client/particles.qh
qcsrc/client/player_skeleton.qc
qcsrc/client/progs.src
qcsrc/client/target_music.qc [deleted file]
qcsrc/client/target_music.qh [deleted file]
qcsrc/client/vehicles/vehicles.qc
qcsrc/client/view.qc
qcsrc/client/waypointsprites.qc
qcsrc/client/weapons/projectile.qc
qcsrc/common/constants.qh
qcsrc/common/csqcmodel_settings.qh
qcsrc/common/monsters/sv_monsters.qc
qcsrc/common/movetypes/include.qc [new file with mode: 0644]
qcsrc/common/movetypes/include.qh [new file with mode: 0644]
qcsrc/common/movetypes/movetypes.qc [new file with mode: 0644]
qcsrc/common/movetypes/movetypes.qh [new file with mode: 0644]
qcsrc/common/movetypes/push.qc [new file with mode: 0644]
qcsrc/common/movetypes/push.qh [new file with mode: 0644]
qcsrc/common/movetypes/toss.qc [new file with mode: 0644]
qcsrc/common/movetypes/toss.qh [new file with mode: 0644]
qcsrc/common/nades.qc
qcsrc/common/net_notice.qc
qcsrc/common/physics.qc [new file with mode: 0644]
qcsrc/common/physics.qh [new file with mode: 0644]
qcsrc/common/stats.qh
qcsrc/common/triggers/func/bobbing.qc [new file with mode: 0644]
qcsrc/common/triggers/func/breakable.qc [new file with mode: 0644]
qcsrc/common/triggers/func/breakable.qh [new file with mode: 0644]
qcsrc/common/triggers/func/button.qc [new file with mode: 0644]
qcsrc/common/triggers/func/conveyor.qc [new file with mode: 0644]
qcsrc/common/triggers/func/conveyor.qh [new file with mode: 0644]
qcsrc/common/triggers/func/door.qc [new file with mode: 0644]
qcsrc/common/triggers/func/door.qh [new file with mode: 0644]
qcsrc/common/triggers/func/door_rotating.qc [new file with mode: 0644]
qcsrc/common/triggers/func/door_secret.qc [new file with mode: 0644]
qcsrc/common/triggers/func/fourier.qc [new file with mode: 0644]
qcsrc/common/triggers/func/include.qc [new file with mode: 0644]
qcsrc/common/triggers/func/include.qh [new file with mode: 0644]
qcsrc/common/triggers/func/ladder.qc [new file with mode: 0644]
qcsrc/common/triggers/func/ladder.qh [new file with mode: 0644]
qcsrc/common/triggers/func/pendulum.qc [new file with mode: 0644]
qcsrc/common/triggers/func/plat.qc [new file with mode: 0644]
qcsrc/common/triggers/func/plat.qh [new file with mode: 0644]
qcsrc/common/triggers/func/pointparticles.qc [new file with mode: 0644]
qcsrc/common/triggers/func/pointparticles.qh [new file with mode: 0644]
qcsrc/common/triggers/func/rainsnow.qc [new file with mode: 0644]
qcsrc/common/triggers/func/rainsnow.qh [new file with mode: 0644]
qcsrc/common/triggers/func/rotating.qc [new file with mode: 0644]
qcsrc/common/triggers/func/stardust.qc [new file with mode: 0644]
qcsrc/common/triggers/func/train.qc [new file with mode: 0644]
qcsrc/common/triggers/func/train.qh [new file with mode: 0644]
qcsrc/common/triggers/func/vectormamamam.qc [new file with mode: 0644]
qcsrc/common/triggers/include.qc [new file with mode: 0644]
qcsrc/common/triggers/include.qh [new file with mode: 0644]
qcsrc/common/triggers/misc/corner.qc [new file with mode: 0644]
qcsrc/common/triggers/misc/corner.qh [new file with mode: 0644]
qcsrc/common/triggers/misc/follow.qc [new file with mode: 0644]
qcsrc/common/triggers/misc/include.qc [new file with mode: 0644]
qcsrc/common/triggers/misc/include.qh [new file with mode: 0644]
qcsrc/common/triggers/misc/laser.qc [new file with mode: 0644]
qcsrc/common/triggers/misc/laser.qh [new file with mode: 0644]
qcsrc/common/triggers/misc/teleport_dest.qc [new file with mode: 0644]
qcsrc/common/triggers/platforms.qc [new file with mode: 0644]
qcsrc/common/triggers/platforms.qh [new file with mode: 0644]
qcsrc/common/triggers/subs.qc [new file with mode: 0644]
qcsrc/common/triggers/subs.qh [new file with mode: 0644]
qcsrc/common/triggers/target/changelevel.qc [new file with mode: 0644]
qcsrc/common/triggers/target/include.qc [new file with mode: 0644]
qcsrc/common/triggers/target/include.qh [new file with mode: 0644]
qcsrc/common/triggers/target/location.qc [new file with mode: 0644]
qcsrc/common/triggers/target/music.qc [new file with mode: 0644]
qcsrc/common/triggers/target/music.qh [new file with mode: 0644]
qcsrc/common/triggers/target/spawn.qc [new file with mode: 0644]
qcsrc/common/triggers/target/speaker.qc [new file with mode: 0644]
qcsrc/common/triggers/target/voicescript.qc [new file with mode: 0644]
qcsrc/common/triggers/teleporters.qc [new file with mode: 0644]
qcsrc/common/triggers/teleporters.qh [new file with mode: 0644]
qcsrc/common/triggers/trigger/counter.qc [new file with mode: 0644]
qcsrc/common/triggers/trigger/delay.qc [new file with mode: 0644]
qcsrc/common/triggers/trigger/disablerelay.qc [new file with mode: 0644]
qcsrc/common/triggers/trigger/flipflop.qc [new file with mode: 0644]
qcsrc/common/triggers/trigger/gamestart.qc [new file with mode: 0644]
qcsrc/common/triggers/trigger/gravity.qc [new file with mode: 0644]
qcsrc/common/triggers/trigger/heal.qc [new file with mode: 0644]
qcsrc/common/triggers/trigger/hurt.qc [new file with mode: 0644]
qcsrc/common/triggers/trigger/impulse.qc [new file with mode: 0644]
qcsrc/common/triggers/trigger/impulse.qh [new file with mode: 0644]
qcsrc/common/triggers/trigger/include.qc [new file with mode: 0644]
qcsrc/common/triggers/trigger/include.qh [new file with mode: 0644]
qcsrc/common/triggers/trigger/jumppads.qc [new file with mode: 0644]
qcsrc/common/triggers/trigger/jumppads.qh [new file with mode: 0644]
qcsrc/common/triggers/trigger/keylock.qc [new file with mode: 0644]
qcsrc/common/triggers/trigger/keylock.qh [new file with mode: 0644]
qcsrc/common/triggers/trigger/magicear.qc [new file with mode: 0644]
qcsrc/common/triggers/trigger/monoflop.qc [new file with mode: 0644]
qcsrc/common/triggers/trigger/multi.qc [new file with mode: 0644]
qcsrc/common/triggers/trigger/multi.qh [new file with mode: 0644]
qcsrc/common/triggers/trigger/multivibrator.qc [new file with mode: 0644]
qcsrc/common/triggers/trigger/relay.qc [new file with mode: 0644]
qcsrc/common/triggers/trigger/relay_activators.qc [new file with mode: 0644]
qcsrc/common/triggers/trigger/relay_if.qc [new file with mode: 0644]
qcsrc/common/triggers/trigger/relay_teamcheck.qc [new file with mode: 0644]
qcsrc/common/triggers/trigger/secret.qc [new file with mode: 0644]
qcsrc/common/triggers/trigger/secret.qh [new file with mode: 0644]
qcsrc/common/triggers/trigger/swamp.qc [new file with mode: 0644]
qcsrc/common/triggers/trigger/swamp.qh [new file with mode: 0644]
qcsrc/common/triggers/trigger/teleport.qc [new file with mode: 0644]
qcsrc/common/triggers/triggers.qc [new file with mode: 0644]
qcsrc/common/triggers/triggers.qh [new file with mode: 0644]
qcsrc/common/util.qc
qcsrc/common/weapons/w_porto.qc
qcsrc/common/weapons/weapons.qc
qcsrc/csqcmodellib/cl_player.qc
qcsrc/dpdefs/csprogsdefs.qh
qcsrc/server/autocvars.qh
qcsrc/server/bot/havocbot/havocbot.qc
qcsrc/server/campaign.qc
qcsrc/server/cheats.qc
qcsrc/server/cl_client.qc
qcsrc/server/cl_physics.qc [deleted file]
qcsrc/server/cl_player.qc
qcsrc/server/constants.qh
qcsrc/server/defs.qh
qcsrc/server/func_breakable.qc [deleted file]
qcsrc/server/g_damage.qc
qcsrc/server/g_hook.qc
qcsrc/server/g_models.qc
qcsrc/server/g_subs.qc
qcsrc/server/g_subs.qh
qcsrc/server/g_triggers.qc [deleted file]
qcsrc/server/g_triggers.qh [deleted file]
qcsrc/server/g_world.qc
qcsrc/server/item_key.qc
qcsrc/server/item_key.qh
qcsrc/server/miscfunctions.qc
qcsrc/server/miscfunctions.qh
qcsrc/server/mutators/gamemode_assault.qc
qcsrc/server/mutators/gamemode_assault.qh
qcsrc/server/mutators/gamemode_ctf.qc
qcsrc/server/mutators/gamemode_ctf.qh
qcsrc/server/mutators/gamemode_domination.qc
qcsrc/server/mutators/gamemode_onslaught.qc
qcsrc/server/mutators/mutator_buffs.qc
qcsrc/server/mutators/mutator_dodging.qc
qcsrc/server/mutators/mutator_multijump.qc
qcsrc/server/mutators/mutator_nades.qc
qcsrc/server/mutators/mutators_include.qc
qcsrc/server/progs.src
qcsrc/server/race.qc
qcsrc/server/race.qh
qcsrc/server/secret.qc [deleted file]
qcsrc/server/secret.qh [deleted file]
qcsrc/server/spawnpoints.qc
qcsrc/server/t_halflife.qc
qcsrc/server/t_items.qc
qcsrc/server/t_jumppads.qc [deleted file]
qcsrc/server/t_jumppads.qh [deleted file]
qcsrc/server/t_plats.qc [deleted file]
qcsrc/server/t_swamp.qc [deleted file]
qcsrc/server/t_teleporters.qc [deleted file]
qcsrc/server/t_teleporters.qh [deleted file]
qcsrc/server/target_music.qc [deleted file]
qcsrc/server/target_spawn.qc [deleted file]
qcsrc/server/tturrets/system/system_main.qc
qcsrc/server/tturrets/system/system_misc.qc
qcsrc/server/vehicles/racer.qc
qcsrc/server/vehicles/vehicles.qc
qcsrc/warpzonelib/common.qc
qcsrc/warpzonelib/common.qh
qcsrc/warpzonelib/server.qc
qcsrc/warpzonelib/util_server.qc
qcsrc/warpzonelib/util_server.qh

index f66dc0128bdb87059cb2a4433fb81f8f7365c75a..19643ec210c93cd14460279935266435bb958584 100644 (file)
@@ -296,6 +296,7 @@ set sv_fraginfo_stats 1 "Enable statistics (health/armor) display information, 0
 
 // use default physics
 set sv_friction_on_land 0
+set sv_friction_slick 0.5
 
 set sv_player_viewoffset "0 0 35" "view offset of the player model"
 set sv_player_mins "-16 -16 -24" "playermodel mins"
index e03a6d4171b442f1f5297e3c2f9db5b676cd7c99..c109e1157636e5b6fb91f658a0c8ba37910b922f 100644 (file)
@@ -44,6 +44,7 @@ sv_warsowbunny_accel 0.1593
 sv_warsowbunny_topspeed 925
 sv_warsowbunny_backtosideratio 0.8
 sv_friction_on_land 0
+sv_friction_slick 0.5
 sv_doublejump 0
 sv_jumpspeedcap_min ""
 sv_jumpspeedcap_max ""
index 77926d05f7bf99a21aba09dee93c8ce864c974bb..6dadcf161bd1078d9b6b4227e84c6d9624b0f7e0 100644 (file)
@@ -37,6 +37,7 @@ sv_warsowbunny_accel 0.1593
 sv_warsowbunny_topspeed 925
 sv_warsowbunny_backtosideratio 0.8
 sv_friction_on_land 0
+sv_friction_slick 0.5
 sv_doublejump 0
 sv_jumpspeedcap_min ""
 sv_jumpspeedcap_max ""
index e2a3ef648087b64716ae7b5a7153a9663995ec67..a3e42a977eca1a69fc3ba438f7aa69f305466e34 100644 (file)
@@ -43,6 +43,7 @@ sv_warsowbunny_accel 0.1593
 sv_warsowbunny_topspeed 925
 sv_warsowbunny_backtosideratio 0.8
 sv_friction_on_land 0
+sv_friction_slick 0.5
 sv_doublejump 0
 sv_jumpspeedcap_min ""
 sv_jumpspeedcap_max ""
index 0acb6e9535955b03e79f281a2a502f95c6946f58..8597e9e005d77020ea96c2375efa78ba56887b8c 100644 (file)
@@ -35,6 +35,7 @@ sv_warsowbunny_accel 0.1593
 sv_warsowbunny_topspeed 925
 sv_warsowbunny_backtosideratio 0.8
 sv_friction_on_land 0
+sv_friction_slick 0.5
 sv_doublejump 1
 sv_jumpspeedcap_min 0
 sv_jumpspeedcap_max 0.5
index cac90b4ee748314e844c882df28d9edbbb8eb8c6..6cb0780dd988ee2f7ffafed71d16e8348d0a8b4b 100644 (file)
@@ -35,6 +35,7 @@ sv_warsowbunny_accel 0.1593
 sv_warsowbunny_topspeed 925
 sv_warsowbunny_backtosideratio 0.8
 sv_friction_on_land 0
+sv_friction_slick 0.5
 sv_doublejump 0
 sv_jumpspeedcap_min ""
 sv_jumpspeedcap_max ""
index e35bf82dd2759122afc2cf11d46707ef5fc1177b..871ac8cdc12fee0018412b335dc68560c3996228 100644 (file)
@@ -469,4 +469,5 @@ vector autocvar_crosshair_rpc_color = '0.2 1.0 0.2';
 float autocvar_crosshair_rpc_alpha = 1;
 float autocvar_crosshair_rpc_size = 1;
 int autocvar_cl_nade_timer;
+float autocvar_speedmeter;
 #endif
index b567190caa666f165cfe49bea7a09ea6fd8b0d73..615b571694f0caca933b08a51c0e1bded62f3ce4 100644 (file)
@@ -1,6 +1,7 @@
 #if defined(CSQC)
        #include "../dpdefs/csprogsdefs.qh"
        #include "defs.qh"
+       #include "../common/triggers/triggers.qh"
        #include "../common/util.qh"
        #include "autocvars.qh"
        #include "bgmscript.qh"
index 910ef6b5df006c69f3347ad33363498d31f53a74..fba28762a349652a1a27616083a509f9f7d0b6a9 100644 (file)
@@ -1,12 +1,6 @@
 #ifndef BGMSCRIPT_H
 #define BGMSCRIPT_H
 
-.string bgmscript;
-.float bgmscriptattack;
-.float bgmscriptdecay;
-.float bgmscriptsustain;
-.float bgmscriptrelease;
-
 .float just_toggled;
 
 void BGMScript_InitEntity(entity e);
index 6068a799f752892a2d1dc8f86b7d412537c0644c..38c5c67517885a08075db935ef2b8f9614e05f86 100644 (file)
@@ -1,5 +1,5 @@
 #if defined(CSQC)
-       #include "movetypes.qh"
+       #include "../common/movetypes/movetypes.qh"
        #include "prandom.qh"
        #include "rubble.qh"
 
index 7b74d2dd7fa56752889ee75f6abb3cdbb1e08115..0748483c5f1ec62d9901a540483aaca67352671d 100644 (file)
@@ -339,6 +339,31 @@ void LocalCommand_mv_download(int request, int argc)
        }
 }
 
+void LocalCommand_find(int request, int argc)
+{
+       switch(request)
+       {
+               case CMD_REQUEST_COMMAND:
+               {
+                       entity client;
+
+                       for(client = world; (client = find(client, classname, argv(1))); )
+                               print(etos(client), "\n");
+
+                       return;
+               }
+
+               default:
+                       print("Incorrect parameters for ^2find^7\n");
+               case CMD_REQUEST_USAGE:
+               {
+                       print("\nUsage:^3 cl_cmd find classname\n");
+                       print("  Where 'classname' is the classname to search for.\n");
+                       return;
+               }
+       }
+}
+
 void LocalCommand_sendcvar(int request, int argc)
 {
        switch(request)
@@ -410,6 +435,7 @@ void LocalCommand_(int request)
        CLIENT_COMMAND("handlevote", LocalCommand_handlevote(request, arguments), "System to handle selecting a vote or option") \
        CLIENT_COMMAND("hud", LocalCommand_hud(request, arguments), "Commands regarding/controlling the HUD system") \
        CLIENT_COMMAND("localprint", LocalCommand_localprint(request, arguments), "Create your own centerprint sent to yourself") \
+       CLIENT_COMMAND("find", LocalCommand_find(request, arguments), "Search through entities for matching classname") \
        CLIENT_COMMAND("mv_download", LocalCommand_mv_download(request, arguments), "Retrieve mapshot picture from the server") \
        CLIENT_COMMAND("sendcvar", LocalCommand_sendcvar(request, arguments), "Send a cvar to the server (like weaponpriority)") \
        /* nothing */
index 000ef8bcc9f45106d9c33e38c085c1e66653ebb3..836c9163a46e7afc102517c9ce20a5203bc12528 100644 (file)
@@ -7,7 +7,7 @@
        #include "autocvars.qh"
        #include "../common/deathtypes.qh"
        #include "damage.qh"
-       #include "movetypes.qh"
+       #include "../common/movetypes/movetypes.qh"
        #include "prandom.qh"
        #include "vehicles/vehicles.qh"
 #elif defined(MENUQC)
@@ -47,7 +47,7 @@ void DamageEffect_Think()
        pointparticles(self.team, org, '0 0 0', 1);
 }
 
-void DamageEffect(vector hitorg, float dmg, int type, int specnum)
+void DamageEffect(vector hitorg, float thedamage, int type, int specnum)
 {
        // particle effects for players and objects damaged by weapons (eg: flames coming out of victims shot with rockets)
 
@@ -92,7 +92,7 @@ void DamageEffect(vector hitorg, float dmg, int type, int specnum)
                        return; // allow a single damage on non-skeletal models
        }
 
-       life = bound(autocvar_cl_damageeffect_lifetime_min, dmg * autocvar_cl_damageeffect_lifetime, autocvar_cl_damageeffect_lifetime_max);
+       life = bound(autocvar_cl_damageeffect_lifetime_min, thedamage * autocvar_cl_damageeffect_lifetime, autocvar_cl_damageeffect_lifetime_max);
 
        effectname = get_weaponinfo(DEATH_WEAPONOF(type)).netname;
 
@@ -121,7 +121,7 @@ void DamageEffect(vector hitorg, float dmg, int type, int specnum)
 
 void Ent_DamageInfo(float isNew)
 {
-       float dmg, rad, edge, thisdmg;
+       float thedamage, rad, edge, thisdmg;
        bool hitplayer = false;
        int species, forcemul;
        vector force, thisforce;
@@ -137,7 +137,7 @@ void Ent_DamageInfo(float isNew)
        w_org.y = ReadCoord();
        w_org.z = ReadCoord();
 
-       dmg = ReadByte();
+       thedamage = ReadByte();
        rad = ReadByte();
        edge = ReadByte();
        force = decompressShortVector(ReadShort());
@@ -168,10 +168,10 @@ void Ent_DamageInfo(float isNew)
                                continue;
                        if(thisdmg < 0)
                                thisdmg = 0;
-                       if(dmg)
+                       if(thedamage)
                        {
-                               thisdmg = dmg + (edge - dmg) * thisdmg;
-                               thisforce = forcemul * vlen(force) * (thisdmg / dmg) * normalize(self.origin - w_org);
+                               thisdmg = thedamage + (edge - thedamage) * thisdmg;
+                               thisforce = forcemul * vlen(force) * (thisdmg / thedamage) * normalize(self.origin - w_org);
                        }
                        else
                        {
@@ -184,7 +184,7 @@ void Ent_DamageInfo(float isNew)
                        if(vlen(nearest - w_org) > bound(MIN_DAMAGEEXTRARADIUS, self.damageextraradius, MAX_DAMAGEEXTRARADIUS))
                                continue;
 
-                       thisdmg = dmg;
+                       thisdmg = thedamage;
                        thisforce = forcemul * force;
                }
 
index 95623505b83e297d2a7e63e12a4f78e378838d5b..0cc1e20f6c2c7d7836874ac3945bffd913ce2539 100644 (file)
@@ -12,9 +12,6 @@
 .string fx_texture;
 .float  fx_lifetime;
 
-void SUB_Remove()
-{ remove(self); }
-
 void b_draw()
 {
     //Draw_CylindricLine(self.fx_start, self.fx_end, self.fx_with, self.fx_texture, 0, time * 3, '1 1 1', 0.7, DRAWFLAG_ADDITIVE, view_origin);
diff --git a/qcsrc/client/laser.qc b/qcsrc/client/laser.qc
deleted file mode 100644 (file)
index a74c668..0000000
+++ /dev/null
@@ -1,134 +0,0 @@
-#if defined(CSQC)
-       #include "../dpdefs/csprogsdefs.qh"
-       #include "../common/buffs.qh"
-       #include "../csqcmodellib/interpolate.qh"
-       #include "main.qh"
-       #include "../csqcmodellib/cl_model.qh"
-#elif defined(MENUQC)
-#elif defined(SVQC)
-#endif
-
-
-// a laser goes from origin in direction angles
-// it has color 'colormod'
-// and stops when something is in the way
-.int cnt; // end effect
-.vector colormod;
-.int state; // on-off
-.int count; // flags for the laser
-.vector velocity;
-.float alpha;
-.float scale; // scaling factor of the thickness
-.float modelscale; // scaling factor of the dlight
-
-void Draw_Laser()
-{
-       if(!self.state)
-               return;
-       InterpolateOrigin_Do();
-       if(self.count & 0x80)
-       {
-               if(self.count & 0x10)
-               {
-                       trace_endpos = self.velocity;
-                       trace_dphitq3surfaceflags = 0;
-               }
-               else
-                       traceline(self.origin, self.velocity, 0, self);
-       }
-       else
-       {
-               if(self.count & 0x10)
-               {
-                       makevectors(self.angles);
-                       trace_endpos = self.origin + v_forward * 1048576;
-                       trace_dphitq3surfaceflags = Q3SURFACEFLAG_SKY;
-               }
-               else
-               {
-                       makevectors(self.angles);
-                       traceline(self.origin, self.origin + v_forward * 32768, 0, self);
-                       if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY)
-                               trace_endpos = self.origin + v_forward * 1048576;
-               }
-       }
-       if(self.scale != 0)
-       {
-               if(self.alpha)
-               {
-                       Draw_CylindricLine(self.origin, trace_endpos, self.scale, "particles/laserbeam", 0, time * 3, self.colormod, self.alpha, DRAWFLAG_NORMAL, view_origin);
-               }
-               else
-               {
-                       Draw_CylindricLine(self.origin, trace_endpos, self.scale, "particles/laserbeam", 0, time * 3, self.colormod, 0.5, DRAWFLAG_ADDITIVE, view_origin);
-               }
-       }
-       if (!(trace_dphitq3surfaceflags & (Q3SURFACEFLAG_SKY | Q3SURFACEFLAG_NOIMPACT)))
-       {
-               if(self.cnt >= 0)
-                       pointparticles(self.cnt, trace_endpos, trace_plane_normal, drawframetime * 1000);
-               if(self.colormod != '0 0 0' && self.modelscale != 0)
-                       adddynamiclight(trace_endpos + trace_plane_normal * 1, self.modelscale, self.colormod * 5);
-       }
-}
-
-void Ent_Laser()
-{
-       InterpolateOrigin_Undo();
-
-       // 30 bytes, or 13 bytes for just moving
-       int f = ReadByte();
-       self.count = (f & 0xF0);
-
-       if(self.count & 0x80)
-               self.iflags = IFLAG_VELOCITY | IFLAG_ORIGIN;
-       else
-               self.iflags = IFLAG_ANGLES | IFLAG_ORIGIN;
-
-       if(f & 1)
-       {
-               self.origin_x = ReadCoord();
-               self.origin_y = ReadCoord();
-               self.origin_z = ReadCoord();
-               setorigin(self, self.origin);
-       }
-       if(f & 8)
-       {
-               self.colormod_x = ReadByte() / 255.0;
-               self.colormod_y = ReadByte() / 255.0;
-               self.colormod_z = ReadByte() / 255.0;
-               if(f & 0x40)
-                       self.alpha = ReadByte() / 255.0;
-               else
-                       self.alpha = 0;
-               self.scale = 2;
-               self.modelscale = 50;
-               if(f & 0x20)
-               {
-                       self.scale *= ReadByte() / 16.0; // beam radius
-                       self.modelscale *= ReadByte() / 16.0; // dlight radius
-               }
-               if((f & 0x80) || !(f & 0x10))
-                       self.cnt = ReadShort() - 1; // effect number
-               else
-                       self.cnt = 0;
-       }
-       if(f & 2)
-       {
-               if(f & 0x80)
-               {
-                       self.velocity_x = ReadCoord();
-                       self.velocity_y = ReadCoord();
-                       self.velocity_z = ReadCoord();
-               }
-               else
-               {
-                       self.angles_x = ReadAngle();
-                       self.angles_y = ReadAngle();
-               }
-       }
-       if(f & 4)
-               self.state = ReadByte();
-       InterpolateOrigin_Note();
-       self.draw = Draw_Laser;
-}
index 13f9545faab60a7c944d4d45b192dc6e16ca8a46..0b75058b43ee5174925980de62065e04a159e9e4 100644 (file)
@@ -3,7 +3,6 @@
 #include "particles.qh"
 #include "scoreboard.qh"
 #include "shownames.qh"
-#include "target_music.qh"
 #include "tturrets.qh"
 #include "tuba.qh"
 #include "wall.qh"
@@ -17,6 +16,8 @@
 
 #include "../common/monsters/monsters.qh"
 
+#include "../common/triggers/include.qh"
+
 #include "../warpzonelib/client.qh"
 
 // --------------------------------------------------------------------------
@@ -350,6 +351,7 @@ float CSQC_InputEvent(float bInputType, float nPrimary, float nSecondary)
 
 // --------------------------------------------------------------------------
 // BEGIN OPTIONAL CSQC FUNCTIONS
+
 void Ent_RemoveEntCS()
 {
        entcs_receiver[self.sv_entnum] = world;
@@ -842,6 +844,16 @@ void CSQC_Ent_Update(float bIsNewEntity)
                case ENT_CLIENT_SPAWNEVENT: Ent_ReadSpawnEvent(bIsNewEntity); break;
                case ENT_CLIENT_NOTIFICATION: Read_Notification(bIsNewEntity); break;
                case ENT_CLIENT_HEALING_ORB: ent_healer(); break;
+               case ENT_CLIENT_LADDER: ent_func_ladder(); break;
+               case ENT_CLIENT_TRIGGER_PUSH: ent_trigger_push(); break;
+               case ENT_CLIENT_TARGET_PUSH: ent_target_push(); break;
+               case ENT_CLIENT_CONVEYOR: ent_conveyor(); break;
+               case ENT_CLIENT_DOOR: ent_door(); break;
+               case ENT_CLIENT_PLAT: ent_plat(); break;
+               case ENT_CLIENT_SWAMP: ent_swamp(); break;
+               case ENT_CLIENT_CORNER: ent_corner(); break;
+               case ENT_CLIENT_KEYLOCK: ent_keylock(); break;
+               case ENT_CLIENT_TRAIN: ent_train(); break;
 
                default:
                        //error(strcat(_("unknown entity type in CSQC_Ent_Update: %d\n"), self.enttype));
index d74a24a320142d35e7da242c19f24586ca1e08fe..95f94794bcba615d32060fa6e8cab67078316795 100644 (file)
@@ -4,6 +4,116 @@
 
 #include "../common/command/generic.qh"
 
+void InitializeEntity(entity e, void(void) func, float order)
+{
+    entity prev, cur;
+
+    if (!e || e.initialize_entity)
+    {
+        // make a proxy initializer entity
+        entity e_old;
+        e_old = e;
+        e = spawn();
+        e.classname = "initialize_entity";
+        e.enemy = e_old;
+    }
+
+    e.initialize_entity = func;
+    e.initialize_entity_order = order;
+
+    cur = initialize_entity_first;
+    prev = world;
+    for (;;)
+    {
+        if (!cur || cur.initialize_entity_order > order)
+        {
+            // insert between prev and cur
+            if (prev)
+                prev.initialize_entity_next = e;
+            else
+                initialize_entity_first = e;
+            e.initialize_entity_next = cur;
+            return;
+        }
+        prev = cur;
+        cur = cur.initialize_entity_next;
+    }
+}
+void InitializeEntitiesRun()
+{
+    entity startoflist;
+    startoflist = initialize_entity_first;
+    initialize_entity_first = world;
+    for (self = startoflist; self; self = self.initialize_entity_next)
+    {
+       //self.remove_except_protected_forbidden = 1;
+    }
+    for (self = startoflist; self; )
+    {
+        entity e;
+        var void(void) func;
+        e = self.initialize_entity_next;
+        func = self.initialize_entity;
+        self.initialize_entity_order = 0;
+        self.initialize_entity = func_null;
+        self.initialize_entity_next = world;
+       //self.remove_except_protected_forbidden = 0;
+        if (self.classname == "initialize_entity")
+        {
+            entity e_old;
+            e_old = self.enemy;
+            remove(self);
+            self = e_old;
+        }
+        //dprint("Delayed initialization: ", self.classname, "\n");
+        if(func)
+            func();
+        else
+        {
+            eprint(self);
+            backtrace(strcat("Null function in: ", self.classname, "\n"));
+        }
+        self = e;
+    }
+}
+
+void defer_think()
+{
+    entity oself;
+
+    oself           = self;
+    self            = self.owner;
+    oself.think     = SUB_Remove;
+    oself.nextthink = time;
+
+    oself.use();
+}
+
+/*
+    Execute func() after time + fdelay.
+    self when func is executed = self when defer is called
+*/
+void defer(float fdelay, void() func)
+{
+    entity e;
+
+    e           = spawn();
+    e.classname = "defer";
+    e.owner     = self;
+    e.use       = func;
+    e.think     = defer_think;
+    e.nextthink = time + fdelay;
+}
+
+void defer_clear(entity ent)
+{
+       entity e;
+       for(e = world; (e = find(e, classname, "defer")); )
+               if(e.owner == ent)
+                       remove(e);
+}
+
+
 void AuditLists()
 {
        entity e;
index 6c643962a65678ab19643cc48a2300536aab2ee0..142d00a23869f9ae5821c38234bfe15699677ff1 100644 (file)
@@ -5,6 +5,24 @@ entity players;
 entity teams;
 float team_count; // real teams
 
+const int INITPRIO_FIRST                               = 0;
+const int INITPRIO_GAMETYPE                    = 0;
+const int INITPRIO_GAMETYPE_FALLBACK   = 1;
+const int INITPRIO_FINDTARGET                  = 10;
+const int INITPRIO_DROPTOFLOOR                         = 20;
+const int INITPRIO_SETLOCATION                         = 90;
+const int INITPRIO_LINKDOORS                   = 91;
+const int INITPRIO_LAST                                = 99;
+
+.void(void) initialize_entity;
+.int initialize_entity_order;
+.entity initialize_entity_next;
+entity initialize_entity_first;
+
+void InitializeEntity(entity e, void(void) func, int order);
+
+void InitializeEntitiesRun();
+
 void AuditLists();
 
 float RegisterPlayer(entity player);
@@ -46,6 +64,10 @@ void drawborderlines(float thickness, vector pos, vector dim, vector color, floa
 
 void drawpic_tiled(vector pos, string pic, vector sz, vector area, vector color, float theAlpha, float drawflag);
 
+void defer(float fdelay, void() func);
+
+void defer_clear(entity ent);
+
 // drawpic wrapper to draw an image as large as possible with preserved aspect ratio into a box
 float _drawpic_imgaspect;
 vector _drawpic_imgsize;
diff --git a/qcsrc/client/movetypes.qc b/qcsrc/client/movetypes.qc
deleted file mode 100644 (file)
index 2f62ae6..0000000
+++ /dev/null
@@ -1,559 +0,0 @@
-#if defined(CSQC)
-       #include "../dpdefs/csprogsdefs.qh"
-       #include "defs.qh"
-       #include "../common/stats.qh"
-       #include "../common/util.qh"
-       #include "movetypes.qh"
-       #include "../csqcmodellib/common.qh"
-       #include "../server/t_items.qh"
-#elif defined(MENUQC)
-#elif defined(SVQC)
-#endif
-
-
-const int MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE = 4;
-#define GRAVITY_UNAFFECTED_BY_TICRATE (getstati(STAT_MOVEFLAGS) & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE)
-
-.entity move_groundentity; // FIXME add move_groundnetworkentity?
-.float move_suspendedinair;
-.float move_didgravity;
-
-void _Movetype_CheckVelocity() // SV_CheckVelocity
-{
-}
-
-float _Movetype_CheckWater(entity ent) // SV_CheckWater
-{
-       vector point = ent.move_origin;
-       point.z += (ent.mins.z + 1);
-
-       int nativecontents = pointcontents(point);
-
-       if(ent.move_watertype)
-       if(ent.move_watertype != nativecontents)
-       {
-               //print(sprintf("_Movetype_CheckWater(): Original: '%d', New: '%d'\n", ent.move_watertype, nativecontents));
-               if(ent.contentstransition)
-                       ent.contentstransition(ent.move_watertype, nativecontents);
-       }
-
-       ent.move_waterlevel = 0;
-       ent.move_watertype = CONTENT_EMPTY;
-
-       int supercontents = Mod_Q1BSP_SuperContentsFromNativeContents(nativecontents);
-       if(supercontents & DPCONTENTS_LIQUIDSMASK)
-       {
-               ent.move_watertype = nativecontents;
-               ent.move_waterlevel = 1;
-               point.y = (ent.origin.y + ((ent.mins.z + ent.maxs.y) * 0.5));
-               if(Mod_Q1BSP_SuperContentsFromNativeContents(pointcontents(point)) & DPCONTENTS_LIQUIDSMASK)
-               {
-                       ent.move_waterlevel = 2;
-                       point.y = ent.origin.y + ent.view_ofs.y;
-                       if(Mod_Q1BSP_SuperContentsFromNativeContents(pointcontents(point)) & DPCONTENTS_LIQUIDSMASK)
-                               ent.move_waterlevel = 3;
-               }
-       }
-
-       return (ent.move_waterlevel > 1);
-}
-
-void _Movetype_CheckWaterTransition(entity ent) // SV_CheckWaterTransition
-{
-       float contents = pointcontents(ent.move_origin);
-
-       if(!ent.move_watertype)
-       {
-               // just spawned here
-               if(!autocvar_cl_gameplayfix_fixedcheckwatertransition)
-               {
-                       ent.move_watertype = contents;
-                       ent.move_waterlevel = 1;
-                       return;
-               }
-       }
-       else if(ent.move_watertype != contents)
-       {
-               //print(sprintf("_Movetype_CheckWaterTransition(): Origin: %s, Direct: '%d', Original: '%d', New: '%d'\n", vtos(ent.move_origin), pointcontents(ent.move_origin), ent.move_watertype, contents));
-               if(ent.contentstransition)
-                       ent.contentstransition(ent.move_watertype, contents);
-       }
-
-       if(contents <= CONTENT_WATER)
-       {
-               ent.move_watertype = contents;
-               ent.move_waterlevel = 1;
-       }
-       else
-       {
-               ent.move_watertype = CONTENT_EMPTY;
-               ent.move_waterlevel = (autocvar_cl_gameplayfix_fixedcheckwatertransition ? 0 : contents);
-       }
-}
-
-void _Movetype_Impact(entity oth) // SV_Impact
-{
-       entity oldother, oldself;
-
-       oldself = self;
-       oldother = other;
-
-       if(self.move_touch)
-       {
-               other = oth;
-
-               self.move_touch();
-
-               other = oldother;
-       }
-
-       if(oth.move_touch)
-       {
-               other = self;
-               self = oth;
-
-               self.move_touch();
-
-               self = oldself;
-               other = oldother;
-       }
-}
-
-void _Movetype_LinkEdict_TouchAreaGrid() // SV_LinkEdict_TouchAreaGrid
-{
-       entity e, oldself, oldother;
-
-       oldself = self;
-       oldother = other;
-
-       for(e = findradius(0.5 * (self.absmin + self.absmax), 0.5 * vlen(self.absmax - self.absmin)); e; e = e.chain)
-       {
-               if(e.move_touch)
-               if(boxesoverlap(e.absmin, e.absmax, oldself.absmin, oldself.absmax))
-               {
-                       self = e;
-                       other = oldself;
-
-                       trace_allsolid = false;
-                       trace_startsolid = false;
-                       trace_fraction = 1;
-                       trace_inwater = false;
-                       trace_inopen = true;
-                       trace_endpos = e.origin;
-                       trace_plane_normal = '0 0 1';
-                       trace_plane_dist = 0;
-                       trace_ent = oldself;
-
-                       e.move_touch();
-               }
-       }
-
-       other = oldother;
-       self = oldself;
-}
-
-void _Movetype_LinkEdict(float touch_triggers) // SV_LinkEdict
-{
-       vector mi, ma;
-       if(self.solid == SOLID_BSP)
-       {
-               // TODO set the absolute bbox
-               mi = self.mins;
-               ma = self.maxs;
-       }
-       else
-       {
-               mi = self.mins;
-               ma = self.maxs;
-       }
-       mi = mi + self.origin;
-       ma = ma + self.origin;
-
-       if(self.move_flags & FL_ITEM)
-       {
-               mi.x -= 15;
-               mi.y -= 15;
-               mi.z -= 1;
-               ma.x += 15;
-               ma.y += 15;
-               ma.z += 1;
-       }
-       else
-       {
-               mi.x -= 1;
-               mi.y -= 1;
-               mi.z -= 1;
-               ma.x += 1;
-               ma.y += 1;
-               ma.z += 1;
-       }
-
-       self.absmin = mi;
-       self.absmax = ma;
-
-       if(touch_triggers)
-               _Movetype_LinkEdict_TouchAreaGrid();
-}
-
-float _Movetype_TestEntityPosition(vector ofs) // SV_TestEntityPosition
-{
-       vector org;
-       org = self.move_origin + ofs;
-
-       int cont = self.dphitcontentsmask;
-       self.dphitcontentsmask = DPCONTENTS_SOLID;
-       tracebox(self.move_origin, self.mins, self.maxs, self.move_origin, MOVE_NOMONSTERS, self);
-       self.dphitcontentsmask = cont;
-
-       if(trace_startsolid)
-               return true;
-
-       if(vlen(trace_endpos - self.move_origin) > 0.0001)
-               self.move_origin = trace_endpos;
-       return false;
-}
-
-float _Movetype_UnstickEntity() // SV_UnstickEntity
-{
-       if(!_Movetype_TestEntityPosition('0 0 0'))
-               return true;
-       if(!_Movetype_TestEntityPosition('-1 0 0')) goto success;
-       if(!_Movetype_TestEntityPosition('1 0 0')) goto success;
-       if(!_Movetype_TestEntityPosition('0 -1 0')) goto success;
-       if(!_Movetype_TestEntityPosition('0 1 0')) goto success;
-       if(!_Movetype_TestEntityPosition('-1 -1 0')) goto success;
-       if(!_Movetype_TestEntityPosition('1 -1 0')) goto success;
-       if(!_Movetype_TestEntityPosition('-1 1 0')) goto success;
-       if(!_Movetype_TestEntityPosition('1 1 0')) goto success;
-       float i;
-       for(i = 1; i <= 17; ++i)
-       {
-               if(!_Movetype_TestEntityPosition('0 0 -1' * i)) goto success;
-               if(!_Movetype_TestEntityPosition('0 0 1' * i)) goto success;
-       }
-       dprintf("Can't unstick an entity (edict: %d, classname: %s, origin: %s)\n", num_for_edict(self), self.classname, vtos(self.move_origin));
-       return false;
-:success
-       dprintf("Sucessfully unstuck an entity (edict: %d, classname: %s, origin: %s)\n", num_for_edict(self), self.classname, vtos(self.move_origin));
-       _Movetype_LinkEdict(true);
-       return true;
-}
-
-vector _Movetype_ClipVelocity(vector vel, vector norm, float f) // SV_ClipVelocity
-{
-       vel = vel - ((vel * norm) * norm) * f;
-
-       if(vel.x > -0.1 && vel.x < 0.1) vel.x = 0;
-       if(vel.y > -0.1 && vel.y < 0.1) vel.y = 0;
-       if(vel.z > -0.1 && vel.z < 0.1) vel.z = 0;
-
-       return vel;
-}
-
-void _Movetype_PushEntityTrace(vector push)
-{
-       vector end;
-       float type;
-
-       end = self.move_origin + push;
-
-       if(self.move_nomonsters)
-               type = max(0, self.move_nomonsters);
-       else if(self.move_movetype == MOVETYPE_FLYMISSILE)
-               type = MOVE_MISSILE;
-       else if(self.solid == SOLID_TRIGGER || self.solid == SOLID_NOT)
-               type = MOVE_NOMONSTERS;
-       else
-               type = MOVE_NORMAL;
-
-       tracebox(self.move_origin, self.mins, self.maxs, end, type, self);
-}
-
-float _Movetype_PushEntity(vector push, float failonstartsolid) // SV_PushEntity
-{
-       _Movetype_PushEntityTrace(push);
-
-       if(trace_startsolid && failonstartsolid)
-               return trace_fraction;
-
-       self.move_origin = trace_endpos;
-
-       if(trace_fraction < 1)
-               if(self.solid >= SOLID_TRIGGER && (!(self.move_flags & FL_ONGROUND) || (self.move_groundentity != trace_ent)))
-                       _Movetype_Impact(trace_ent);
-
-       return trace_fraction;
-}
-
-const float MAX_CLIP_PLANES = 5;
-void _Movetype_Physics_Toss(float dt) // SV_Physics_Toss
-{
-       if(self.move_flags & FL_ONGROUND)
-       {
-               if(self.move_velocity.z >= 1/32)
-                       self.move_flags &= ~FL_ONGROUND;
-               else if(!self.move_groundentity)
-                       return;
-               else if(self.move_suspendedinair && wasfreed(self.move_groundentity))
-               {
-                       self.move_groundentity = world;
-                       return;
-               }
-       }
-
-       self.move_suspendedinair = false;
-
-       _Movetype_CheckVelocity();
-
-       if(self.move_movetype == MOVETYPE_BOUNCE || self.move_movetype == MOVETYPE_TOSS)
-       {
-               self.move_didgravity = 1;
-               if(GRAVITY_UNAFFECTED_BY_TICRATE)
-               {
-                       if(self.gravity)
-                               self.move_velocity_z -= 0.5 * dt * self.gravity * getstatf(STAT_MOVEVARS_GRAVITY);
-                       else
-                               self.move_velocity_z -= 0.5 * dt * getstatf(STAT_MOVEVARS_GRAVITY);
-               }
-               else
-               {
-                       if(self.gravity)
-                               self.move_velocity_z -= dt * self.gravity * getstatf(STAT_MOVEVARS_GRAVITY);
-                       else
-                               self.move_velocity_z -= dt * getstatf(STAT_MOVEVARS_GRAVITY);
-               }
-       }
-
-       self.move_angles = self.move_angles + self.move_avelocity * dt;
-
-       float movetime, bump;
-       movetime = dt;
-       for(bump = 0; bump < MAX_CLIP_PLANES && movetime > 0; ++bump)
-       {
-               vector move;
-               move = self.move_velocity * movetime;
-               _Movetype_PushEntity(move, true);
-               if(wasfreed(self))
-                       return;
-
-               if(trace_startsolid)
-               {
-                       _Movetype_UnstickEntity();
-                       _Movetype_PushEntity(move, false);
-                       if(wasfreed(self))
-                               return;
-               }
-
-               if(trace_fraction == 1)
-                       break;
-
-               movetime *= 1 - min(1, trace_fraction);
-
-               if(self.move_movetype == MOVETYPE_BOUNCEMISSILE)
-               {
-                       self.move_velocity = _Movetype_ClipVelocity(self.move_velocity, trace_plane_normal, 2.0);
-                       self.move_flags &= ~FL_ONGROUND;
-               }
-               else if(self.move_movetype == MOVETYPE_BOUNCE)
-               {
-                       float d, bouncefac, bouncestop;
-
-                       bouncefac = self.move_bounce_factor;     if(!bouncefac)  bouncefac = 0.5;
-                       bouncestop = self.move_bounce_stopspeed; if(!bouncestop) bouncestop = 60 / 800;
-                       if(self.gravity)
-                               bouncestop *= self.gravity * getstatf(STAT_MOVEVARS_GRAVITY);
-                       else
-                               bouncestop *= getstatf(STAT_MOVEVARS_GRAVITY);
-
-                       self.move_velocity = _Movetype_ClipVelocity(self.move_velocity, trace_plane_normal, 1 + bouncefac);
-
-                       d = trace_plane_normal * self.move_velocity;
-                       if(trace_plane_normal.z > 0.7 && d < bouncestop && d > -bouncestop)
-                       {
-                               self.move_flags |= FL_ONGROUND;
-                               self.move_groundentity = trace_ent;
-                               self.move_velocity = '0 0 0';
-                               self.move_avelocity = '0 0 0';
-                       }
-                       else
-                               self.move_flags &= ~FL_ONGROUND;
-               }
-               else
-               {
-                       self.move_velocity = _Movetype_ClipVelocity(self.move_velocity, trace_plane_normal, 1.0);
-                       if(trace_plane_normal.z > 0.7)
-                       {
-                               self.move_flags |= FL_ONGROUND;
-                               self.move_groundentity = trace_ent;
-                               if(trace_ent.solid == SOLID_BSP)
-                                       self.move_suspendedinair = true;
-                               self.move_velocity = '0 0 0';
-                               self.move_avelocity = '0 0 0';
-                       }
-                       else
-                               self.move_flags &= ~FL_ONGROUND;
-               }
-
-               // DP revision 8905 (just, WHY...)
-               if(self.move_movetype == MOVETYPE_BOUNCEMISSILE)
-                       break;
-
-               // DP revision 8918 (WHY...)
-               if(self.move_flags & FL_ONGROUND)
-                       break;
-       }
-
-       if(GRAVITY_UNAFFECTED_BY_TICRATE)
-       if(self.move_didgravity > 0)
-       if(!(self.move_flags & FL_ONGROUND))
-       {
-               if(self.gravity)
-                       self.move_velocity_z -= 0.5 * dt * self.gravity * getstatf(STAT_MOVEVARS_GRAVITY);
-               else
-                       self.move_velocity_z -= 0.5 * dt * getstatf(STAT_MOVEVARS_GRAVITY);
-       }
-
-       _Movetype_CheckWaterTransition(self);
-}
-
-void _Movetype_Physics_Frame(float movedt)
-{
-       self.move_didgravity = -1;
-       switch(self.move_movetype)
-       {
-               case MOVETYPE_PUSH:
-               case MOVETYPE_FAKEPUSH:
-                       error("SV_Physics_Pusher not implemented");
-                       break;
-               case MOVETYPE_NONE:
-                       break;
-               case MOVETYPE_FOLLOW:
-                       error("SV_Physics_Follow not implemented");
-                       break;
-               case MOVETYPE_NOCLIP:
-                       _Movetype_CheckWater(self);
-                       self.move_origin = self.move_origin + ticrate * self.move_velocity;
-                       self.move_angles = self.move_angles + ticrate * self.move_avelocity;
-                       _Movetype_LinkEdict(false);
-                       break;
-               case MOVETYPE_STEP:
-                       error("SV_Physics_Step not implemented");
-                       break;
-               case MOVETYPE_WALK:
-                       error("SV_Physics_Walk not implemented");
-                       break;
-               case MOVETYPE_TOSS:
-               case MOVETYPE_BOUNCE:
-               case MOVETYPE_BOUNCEMISSILE:
-               case MOVETYPE_FLYMISSILE:
-               case MOVETYPE_FLY:
-                       _Movetype_Physics_Toss(movedt);
-                       break;
-       }
-}
-
-void Movetype_Physics_NoMatchServer() // optimized
-{
-       float movedt;
-
-       movedt = time - self.move_time;
-       self.move_time = time;
-
-       _Movetype_Physics_Frame(movedt);
-       if(wasfreed(self))
-               return;
-
-       self.avelocity = self.move_avelocity;
-       self.velocity = self.move_velocity;
-       self.angles = self.move_angles;
-       setorigin(self, self.move_origin);
-}
-
-void Movetype_Physics_MatchServer(bool sloppy)
-{
-       Movetype_Physics_MatchTicrate(ticrate, sloppy);
-}
-
-void Movetype_Physics_MatchTicrate(float tr, bool sloppy) // SV_Physics_Entity
-{
-       float n, i, dt, movedt;
-
-       if(tr <= 0)
-       {
-               Movetype_Physics_NoMatchServer();
-               return;
-       }
-
-       dt = time - self.move_time;
-
-       movedt = tr;
-       n = max(0, floor(dt / tr));
-       dt -= n * tr;
-       self.move_time += n * tr;
-
-       if(!self.move_didgravity)
-               self.move_didgravity = ((self.move_movetype == MOVETYPE_BOUNCE || self.move_movetype == MOVETYPE_TOSS) && !(self.move_flags & FL_ONGROUND));
-
-       for(i = 0; i < n; ++i)
-       {
-               _Movetype_Physics_Frame(movedt);
-               if(wasfreed(self))
-                       return;
-       }
-
-       self.avelocity = self.move_avelocity;
-
-       if(dt > 0 && self.move_movetype != MOVETYPE_NONE && !(self.move_flags & FL_ONGROUND))
-       {
-               // now continue the move from move_time to time
-               self.velocity = self.move_velocity;
-
-               if(self.move_didgravity > 0)
-               {
-                       if(GRAVITY_UNAFFECTED_BY_TICRATE)
-                       {
-                               if(self.gravity)
-                                       self.velocity_z -= 0.5 * dt * self.gravity * getstatf(STAT_MOVEVARS_GRAVITY);
-                               else
-                                       self.velocity_z -= 0.5 * dt * getstatf(STAT_MOVEVARS_GRAVITY);
-                       }
-                       else
-                       {
-                               if(self.gravity)
-                                       self.velocity_z -= dt * self.gravity * getstatf(STAT_MOVEVARS_GRAVITY);
-                               else
-                                       self.velocity_z -= dt * getstatf(STAT_MOVEVARS_GRAVITY);
-                       }
-               }
-
-               self.angles = self.move_angles + dt * self.avelocity;
-
-               if(sloppy || self.movetype == MOVETYPE_NOCLIP)
-               {
-                       setorigin(self, self.move_origin + dt * self.velocity);
-               }
-               else
-               {
-                       _Movetype_PushEntityTrace(dt * self.velocity);
-                       if(!trace_startsolid)
-                               setorigin(self, trace_endpos);
-               }
-
-               if(self.move_didgravity > 0)
-               {
-                       if(GRAVITY_UNAFFECTED_BY_TICRATE)
-                       {
-                               if(self.gravity)
-                                       self.velocity_z -= 0.5 * dt * self.gravity * getstatf(STAT_MOVEVARS_GRAVITY);
-                               else
-                                       self.velocity_z -= 0.5 * dt * getstatf(STAT_MOVEVARS_GRAVITY);
-                       }
-               }
-       }
-       else
-       {
-               self.velocity = self.move_velocity;
-               self.angles = self.move_angles;
-               setorigin(self, self.move_origin);
-       }
-}
diff --git a/qcsrc/client/movetypes.qh b/qcsrc/client/movetypes.qh
deleted file mode 100644 (file)
index 3294ce4..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-#ifndef MOVETYPES_H
-#define MOVETYPES_H
-
-.float move_movetype;
-.float move_time;
-.vector move_origin;
-.vector move_angles;
-.vector move_velocity;
-.vector move_avelocity;
-.int move_flags;
-.int move_watertype;
-.int move_waterlevel;
-.void(void) move_touch;
-.void(float, float) contentstransition;
-.float move_bounce_factor;
-.float move_bounce_stopspeed;
-.float move_nomonsters; // -1 for MOVE_NORMAL, otherwise a MOVE_ constant
-
-// should match sv_gameplayfix_fixedcheckwatertransition
-float autocvar_cl_gameplayfix_fixedcheckwatertransition = 1;
-
-void Movetype_Physics_MatchTicrate(float tr, bool sloppy);
-void Movetype_Physics_MatchServer(bool sloppy);
-void Movetype_Physics_NoMatchServer();
-
-const int MOVETYPE_NONE                                = 0;
-const int MOVETYPE_ANGLENOCLIP     = 1;
-const int MOVETYPE_ANGLECLIP       = 2;
-const int MOVETYPE_WALK                                = 3;
-const int MOVETYPE_STEP                                = 4;
-const int MOVETYPE_FLY                         = 5;
-const int MOVETYPE_TOSS                                = 6;
-const int MOVETYPE_PUSH                                = 7;
-const int MOVETYPE_NOCLIP                  = 8;
-const int MOVETYPE_FLYMISSILE      = 9;
-const int MOVETYPE_BOUNCE                  = 10;
-const int MOVETYPE_BOUNCEMISSILE       = 11;   // Like bounce but doesn't lose speed on bouncing
-const int MOVETYPE_FOLLOW           = 12;
-const int MOVETYPE_FAKEPUSH         = 13;
-const int MOVETYPE_FLY_WORLDONLY    = 33;
-
-const int FL_ITEM                   = 256;
-const int FL_ONGROUND                          = 512;
-#endif
index 6d2ddf87d5a431b3cf4c901b60881d868fb0e25b..59b9b75f46c7b4c781f319c3ce1bbd801577ca7e 100644 (file)
@@ -1,218 +1,5 @@
 #include "particles.qh"
 
-void Draw_PointParticles()
-{
-       float n, i, fail;
-       vector p;
-       vector sz;
-       vector o;
-       o = self.origin;
-       sz = self.maxs - self.mins;
-       n = BGMScript(self);
-       if(self.absolute == 2)
-       {
-               if(n >= 0)
-                       n = self.just_toggled ? self.impulse : 0;
-               else
-                       n = self.impulse * drawframetime;
-       }
-       else
-       {
-               n *= self.impulse * drawframetime;
-               if(self.just_toggled)
-                       if(n < 1)
-                               n = 1;
-       }
-       if(n == 0)
-               return;
-       fail = 0;
-       for(i = random(); i <= n && fail <= 64*n; ++i)
-       {
-               p = o + self.mins;
-               p.x += random() * sz.x;
-               p.y += random() * sz.y;
-               p.z += random() * sz.z;
-               if(WarpZoneLib_BoxTouchesBrush(p, p, self, world))
-               {
-                       if(self.movedir != '0 0 0')
-                       {
-                               traceline(p, p + normalize(self.movedir) * 4096, 0, world);
-                               p = trace_endpos;
-                               pointparticles(self.cnt, p, trace_plane_normal * vlen(self.movedir) + self.velocity + randomvec() * self.waterlevel, self.count);
-                       }
-                       else
-                       {
-                               pointparticles(self.cnt, p, self.velocity + randomvec() * self.waterlevel, self.count);
-                       }
-                       if(self.noise != "")
-                       {
-                               setorigin(self, p);
-                               sound(self, CH_AMBIENT, self.noise, VOL_BASE * self.volume, self.atten);
-                       }
-                       self.just_toggled = 0;
-               }
-               else if(self.absolute)
-               {
-                       ++fail;
-                       --i;
-               }
-       }
-       setorigin(self, o);
-}
-
-void Ent_PointParticles_Remove()
-{
-       if(self.noise)
-               strunzone(self.noise);
-       self.noise = string_null;
-       if(self.bgmscript)
-               strunzone(self.bgmscript);
-       self.bgmscript = string_null;
-}
-
-void Ent_PointParticles()
-{
-       float i;
-       vector v;
-       int f = ReadByte();
-       if(f & 2)
-       {
-               i = ReadCoord(); // density (<0: point, >0: volume)
-               if(i && !self.impulse && self.cnt) // self.cnt check is so it only happens if the ent already existed
-                       self.just_toggled = 1;
-               self.impulse = i;
-       }
-       if(f & 4)
-       {
-               self.origin_x = ReadCoord();
-               self.origin_y = ReadCoord();
-               self.origin_z = ReadCoord();
-       }
-       if(f & 1)
-       {
-               self.modelindex = ReadShort();
-               if(f & 0x80)
-               {
-                       if(self.modelindex)
-                       {
-                               self.mins_x = ReadCoord();
-                               self.mins_y = ReadCoord();
-                               self.mins_z = ReadCoord();
-                               self.maxs_x = ReadCoord();
-                               self.maxs_y = ReadCoord();
-                               self.maxs_z = ReadCoord();
-                       }
-                       else
-                       {
-                               self.mins    = '0 0 0';
-                               self.maxs_x = ReadCoord();
-                               self.maxs_y = ReadCoord();
-                               self.maxs_z = ReadCoord();
-                       }
-               }
-               else
-               {
-                       self.mins = self.maxs = '0 0 0';
-               }
-
-               self.cnt = ReadShort(); // effect number
-
-               if(f & 0x20)
-               {
-                       self.velocity = decompressShortVector(ReadShort());
-                       self.movedir = decompressShortVector(ReadShort());
-               }
-               else
-               {
-                       self.velocity = self.movedir = '0 0 0';
-               }
-               if(f & 0x40)
-               {
-                       self.waterlevel = ReadShort() / 16.0;
-                       self.count = ReadByte() / 16.0;
-               }
-               else
-               {
-                       self.waterlevel = 0;
-                       self.count = 1;
-               }
-               if(self.noise)
-                       strunzone(self.noise);
-               if(self.bgmscript)
-                       strunzone(self.bgmscript);
-               self.noise = strzone(ReadString());
-               if(self.noise != "")
-               {
-                       self.atten = ReadByte() / 64.0;
-                       self.volume = ReadByte() / 255.0;
-               }
-               self.bgmscript = strzone(ReadString());
-               if(self.bgmscript != "")
-               {
-                       self.bgmscriptattack = ReadByte() / 64.0;
-                       self.bgmscriptdecay = ReadByte() / 64.0;
-                       self.bgmscriptsustain = ReadByte() / 255.0;
-                       self.bgmscriptrelease = ReadByte() / 64.0;
-               }
-               BGMScript_InitEntity(self);
-       }
-
-       if(f & 2)
-       {
-               self.absolute = (self.impulse >= 0);
-               if(!self.absolute)
-               {
-                       v = self.maxs - self.mins;
-                       self.impulse *= -v.x * v.y * v.z / 262144; // relative: particles per 64^3 cube
-               }
-       }
-
-       if(f & 0x10)
-               self.absolute = 2;
-
-       setorigin(self, self.origin);
-       setsize(self, self.mins, self.maxs);
-       self.solid = SOLID_NOT;
-       self.draw = Draw_PointParticles;
-       self.entremove = Ent_PointParticles_Remove;
-}
-
-void Draw_Rain()
-{
-    te_particlerain(self.origin + self.mins, self.origin + self.maxs, self.velocity, floor(self.count * drawframetime + random()), self.glow_color);
-}
-
-void Draw_Snow()
-{
-    te_particlesnow(self.origin + self.mins, self.origin + self.maxs, self.velocity, floor(self.count * drawframetime + random()), self.glow_color);
-}
-
-void Ent_RainOrSnow()
-{
-       self.impulse = ReadByte(); // Rain, Snow, or Whatever
-       self.origin_x = ReadCoord();
-       self.origin_y = ReadCoord();
-       self.origin_z = ReadCoord();
-       self.maxs_x = ReadCoord();
-       self.maxs_y = ReadCoord();
-       self.maxs_z = ReadCoord();
-       self.velocity = decompressShortVector(ReadShort());
-       self.count = ReadShort() * 10;
-       self.glow_color = ReadByte(); // color
-
-       self.mins    = -0.5 * self.maxs;
-       self.maxs    =  0.5 * self.maxs;
-       self.origin  = self.origin - self.mins;
-
-       setorigin(self, self.origin);
-       setsize(self, self.mins, self.maxs);
-       self.solid = SOLID_NOT;
-       if(self.impulse)
-               self.draw = Draw_Rain;
-       else
-               self.draw = Draw_Snow;
-}
-
 void Net_ReadVortexBeamParticle()
 {
        vector shotorg, endpos;
index 12c999ba2bec219d5fb0adb0089aa66a271bfec1..a70aef86d703752b3e42b98e393558c64e9e3fed 100644 (file)
@@ -1,6 +1,5 @@
 #ifndef PARTICLES_H
 #define PARTICLES_H
-
 .int dphitcontentsmask;
 
 .int cnt; // effect number
 .float absolute; // 1 = count per second is absolute, 2 = only spawn at toggle
 .vector movedir; // trace direction
 
-void Draw_PointParticles();
-
-void Ent_PointParticles_Remove();
-
-void Ent_PointParticles();
-
 .float glow_color; // palette index
 
-void Draw_Rain();
-
-void Draw_Snow();
-
-void Ent_RainOrSnow();
-
 void Net_ReadVortexBeamParticle();
 #endif
index 0a9ac8cacb9167ddf05fc7eb762aafc8b02b26ff..d8b0aaad4f07d46a39fae6bf5098d48093a0d44c 100644 (file)
@@ -170,6 +170,8 @@ void skeleton_from_frames(entity e, float is_dead)
 
        if(!is_dead)
        {
+               if(self == csqcplayer)
+                       self.v_angle_x = input_angles_x;
                int i;
                for(i = 0; i < MAX_AIM_BONES; ++i)
                {
index f80da18d67309ace7482be0dd4ac37ac784cd1c0..a72a2104b267f0fa7ae9c51c7b733b6e4e0e31f2 100644 (file)
@@ -13,12 +13,10 @@ gibs.qc
 hook.qc
 hud_config.qc
 hud.qc
-laser.qc
 main.qc
 mapvoting.qc
 miscfunctions.qc
 modeleffects.qc
-movetypes.qc
 noise.qc
 particles.qc
 player_skeleton.qc
@@ -27,7 +25,6 @@ rubble.qc
 scoreboard.qc
 shownames.qc
 sortlist.qc
-target_music.qc
 teamradar.qc
 tturrets.qc
 tuba.qc
@@ -43,9 +40,11 @@ weapons/projectile.qc // TODO
 ../common/animdecide.qc
 ../common/buffs.qc
 ../common/mapinfo.qc
+../common/movetypes/include.qc
 ../common/nades.qc
 ../common/net_notice.qc
 ../common/notifications.qc
+../common/physics.qc
 ../common/playerstats.qc
 ../common/test.qc
 ../common/urllib.qc
@@ -59,14 +58,20 @@ weapons/projectile.qc // TODO
 
 ../common/weapons/weapons.qc // TODO
 
+../common/triggers/include.qc
+
 ../csqcmodellib/cl_model.qc
 ../csqcmodellib/cl_player.qc
 ../csqcmodellib/interpolate.qc
 
 ../server/movelib.qc
-../server/t_items.qc
+
+../server/mutators/mutator_multijump.qc
+
 ../server/vehicles/bumblebee.qc
 
+../server/t_items.qc
+
 ../warpzonelib/anglestransform.qc
 ../warpzonelib/client.qc
 ../warpzonelib/common.qc
diff --git a/qcsrc/client/target_music.qc b/qcsrc/client/target_music.qc
deleted file mode 100644 (file)
index 662a673..0000000
+++ /dev/null
@@ -1,183 +0,0 @@
-#include "target_music.qh"
-
-void TargetMusic_Advance()
-{
-       // run AFTER all the thinks!
-       entity best, e;
-       float vol, vol0;
-       best = music_default;
-       if(music_target && time < music_target.lifetime)
-               best = music_target;
-       if(music_trigger)
-               best = music_trigger;
-       for(e = world; (e = findfloat(e, enttype, ENT_CLIENT_TRIGGER_MUSIC)); ) if(e.noise)
-       {
-               vol0 = e.lastvol;
-               if(getsoundtime(e, CH_BGM_SINGLE) < 0)
-               {
-                       vol0 = -1;
-               }
-               if(e == best)
-               {
-                       // increase volume
-                       if(e.fade_time > 0)
-                               e.state = bound(0, e.state + frametime / e.fade_time, 1);
-                       else
-                               e.state = 1;
-               }
-               else
-               {
-                       // decrease volume
-                       if(e.fade_rate > 0)
-                               e.state = bound(0, e.state - frametime / e.fade_rate, 1);
-                       else
-                               e.state = 0;
-               }
-               vol = e.state * e.volume * autocvar_bgmvolume;
-               if(vol != vol0)
-               {
-                       if(vol0 < 0)
-                               sound(e, CH_BGM_SINGLE, e.noise, vol, ATTEN_NONE); // restart
-                       else
-                               sound(e, CH_BGM_SINGLE, "", vol, ATTEN_NONE);
-                       e.lastvol = vol;
-               }
-       }
-       music_trigger = world;
-
-       if(best)
-               bgmtime = getsoundtime(best, CH_BGM_SINGLE);
-       else
-               bgmtime = gettime(GETTIME_CDTRACK);
-}
-
-void Net_TargetMusic()
-{
-       int id = ReadShort();
-       float vol = ReadByte() / 255.0;
-       float fai = ReadByte() / 16.0;
-       float fao = ReadByte() / 16.0;
-       float tim = ReadByte();
-       string noi = ReadString();
-
-       entity e;
-       for(e = world; (e = findfloat(e, enttype, ENT_CLIENT_TRIGGER_MUSIC)); )
-       {
-               if(e.count == id)
-                       break;
-       }
-       if(!e)
-       {
-               e = spawn();
-               e.enttype = ENT_CLIENT_TRIGGER_MUSIC;
-               e.count = id;
-       }
-       if(e.noise != noi)
-       {
-               if(e.noise)
-                       strunzone(e.noise);
-               e.noise = strzone(noi);
-               precache_sound(e.noise);
-               sound(e, CH_BGM_SINGLE, e.noise, 0, ATTEN_NONE);
-               if(getsoundtime(e, CH_BGM_SINGLE) < 0)
-               {
-                       dprintf("Cannot initialize sound %s\n", e.noise);
-                       strunzone(e.noise);
-                       e.noise = string_null;
-               }
-       }
-       e.volume = vol;
-       e.fade_time = fai;
-       e.fade_rate = fao;
-       if(vol > 0)
-       {
-               if(tim == 0)
-               {
-                       music_default = e;
-                       if(!music_disabled)
-                       {
-                               e.state = 2;
-                               cvar_settemp("music_playlist_index", "-1"); // don't use playlists
-                               localcmd("cd stop\n"); // just in case
-                               music_disabled = 1;
-                       }
-               }
-               else
-               {
-                       music_target = e;
-                       e.lifetime = time + tim;
-               }
-       }
-}
-
-void Ent_TriggerMusic_Think()
-{
-       if(WarpZoneLib_BoxTouchesBrush(view_origin, view_origin, self, world))
-       {
-               music_trigger = self;
-       }
-       self.nextthink = time;
-}
-
-void Ent_TriggerMusic_Remove()
-{
-       if(self.noise)
-               strunzone(self.noise);
-       self.noise = string_null;
-}
-
-void Ent_ReadTriggerMusic()
-{
-       int f = ReadByte();
-       if(f & 4)
-       {
-               self.origin_x = ReadCoord();
-               self.origin_y = ReadCoord();
-               self.origin_z = ReadCoord();
-       }
-       if(f & 1)
-       {
-               self.modelindex = ReadShort();
-               if(self.modelindex)
-               {
-                       self.mins_x = ReadCoord();
-                       self.mins_y = ReadCoord();
-                       self.mins_z = ReadCoord();
-                       self.maxs_x = ReadCoord();
-                       self.maxs_y = ReadCoord();
-                       self.maxs_z = ReadCoord();
-               }
-               else
-               {
-                       self.mins    = '0 0 0';
-                       self.maxs_x = ReadCoord();
-                       self.maxs_y = ReadCoord();
-                       self.maxs_z = ReadCoord();
-               }
-
-               self.volume = ReadByte() / 255.0;
-               self.fade_time = ReadByte() / 16.0;
-               self.fade_rate = ReadByte() / 16.0;
-               string s = self.noise;
-               if(self.noise)
-                       strunzone(self.noise);
-               self.noise = strzone(ReadString());
-               if(self.noise != s)
-               {
-                       precache_sound(self.noise);
-                       sound(self, CH_BGM_SINGLE, self.noise, 0, ATTEN_NONE);
-                       if(getsoundtime(self, CH_BGM_SINGLE) < 0)
-                       {
-                               dprintf("Cannot initialize sound %s\n", self.noise);
-                               strunzone(self.noise);
-                               self.noise = string_null;
-                       }
-               }
-       }
-
-       setorigin(self, self.origin);
-       setsize(self, self.mins, self.maxs);
-       self.cnt = 1;
-       self.think = Ent_TriggerMusic_Think;
-       self.nextthink = time;
-}
diff --git a/qcsrc/client/target_music.qh b/qcsrc/client/target_music.qh
deleted file mode 100644 (file)
index c685214..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-#ifndef TARGET_MUSIC_H
-#define TARGET_MUSIC_H
-
-float music_disabled;
-entity music_default;
-entity music_target;
-entity music_trigger;
-// FIXME also control bgmvolume here, to not require a target_music for the default track.
-
-.int state;
-.float lastvol;
-
-void TargetMusic_Advance();
-
-void Net_TargetMusic();
-
-void Ent_TriggerMusic_Think();
-
-void Ent_TriggerMusic_Remove();
-
-void Ent_ReadTriggerMusic();
-#endif
index 290385e495fcdd389aa4629b22fbe773a159d961..69afac85a2e8961372c654298bc77ecc3ff2917b 100644 (file)
@@ -6,7 +6,7 @@
     #include "../../common/util.qh"
     #include "../../common/buffs.qh"
     #include "../autocvars.qh"
-    #include "../movetypes.qh"
+    #include "../../common/movetypes/movetypes.qh"
     #include "../prandom.qh"
     #include "../main.qh"
     #include "vehicles.qh"
index ee8ef320ae4643740f8bb275f848b9436914462f..f7be34504e3c3cabe8f1634994177d9e0093852b 100644 (file)
@@ -1738,6 +1738,7 @@ void CSQC_UpdateView(float w, float h)
        UpdateDamage();
        UpdateCrosshair();
        UpdateHitsound();
+       InitializeEntitiesRun();
 
        if(NextFrameCommand)
        {
index 788dd871eed84f74e2050375211ef9eca122ee20..4a8b156799cce1beb339e139e4d5f55375cd045c 100644 (file)
@@ -38,7 +38,7 @@ void drawquad(vector o, vector ri, vector up, string pic, vector rgb, float a, f
        R_EndPolygon();
 }
 
-void drawhealthbar(vector org, float rot, float h, vector sz, vector hotspot, float width, float height, float margin, float border, float align, vector rgb, float a, vector hrgb, float ha, float f)
+void drawhealthbar(vector org, float rot, float h, vector sz, vector hotspot, float width, float theheight, float margin, float border, float align, vector rgb, float a, vector hrgb, float ha, float f)
 {
        vector o, ri, up;
        float owidth; // outer width
@@ -56,13 +56,13 @@ void drawhealthbar(vector org, float rot, float h, vector sz, vector hotspot, fl
        up = rotate(up, rot);
 
        owidth = width + 2 * border;
-       o = o - up * (margin + border + height) + ri * (sz.x - owidth) * 0.5;
+       o = o - up * (margin + border + theheight) + ri * (sz.x - owidth) * 0.5;
 
        drawquad(o - up * border,                               ri * owidth,    up * border, "", rgb,  a,  f);
-       drawquad(o + up * height,                               ri * owidth,    up * border, "", rgb,  a,  f);
-       drawquad(o,                                             ri * border,    up * height, "", rgb,  a,  f);
-       drawquad(o + ri * (owidth - border),                    ri * border,    up * height, "", rgb,  a,  f);
-       drawquad(o + ri * (border + align * ((1 - h) * width)), ri * width * h, up * height, "", hrgb, ha, f);
+       drawquad(o + up * theheight,                               ri * owidth,    up * border, "", rgb,  a,  f);
+       drawquad(o,                                             ri * border,    up * theheight, "", rgb,  a,  f);
+       drawquad(o + ri * (owidth - border),                    ri * border,    up * theheight, "", rgb,  a,  f);
+       drawquad(o + ri * (border + align * ((1 - h) * width)), ri * width * h, up * theheight, "", hrgb, ha, f);
 }
 
 // returns location of sprite text
index 1d306fede6d6ce8733529821d373a7e16a371a0f..51236fdbba356deaa00b7656cb30c4a26ceda998 100644 (file)
@@ -469,6 +469,7 @@ void Ent_Projectile()
        if(!(self.count & 0x80))
                InterpolateOrigin_Note();
 
+       self.classname = "csqcprojectile";
        self.draw = Projectile_Draw;
        self.entremove = Ent_RemoveProjectile;
 }
index 45a65abbe96bd904bae1c3e93007637ba5d8b581..4c8d448dd4e1ba047479b83869fed05e32d4fb07 100644 (file)
@@ -1,6 +1,31 @@
 #ifndef CONSTANTS_H
 #define CONSTANTS_H
 
+// COMMIT-TODO: Update if necessary before committing
+// Revision 1: additional statistics sent (flag caps, returns, deaths)
+// Revision 2: Mapvote preview pictures
+// Revision 3: optimized map vote protocol
+// Revision 4: CSQC config var system
+// Revision 5: mapvote time fix
+// Revision 6: more robust against packet loss/delays, also show not yet connected clients
+// Revision 7: packet loss column
+// Revision 8: race
+// Revision 9: race delta
+// Revision 10: scoreboard force
+// Revision 11: scoreboard unforce; spectator support beginning
+// Revision 12: smaller scores updates (SERVER: requires new engine)
+// Revision 13: pointparticles
+// Revision 14: laser
+// Revision 15: zoom
+// Revision 16: multi-weapons
+// Revision 17: multi-weaponimpulses
+// Revision 18: warmup
+// Revision 19: fog
+// Revision 20: naggers
+// Revision 21: entcs for players optimized (position data down from 12 to 7 bytes); waypointsprites in csqc for team radar
+// Revision 22: hook shot origin
+#define CSQC_REVISION 22
+
 const int AS_STRING = 1;
 const int AS_INT = 2;
 const int AS_FLOAT_TRUNCATED = 2;
@@ -78,6 +103,17 @@ const int ENT_CLIENT_ELIMINATEDPLAYERS = 39;
 const int ENT_CLIENT_TURRET = 40;
 const int ENT_CLIENT_AUXILIARYXHAIR = 50;
 const int ENT_CLIENT_VEHICLE = 60;
+const int ENT_CLIENT_LADDER = 61;
+const int ENT_CLIENT_TRIGGER_PUSH = 62;
+const int ENT_CLIENT_TARGET_PUSH = 63;
+const int ENT_CLIENT_CONVEYOR = 64;
+const int ENT_CLIENT_DOOR = 65;
+const int ENT_CLIENT_TRAIN = 66;
+const int ENT_CLIENT_PLAT = 67;
+// 68
+const int ENT_CLIENT_SWAMP = 69;
+const int ENT_CLIENT_CORNER = 70;
+const int ENT_CLIENT_KEYLOCK = 71;
 
 const int ENT_CLIENT_HEALING_ORB = 80;
 
index fdc555e2460e69fd7f344de9f15dfcf65b2533f0..5e5ff42eb47e2893ca4699626fef5bfece9abe13 100644 (file)
@@ -8,7 +8,7 @@
 //#define CSQCMODEL_SUPPORT_GETTAGINFO_BEFORE_DRAW
 
 // server decides crouching, this lags, but so be it
-#define CSQCMODEL_SERVERSIDE_CROUCH
+//#define CSQCMODEL_SERVERSIDE_CROUCH
 
 // a hack for Xonotic
 #ifdef CSQC
@@ -52,7 +52,8 @@
                CSQCMODEL_PROPERTY(512, float, ReadApproxPastTime, WriteApproxPastTime, anim_upper_time) \
        CSQCMODEL_ENDIF \
        CSQCMODEL_PROPERTY(1024, float, ReadAngle, WriteAngle, v_angle_x) \
-       CSQCMODEL_PROPERTY_SCALED(4096, float, ReadByte, WriteByte, scale, 16, 0, 255)
+       CSQCMODEL_PROPERTY_SCALED(4096, float, ReadByte, WriteByte, scale, 16, 0, 255) \
+       CSQCMODEL_PROPERTY(8192, int, ReadInt24_t, WriteInt24_t, dphitcontentsmask)
 // TODO get rid of colormod/glowmod here, find good solution for vortex charge glowmod hack; also get rid of some useless properties on non-players that only exist for CopyBody
 
 // add hook function calls here
index 28430e9617e8a9a6666519a444d39df47303a103..3526a1f18bf66eab391b26c9fc5e6dbbf7648bd1 100644 (file)
@@ -19,6 +19,7 @@
     #include "../../server/campaign.qh"
     #include "../../server/command/common.qh"
     #include "../../server/command/cmd.qh"
+       #include "../triggers/triggers.qh"
     #include "../../csqcmodellib/sv_model.qh"
     #include "../../server/round_handler.qh"
     #include "../../server/tturrets/include/turrets.qh"
diff --git a/qcsrc/common/movetypes/include.qc b/qcsrc/common/movetypes/include.qc
new file mode 100644 (file)
index 0000000..e4d6f39
--- /dev/null
@@ -0,0 +1,4 @@
+#include "push.qc"
+#include "toss.qc"
+
+#include "movetypes.qc"
diff --git a/qcsrc/common/movetypes/include.qh b/qcsrc/common/movetypes/include.qh
new file mode 100644 (file)
index 0000000..126d3b6
--- /dev/null
@@ -0,0 +1,7 @@
+#ifndef MOVETYPES_INCLUDE_H
+#define MOVETYPES_INCLUDE_H
+
+#include "push.qh"
+#include "toss.qh"
+
+#endif
\ No newline at end of file
diff --git a/qcsrc/common/movetypes/movetypes.qc b/qcsrc/common/movetypes/movetypes.qc
new file mode 100644 (file)
index 0000000..5a45e98
--- /dev/null
@@ -0,0 +1,484 @@
+#include "include.qh"
+#include "../physics.qh"
+
+#if defined(CSQC)
+       #include "../../dpdefs/csprogsdefs.qh"
+       #include "../../client/defs.qh"
+       #include "../stats.qh"
+       #include "../util.qh"
+       #include "movetypes.qh"
+       #include "../../csqcmodellib/common.qh"
+       #include "../../server/t_items.qh"
+#elif defined(MENUQC)
+#elif defined(SVQC)
+       #include "../../server/autocvars.qh"
+#endif
+
+void _Movetype_CheckVelocity() // SV_CheckVelocity
+{
+}
+
+float _Movetype_CheckWater(entity ent) // SV_CheckWater
+{
+       vector point = ent.move_origin;
+       point_z += (ent.mins_z + 1);
+
+       int nativecontents = pointcontents(point);
+
+       if(ent.move_watertype)
+       if(ent.move_watertype != nativecontents)
+       {
+               //dprintf("_Movetype_CheckWater(): Original: '%d', New: '%d'\n", ent.move_watertype, nativecontents);
+               if(ent.contentstransition)
+                       ent.contentstransition(ent.move_watertype, nativecontents);
+       }
+
+       ent.move_waterlevel = 0;
+       ent.move_watertype = CONTENT_EMPTY;
+
+       int supercontents = Mod_Q1BSP_SuperContentsFromNativeContents(nativecontents);
+       if(supercontents & DPCONTENTS_LIQUIDSMASK)
+       {
+               ent.move_watertype = nativecontents;
+               ent.move_waterlevel = 1;
+               point_y = (ent.origin_y + ((ent.mins_z + ent.maxs_y) * 0.5));
+               if(Mod_Q1BSP_SuperContentsFromNativeContents(pointcontents(point)) & DPCONTENTS_LIQUIDSMASK)
+               {
+                       ent.move_waterlevel = 2;
+                       point_y = ent.origin_y + ent.view_ofs_y;
+                       if(Mod_Q1BSP_SuperContentsFromNativeContents(pointcontents(point)) & DPCONTENTS_LIQUIDSMASK)
+                               ent.move_waterlevel = 3;
+               }
+       }
+
+       return (ent.move_waterlevel > 1);
+}
+
+void _Movetype_CheckWaterTransition(entity ent) // SV_CheckWaterTransition
+{
+       float contents = pointcontents(ent.move_origin);
+
+       if(!ent.move_watertype)
+       {
+               // just spawned here
+               if(!autocvar_cl_gameplayfix_fixedcheckwatertransition)
+               {
+                       ent.move_watertype = contents;
+                       ent.move_waterlevel = 1;
+                       return;
+               }
+       }
+       else if(ent.move_watertype != contents)
+       {
+               //dprintf("_Movetype_CheckWaterTransition(): Origin: %s, Direct: '%d', Original: '%d', New: '%d'\n", vtos(ent.move_origin), pointcontents(ent.move_origin), ent.move_watertype, contents);
+               if(ent.contentstransition)
+                       ent.contentstransition(ent.move_watertype, contents);
+       }
+
+       if(contents <= CONTENT_WATER)
+       {
+               ent.move_watertype = contents;
+               ent.move_waterlevel = 1;
+       }
+       else
+       {
+               ent.move_watertype = CONTENT_EMPTY;
+               ent.move_waterlevel = (autocvar_cl_gameplayfix_fixedcheckwatertransition ? 0 : contents);
+       }
+}
+
+void _Movetype_Impact(entity oth) // SV_Impact
+{
+       entity oldother, oldself;
+
+       oldself = self;
+       oldother = other;
+
+       if(self.move_touch)
+       {
+               other = oth;
+
+               self.move_touch();
+
+               other = oldother;
+       }
+
+       if(oth.move_touch)
+       {
+               other = self;
+               self = oth;
+
+               self.move_touch();
+
+               self = oldself;
+               other = oldother;
+       }
+}
+
+void _Movetype_LinkEdict_TouchAreaGrid() // SV_LinkEdict_TouchAreaGrid
+{
+       entity e, oldself, oldother;
+
+       oldself = self;
+       oldother = other;
+
+       for(e = findradius(0.5 * (self.absmin + self.absmax), 0.5 * vlen(self.absmax - self.absmin)); e; e = e.chain)
+       {
+               if(e.move_touch)
+               if(boxesoverlap(e.absmin, e.absmax, oldself.absmin, oldself.absmax))
+               {
+                       self = e;
+                       other = oldself;
+
+                       trace_allsolid = false;
+                       trace_startsolid = false;
+                       trace_fraction = 1;
+                       trace_inwater = false;
+                       trace_inopen = true;
+                       trace_endpos = e.origin;
+                       trace_plane_normal = '0 0 1';
+                       trace_plane_dist = 0;
+                       trace_ent = oldself;
+
+                       e.move_touch();
+               }
+       }
+
+       other = oldother;
+       self = oldself;
+}
+
+void _Movetype_LinkEdict(float touch_triggers) // SV_LinkEdict
+{
+       vector mi, ma;
+       if(self.solid == SOLID_BSP)
+       {
+               // TODO set the absolute bbox
+               mi = self.mins;
+               ma = self.maxs;
+       }
+       else
+       {
+               mi = self.mins;
+               ma = self.maxs;
+       }
+       mi = mi + self.origin;
+       ma = ma + self.origin;
+
+       if(self.move_flags & FL_ITEM)
+       {
+               mi_x -= 15;
+               mi_y -= 15;
+               mi_z -= 1;
+               ma_x += 15;
+               ma_y += 15;
+               ma_z += 1;
+       }
+       else
+       {
+               mi_x -= 1;
+               mi_y -= 1;
+               mi_z -= 1;
+               ma_x += 1;
+               ma_y += 1;
+               ma_z += 1;
+       }
+
+       self.absmin = mi;
+       self.absmax = ma;
+
+       if(touch_triggers)
+               _Movetype_LinkEdict_TouchAreaGrid();
+}
+
+float _Movetype_TestEntityPosition(vector ofs) // SV_TestEntityPosition
+{
+       vector org;
+       org = self.move_origin + ofs;
+
+       int cont = self.dphitcontentsmask;
+       self.dphitcontentsmask = DPCONTENTS_SOLID;
+       tracebox(self.move_origin, self.mins, self.maxs, self.move_origin, MOVE_NOMONSTERS, self);
+       self.dphitcontentsmask = cont;
+
+       if(trace_startsolid)
+               return true;
+
+       if(vlen(trace_endpos - self.move_origin) > 0.0001)
+               self.move_origin = trace_endpos;
+       return false;
+}
+
+float _Movetype_UnstickEntity() // SV_UnstickEntity
+{
+       if(!_Movetype_TestEntityPosition('0 0 0'))
+               return true;
+       if(!_Movetype_TestEntityPosition('-1 0 0')) goto success;
+       if(!_Movetype_TestEntityPosition('1 0 0')) goto success;
+       if(!_Movetype_TestEntityPosition('0 -1 0')) goto success;
+       if(!_Movetype_TestEntityPosition('0 1 0')) goto success;
+       if(!_Movetype_TestEntityPosition('-1 -1 0')) goto success;
+       if(!_Movetype_TestEntityPosition('1 -1 0')) goto success;
+       if(!_Movetype_TestEntityPosition('-1 1 0')) goto success;
+       if(!_Movetype_TestEntityPosition('1 1 0')) goto success;
+       float i;
+       for(i = 1; i <= 17; ++i)
+       {
+               if(!_Movetype_TestEntityPosition('0 0 -1' * i)) goto success;
+               if(!_Movetype_TestEntityPosition('0 0 1' * i)) goto success;
+       }
+       dprintf("Can't unstick an entity (edict: %d, classname: %s, origin: %s)\n", num_for_edict(self), self.classname, vtos(self.move_origin));
+       return false;
+:success
+       dprintf("Sucessfully unstuck an entity (edict: %d, classname: %s, origin: %s)\n", num_for_edict(self), self.classname, vtos(self.move_origin));
+       _Movetype_LinkEdict(true);
+       return true;
+}
+
+vector _Movetype_ClipVelocity(vector vel, vector norm, float f) // SV_ClipVelocity
+{
+       vel = vel - ((vel * norm) * norm) * f;
+
+       if(vel_x > -0.1 && vel_x < 0.1) vel_x = 0;
+       if(vel_y > -0.1 && vel_y < 0.1) vel_y = 0;
+       if(vel_z > -0.1 && vel_z < 0.1) vel_z = 0;
+
+       return vel;
+}
+
+void _Movetype_PushEntityTrace(vector push)
+{
+       vector end;
+       float type;
+
+       end = self.move_origin + push;
+
+       if(self.move_nomonsters)
+               type = max(0, self.move_nomonsters);
+       else if(self.move_movetype == MOVETYPE_FLYMISSILE)
+               type = MOVE_MISSILE;
+       else if(self.solid == SOLID_TRIGGER || self.solid == SOLID_NOT)
+               type = MOVE_NOMONSTERS;
+       else
+               type = MOVE_NORMAL;
+
+       tracebox(self.move_origin, self.mins, self.maxs, end, type, self);
+}
+
+float _Movetype_PushEntity(vector push, float failonstartsolid) // SV_PushEntity
+{
+       _Movetype_PushEntityTrace(push);
+
+       if(trace_startsolid && failonstartsolid)
+               return trace_fraction;
+
+       self.move_origin = trace_endpos;
+
+       if(trace_fraction < 1)
+               if(self.solid >= SOLID_TRIGGER && (!(self.move_flags & FL_ONGROUND) || (self.move_groundentity != trace_ent)))
+                       _Movetype_Impact(trace_ent);
+
+       return trace_fraction;
+}
+
+
+.float ltime;
+.void() blocked;
+// matrix version of makevectors, sets v_forward, v_right and v_up
+void makevectors_matrix(vector myangles) // AngleVectorsFLU
+{
+       float angle, sr, sp, sy, cr, cp, cy;
+
+       v_forward = v_right = v_up = '0 0 0';
+
+       angle = myangles_y * (M_PI*2 / 360);
+       sy = sin(angle);
+       cy = cos(angle);
+       angle = myangles_x * (M_PI*2 / 360);
+       sp = sin(angle);
+       cp = cos(angle);
+       if(v_forward)
+       {
+               v_forward_x = cp*cy;
+               v_forward_y = cp*sy;
+               v_forward_z = -sp;
+       }
+       if(v_right || v_up)
+       {
+               if(myangles_z)
+               {
+                       angle = myangles_z * (M_PI*2 / 360);
+                       sr = sin(angle);
+                       cr = cos(angle);
+                       if(v_right)
+                       {
+                               v_right_x = sr*sp*cy+cr*-sy;
+                               v_right_y = sr*sp*sy+cr*cy;
+                               v_right_z = sr*cp;
+                       }
+                       if(v_up)
+                       {
+                               v_up_x = cr*sp*cy+-sr*-sy;
+                               v_up_y = cr*sp*sy+-sr*cy;
+                               v_up_z = cr*cp;
+                       }
+               }
+               else
+               {
+                       if(v_right)
+                       {
+                               v_right_x = -sy;
+                               v_right_y = cy;
+                               v_right_z = 0;
+                       }
+                       if(v_up)
+                       {
+                               v_up_x = sp*cy;
+                               v_up_y = sp*sy;
+                               v_up_z = cp;
+                       }
+               }
+       }
+}
+
+void _Movetype_Physics_Frame(float movedt)
+{
+       self.move_didgravity = -1;
+       switch(self.move_movetype)
+       {
+               case MOVETYPE_PUSH:
+               case MOVETYPE_FAKEPUSH:
+                       _Movetype_Physics_Pusher(movedt);
+                       break;
+               case MOVETYPE_NONE:
+                       break;
+               case MOVETYPE_FOLLOW:
+                       error("SV_Physics_Follow not implemented");
+                       break;
+               case MOVETYPE_NOCLIP:
+                       _Movetype_CheckWater(self);
+                       self.move_origin = self.move_origin + TICRATE * self.move_velocity;
+                       self.move_angles = self.move_angles + TICRATE * self.move_avelocity;
+                       _Movetype_LinkEdict(false);
+                       break;
+               case MOVETYPE_STEP:
+                       error("SV_Physics_Step not implemented");
+                       break;
+               case MOVETYPE_WALK:
+                       error("SV_Physics_Walk not implemented");
+                       break;
+               case MOVETYPE_TOSS:
+               case MOVETYPE_BOUNCE:
+               case MOVETYPE_BOUNCEMISSILE:
+               case MOVETYPE_FLYMISSILE:
+               case MOVETYPE_FLY:
+                       _Movetype_Physics_Toss(movedt);
+                       break;
+       }
+}
+
+void Movetype_Physics_NoMatchServer() // optimized
+{
+       float movedt;
+
+       movedt = time - self.move_time;
+       self.move_time = time;
+
+       _Movetype_Physics_Frame(movedt);
+       if(wasfreed(self))
+               return;
+
+       self.avelocity = self.move_avelocity;
+       self.velocity = self.move_velocity;
+       self.angles = self.move_angles;
+       setorigin(self, self.move_origin);
+}
+
+void Movetype_Physics_MatchServer(bool sloppy)
+{
+       Movetype_Physics_MatchTicrate(TICRATE, sloppy);
+}
+
+void Movetype_Physics_MatchTicrate(float tr, bool sloppy) // SV_Physics_Entity
+{
+       float n, i, dt, movedt;
+
+       if(tr <= 0)
+       {
+               Movetype_Physics_NoMatchServer();
+               return;
+       }
+
+       dt = time - self.move_time;
+
+       movedt = tr;
+       n = max(0, floor(dt / tr));
+       dt -= n * tr;
+       self.move_time += n * tr;
+
+       if(!self.move_didgravity)
+               self.move_didgravity = ((self.move_movetype == MOVETYPE_BOUNCE || self.move_movetype == MOVETYPE_TOSS) && !(self.move_flags & FL_ONGROUND));
+
+       for(i = 0; i < n; ++i)
+       {
+               _Movetype_Physics_Frame(movedt);
+               if(wasfreed(self))
+                       return;
+       }
+
+       self.avelocity = self.move_avelocity;
+
+       if(dt > 0 && self.move_movetype != MOVETYPE_NONE && !(self.move_flags & FL_ONGROUND))
+       {
+               // now continue the move from move_time to time
+               self.velocity = self.move_velocity;
+
+               if(self.move_didgravity > 0)
+               {
+                       if(GRAVITY_UNAFFECTED_BY_TICRATE)
+                       {
+                               if(self.gravity)
+                                       self.velocity_z -= 0.5 * dt * self.gravity * PHYS_GRAVITY;
+                               else
+                                       self.velocity_z -= 0.5 * dt * PHYS_GRAVITY;
+                       }
+                       else
+                       {
+                               if(self.gravity)
+                                       self.velocity_z -= dt * self.gravity * PHYS_GRAVITY;
+                               else
+                                       self.velocity_z -= dt * PHYS_GRAVITY;
+                       }
+               }
+
+               self.angles = self.move_angles + dt * self.avelocity;
+
+               if(sloppy || self.movetype == MOVETYPE_NOCLIP)
+               {
+                       setorigin(self, self.move_origin + dt * self.velocity);
+               }
+               else
+               {
+                       _Movetype_PushEntityTrace(dt * self.velocity);
+                       if(!trace_startsolid)
+                               setorigin(self, trace_endpos);
+               }
+
+               if(self.move_didgravity > 0)
+               {
+                       if(GRAVITY_UNAFFECTED_BY_TICRATE)
+                       {
+                               if(self.gravity)
+                                       self.velocity_z -= 0.5 * dt * self.gravity * PHYS_GRAVITY;
+                               else
+                                       self.velocity_z -= 0.5 * dt * PHYS_GRAVITY;
+                       }
+               }
+       }
+       else
+       {
+               self.velocity = self.move_velocity;
+               self.angles = self.move_angles;
+               setorigin(self, self.move_origin);
+       }
+}
diff --git a/qcsrc/common/movetypes/movetypes.qh b/qcsrc/common/movetypes/movetypes.qh
new file mode 100644 (file)
index 0000000..85c07fc
--- /dev/null
@@ -0,0 +1,93 @@
+#ifndef MOVETYPES_H
+#define MOVETYPES_H
+
+.float move_ltime;
+.void(void) move_think;
+.float move_nextthink;
+.void(void) move_blocked;
+
+.float move_movetype;
+.float move_time;
+.vector move_origin;
+.vector move_angles;
+.vector move_velocity;
+.vector move_avelocity;
+.int move_flags;
+.int move_watertype;
+.int move_waterlevel;
+.void(void) move_touch;
+.void(float, float) contentstransition;
+.float move_bounce_factor;
+.float move_bounce_stopspeed;
+.float move_nomonsters; // -1 for MOVE_NORMAL, otherwise a MOVE_ constant
+
+// should match sv_gameplayfix_fixedcheckwatertransition
+float autocvar_cl_gameplayfix_fixedcheckwatertransition = 1;
+
+#ifdef SVQC
+#define GRAVITY_UNAFFECTED_BY_TICRATE autocvar_sv_gameplayfix_gravityunaffectedbyticrate
+
+#define TICRATE sys_frametime
+#elif defined(CSQC)
+#define GRAVITY_UNAFFECTED_BY_TICRATE (getstati(STAT_MOVEFLAGS) & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE)
+
+#define TICRATE ticrate
+#endif
+
+.entity move_groundentity; // FIXME add move_groundnetworkentity?
+.float move_suspendedinair;
+.float move_didgravity;
+
+void _Movetype_CheckVelocity();
+void _Movetype_CheckWaterTransition(entity ent);
+void _Movetype_LinkEdict_TouchAreaGrid();
+void _Movetype_LinkEdict(float touch_triggers);
+float _Movetype_TestEntityPosition(vector ofs);
+float _Movetype_UnstickEntity();
+vector _Movetype_ClipVelocity(vector vel, vector norm, float f);
+void _Movetype_PushEntityTrace(vector push);
+float _Movetype_PushEntity(vector push, float failonstartsolid);
+void makevectors_matrix(vector myangles);
+
+void Movetype_Physics_MatchTicrate(float tr, bool sloppy);
+void Movetype_Physics_MatchServer(bool sloppy);
+void Movetype_Physics_NoMatchServer();
+void _Movetype_LinkEdict(float touch_triggers);
+void _Movetype_LinkEdict_TouchAreaGrid();
+
+float _Movetype_UnstickEntity();
+
+const int MAX_CLIP_PLANES = 5;
+
+#ifdef CSQC
+const int MOVETYPE_NONE                                = 0;
+const int MOVETYPE_ANGLENOCLIP     = 1;
+const int MOVETYPE_ANGLECLIP       = 2;
+const int MOVETYPE_WALK                                = 3;
+const int MOVETYPE_STEP                                = 4;
+const int MOVETYPE_FLY                         = 5;
+const int MOVETYPE_TOSS                                = 6;
+const int MOVETYPE_PUSH                                = 7;
+const int MOVETYPE_NOCLIP                  = 8;
+const int MOVETYPE_FLYMISSILE      = 9;
+const int MOVETYPE_BOUNCE                  = 10;
+const int MOVETYPE_BOUNCEMISSILE       = 11;   // Like bounce but doesn't lose speed on bouncing
+const int MOVETYPE_FOLLOW           = 12;
+const int MOVETYPE_FLY_WORLDONLY    = 33;
+
+const int FL_ITEM                   = 256;
+const int FL_ONGROUND                          = 512;
+#endif
+
+const int MOVETYPE_FAKEPUSH         = 13;
+
+const float MOVEFLAG_Q2AIRACCELERATE            = 1;
+const float MOVEFLAG_NOGRAVITYONGROUND          = 2;
+const float MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE = 4;
+
+#ifdef CSQC
+// TODO: figure out server's version of this
+#define moveflags (getstati(STAT_MOVEFLAGS))
+#endif
+
+#endif
diff --git a/qcsrc/common/movetypes/push.qc b/qcsrc/common/movetypes/push.qc
new file mode 100644 (file)
index 0000000..7ae5e2e
--- /dev/null
@@ -0,0 +1,171 @@
+void _Movetype_PushMove(float dt) // SV_PushMove
+{
+       bool rotated;
+       int savesolid;
+       float movetime2, pushltime;
+       vector move, move1, moveangle, pushorig, pushang;
+       vector a;
+       vector pivot;
+       entity oldself;
+       entity check;
+
+       if(self.move_velocity == '0 0 0' && self.move_avelocity == '0 0 0')
+       {
+               self.move_ltime += dt;
+               return;
+       }
+
+       switch(self.solid)
+       {
+       // LordHavoc: valid pusher types
+       case SOLID_BSP:
+       case SOLID_BBOX:
+       case SOLID_SLIDEBOX:
+       case SOLID_CORPSE: // LordHavoc: this would be weird...
+               break;
+       // LordHavoc: no collisions
+       case SOLID_NOT:
+       case SOLID_TRIGGER:
+               self.move_origin = self.move_origin + dt * self.move_velocity;
+               self.move_angles = self.move_angles + dt * self.move_avelocity;
+               self.move_angles_x -= 360.0 * floor(self.move_angles_x * (1.0 / 360.0));
+               self.move_angles_y -= 360.0 * floor(self.move_angles_y * (1.0 / 360.0));
+               self.move_angles_z -= 360.0 * floor(self.move_angles_z * (1.0 / 360.0));
+               self.move_ltime += dt;
+               _Movetype_LinkEdict(true);
+               return;
+       default:
+               dprintf("_Movetype_PushMove: entity %e, unrecognized solid type %d\n", self, self.solid);
+               return;
+       }
+
+       rotated = (self.move_angles * self.move_angles) + (self.move_avelocity * self.move_avelocity) > 0;
+
+       movetime2 = dt;
+
+       move1 = self.move_velocity * movetime2;
+       moveangle = self.move_avelocity * movetime2;
+
+       a = -moveangle;
+       makevectors_matrix(a);
+
+       pushorig = self.move_origin;
+       pushang = self.move_angles;
+       pushltime = self.move_ltime;
+
+// move the pusher to its final position
+
+       self.move_origin = self.move_origin + dt * self.move_velocity;
+       self.move_angles = self.move_angles + dt * self.move_avelocity;
+       
+       self.move_ltime += dt;
+       _Movetype_LinkEdict(true);
+
+       savesolid = self.solid;
+
+       if(self.move_movetype != MOVETYPE_FAKEPUSH)
+       for(check = findradius(0.5 * (self.absmin + self.absmax), 0.5 * vlen(self.absmax - self.absmin)); check; check = check.chain)
+       {
+               switch(check.move_movetype)
+               {
+               case MOVETYPE_NONE:
+               case MOVETYPE_PUSH:
+               case MOVETYPE_FOLLOW:
+               case MOVETYPE_NOCLIP:
+               case MOVETYPE_FLY_WORLDONLY:
+                       continue;
+               default:
+                       break;
+               }
+
+               if(check.owner == self)
+                       continue;
+
+               if(self.owner == check)
+                       continue;
+
+               pivot = check.mins + 0.5 * (check.maxs - check.mins);
+
+               if (rotated)
+               {
+                       vector org2;
+                       vector org = check.move_origin - self.move_origin;
+                       org = org + pivot;
+                       org2_x = dotproduct(org, v_forward);
+                       org2_y = dotproduct(org, v_right);
+                       org2_z = dotproduct(org, v_up);
+                       move = org2 - org;
+                       move = move + move1;
+               }
+               else
+                       move = move1;
+
+               // physics objects need better collisions than this code can do
+               if(check.move_movetype == 32) // MOVETYPE_PHYSICS
+               {
+                       check.move_origin = check.move_origin + move;
+                       oldself = self;
+                       self = check;
+                       _Movetype_LinkEdict(true);
+                       self = oldself;
+                       continue;
+               }
+
+               // try moving the contacted entity
+               self.solid = SOLID_NOT;
+               oldself = self;
+               self = check;
+               if(!_Movetype_PushEntity(move, true))
+               {
+                       self = oldself;
+                       // entity "check" got teleported
+                       check.move_angles_y += trace_fraction * moveangle_y;
+                       self.solid = savesolid;
+                       continue; // pushed enough
+               }
+               self = oldself;
+               // FIXME: turn players specially
+               check.move_angles_y += trace_fraction * moveangle_y;
+               self.solid = savesolid;
+
+               // this trace.fraction < 1 check causes items to fall off of pushers
+               // if they pass under or through a wall
+               // the groundentity check causes items to fall off of ledges
+               if(check.move_movetype != MOVETYPE_WALK && (trace_fraction < 1 || check.move_groundentity != self))
+                       check.move_flags &= ~FL_ONGROUND;
+       }
+
+       self.move_angles_x -= 360.0 * floor(self.move_angles_x * (1.0 / 360.0));
+       self.move_angles_y -= 360.0 * floor(self.move_angles_y * (1.0 / 360.0));
+       self.move_angles_z -= 360.0 * floor(self.move_angles_z * (1.0 / 360.0));
+}
+
+void _Movetype_Physics_Pusher(float dt) // SV_Physics_Pusher
+{
+       float thinktime, oldltime, movetime;
+
+       oldltime = self.move_ltime;
+
+       thinktime = self.move_nextthink;
+       if(thinktime < self.move_ltime + dt)
+       {
+               movetime = thinktime - self.move_ltime;
+               if(movetime < 0)
+                       movetime = 0;
+       }
+       else
+               movetime = dt;
+
+       if(movetime)
+               // advances self.move_ltime if not blocked
+               _Movetype_PushMove(movetime);
+
+       if(thinktime > oldltime && thinktime <= self.move_ltime)
+       {
+               self.move_nextthink = 0;
+               self.move_time = time;
+               other = world;
+               if(self.move_think)
+                       self.move_think();
+       }
+}
diff --git a/qcsrc/common/movetypes/push.qh b/qcsrc/common/movetypes/push.qh
new file mode 100644 (file)
index 0000000..44d5442
--- /dev/null
@@ -0,0 +1,6 @@
+#ifndef MOVETYPE_PUSH_H
+#define MOVETYPE_PUSH_H
+
+void _Movetype_Physics_Pusher(float dt);
+
+#endif
\ No newline at end of file
diff --git a/qcsrc/common/movetypes/toss.qc b/qcsrc/common/movetypes/toss.qc
new file mode 100644 (file)
index 0000000..df9c9b8
--- /dev/null
@@ -0,0 +1,131 @@
+#include "../physics.qh"
+
+void _Movetype_Physics_Toss(float dt) // SV_Physics_Toss
+{
+       if(self.move_flags & FL_ONGROUND)
+       {
+               if(self.move_velocity_z >= 1/32)
+                       self.move_flags &= ~FL_ONGROUND;
+               else if(!self.move_groundentity)
+                       return;
+               else if(self.move_suspendedinair && wasfreed(self.move_groundentity))
+               {
+                       self.move_groundentity = world;
+                       return;
+               }
+       }
+
+       self.move_suspendedinair = false;
+
+       _Movetype_CheckVelocity();
+
+       if(self.move_movetype == MOVETYPE_BOUNCE || self.move_movetype == MOVETYPE_TOSS)
+       {
+               self.move_didgravity = 1;
+               if(GRAVITY_UNAFFECTED_BY_TICRATE)
+               {
+                       if(self.gravity)
+                               self.move_velocity_z -= 0.5 * dt * self.gravity * PHYS_GRAVITY;
+                       else
+                               self.move_velocity_z -= 0.5 * dt * PHYS_GRAVITY;
+               }
+               else
+               {
+                       if(self.gravity)
+                               self.move_velocity_z -= dt * self.gravity * PHYS_GRAVITY;
+                       else
+                               self.move_velocity_z -= dt * PHYS_GRAVITY;
+               }
+       }
+
+       self.move_angles = self.move_angles + self.move_avelocity * dt;
+
+       float movetime, bump;
+       movetime = dt;
+       for(bump = 0; bump < MAX_CLIP_PLANES && movetime > 0; ++bump)
+       {
+               vector move;
+               move = self.move_velocity * movetime;
+               _Movetype_PushEntity(move, true);
+               if(wasfreed(self))
+                       return;
+
+               if(trace_startsolid)
+               {
+                       _Movetype_UnstickEntity();
+                       _Movetype_PushEntity(move, false);
+                       if(wasfreed(self))
+                               return;
+               }
+
+               if(trace_fraction == 1)
+                       break;
+
+               movetime *= 1 - min(1, trace_fraction);
+
+               if(self.move_movetype == MOVETYPE_BOUNCEMISSILE)
+               {
+                       self.move_velocity = _Movetype_ClipVelocity(self.move_velocity, trace_plane_normal, 2.0);
+                       self.move_flags &= ~FL_ONGROUND;
+               }
+               else if(self.move_movetype == MOVETYPE_BOUNCE)
+               {
+                       float d, bouncefac, bouncestop;
+
+                       bouncefac = self.move_bounce_factor;     if(!bouncefac)  bouncefac = 0.5;
+                       bouncestop = self.move_bounce_stopspeed; if(!bouncestop) bouncestop = 60 / 800;
+                       if(self.gravity)
+                               bouncestop *= self.gravity * PHYS_GRAVITY;
+                       else
+                               bouncestop *= PHYS_GRAVITY;
+
+                       self.move_velocity = _Movetype_ClipVelocity(self.move_velocity, trace_plane_normal, 1 + bouncefac);
+
+                       d = trace_plane_normal * self.move_velocity;
+                       if(trace_plane_normal_z > 0.7 && d < bouncestop && d > -bouncestop)
+                       {
+                               self.move_flags |= FL_ONGROUND;
+                               self.move_groundentity = trace_ent;
+                               self.move_velocity = '0 0 0';
+                               self.move_avelocity = '0 0 0';
+                       }
+                       else
+                               self.move_flags &= ~FL_ONGROUND;
+               }
+               else
+               {
+                       self.move_velocity = _Movetype_ClipVelocity(self.move_velocity, trace_plane_normal, 1.0);
+                       if(trace_plane_normal_z > 0.7)
+                       {
+                               self.move_flags |= FL_ONGROUND;
+                               self.move_groundentity = trace_ent;
+                               if(trace_ent.solid == SOLID_BSP)
+                                       self.move_suspendedinair = true;
+                               self.move_velocity = '0 0 0';
+                               self.move_avelocity = '0 0 0';
+                       }
+                       else
+                               self.move_flags &= ~FL_ONGROUND;
+               }
+
+               // DP revision 8905 (just, WHY...)
+               if(self.move_movetype == MOVETYPE_BOUNCEMISSILE)
+                       break;
+
+               // DP revision 8918 (WHY...)
+               if(self.move_flags & FL_ONGROUND)
+                       break;
+       }
+
+       if(GRAVITY_UNAFFECTED_BY_TICRATE)
+       if(self.move_didgravity > 0)
+       if(!(self.move_flags & FL_ONGROUND))
+       {
+               if(self.gravity)
+                       self.move_velocity_z -= 0.5 * dt * self.gravity * PHYS_GRAVITY;
+               else
+                       self.move_velocity_z -= 0.5 * dt * PHYS_GRAVITY;
+       }
+
+       _Movetype_CheckWaterTransition(self);
+}
diff --git a/qcsrc/common/movetypes/toss.qh b/qcsrc/common/movetypes/toss.qh
new file mode 100644 (file)
index 0000000..9e11595
--- /dev/null
@@ -0,0 +1,6 @@
+#ifndef MOVETYPE_TOSS_H
+#define MOVETYPE_TOSS_H
+
+void _Movetype_Physics_Toss(float dt);
+
+#endif
\ No newline at end of file
index 4225a19f03cb76e98b5afa198312d94f2c575cfc..fcdc5533e4c1be7b9f94902074c62761b119c4d1 100644 (file)
@@ -3,7 +3,7 @@
        #include "../client/defs.qh"
        #include "nades.qh"
        #include "buffs.qh"
-       #include "../client/movetypes.qh"
+       #include "../common/movetypes/movetypes.qh"
        #include "../server/tturrets/include/turrets_early.qh"
        #include "../client/main.qh"
        #include "../csqcmodellib/cl_model.qh"
index bc520e59d57005b7547d8ddbeba0a3cd6a5b9584..fb9958b08fcbd186fd151b65afb89462d442a357 100644 (file)
@@ -47,9 +47,6 @@ void sv_notice_toall(string _notice, float _howlong, float _modal)
 #endif // SVQC
 
 #ifdef CSQC
-void SUB_Remove()
-{ remove(self); }
-
 void cl_notice_read()
 {
     entity _notice;
diff --git a/qcsrc/common/physics.qc b/qcsrc/common/physics.qc
new file mode 100644 (file)
index 0000000..ad5bc16
--- /dev/null
@@ -0,0 +1,1936 @@
+#include "physics.qh"
+#include "triggers/trigger/swamp.qh"
+#include "triggers/trigger/jumppads.qh"
+
+#ifdef SVQC
+
+#include "../server/miscfunctions.qh"
+
+void Physics_AddStats()
+{
+       // g_movementspeed hack
+       addstat(STAT_MOVEVARS_AIRSPEEDLIMIT_NONQW, AS_FLOAT, stat_sv_airspeedlimit_nonqw);
+       addstat(STAT_MOVEVARS_MAXSPEED, AS_FLOAT, stat_sv_maxspeed);
+       addstat(STAT_MOVEVARS_AIRACCEL_QW, AS_FLOAT, stat_sv_airaccel_qw);
+       addstat(STAT_MOVEVARS_AIRSTRAFEACCEL_QW, AS_FLOAT, stat_sv_airstrafeaccel_qw);
+       addstat(STAT_MOVEVARS_HIGHSPEED, AS_FLOAT, stat_movement_highspeed);
+
+       // jet pack
+       addstat(STAT_JETPACK_ACCEL_SIDE, AS_FLOAT, stat_jetpack_accel_side);
+       addstat(STAT_JETPACK_ACCEL_UP, AS_FLOAT, stat_jetpack_accel_up);
+       addstat(STAT_JETPACK_ANTIGRAVITY, AS_FLOAT, stat_jetpack_antigravity);
+       addstat(STAT_JETPACK_FUEL, AS_FLOAT, stat_jetpack_fuel);
+       addstat(STAT_JETPACK_MAXSPEED_UP, AS_FLOAT, stat_jetpack_maxspeed_up);
+       addstat(STAT_JETPACK_MAXSPEED_SIDE, AS_FLOAT, stat_jetpack_maxspeed_side);
+
+       // hack to fix track_canjump
+       addstat(STAT_MOVEVARS_TRACK_CANJUMP, AS_INT, cvar_cl_movement_track_canjump);
+
+       // double jump
+       addstat(STAT_DOUBLEJUMP, AS_INT, stat_doublejump);
+
+       // jump speed caps
+       addstat(STAT_MOVEVARS_JUMPSPEEDCAP_MIN, AS_FLOAT, stat_jumpspeedcap_min);
+       addstat(STAT_MOVEVARS_JUMPSPEEDCAP_MIN, AS_FLOAT, stat_jumpspeedcap_min);
+       addstat(STAT_MOVEVARS_JUMPSPEEDCAP_DISABLE_ONRAMPS, AS_INT, stat_jumpspeedcap_disable_onramps);
+
+       // hacks
+       addstat(STAT_MOVEVARS_FRICTION_ONLAND, AS_FLOAT, stat_sv_friction_on_land);
+       addstat(STAT_MOVEVARS_FRICTION_SLICK, AS_FLOAT, stat_sv_friction_slick);
+       addstat(STAT_GAMEPLAYFIX_EASIERWATERJUMP, AS_INT, stat_gameplayfix_easierwaterjump);
+}
+
+void Physics_UpdateStats(float maxspd_mod)
+{
+       self.stat_sv_airaccel_qw = AdjustAirAccelQW(autocvar_sv_airaccel_qw, maxspd_mod);
+       if (autocvar_sv_airstrafeaccel_qw)
+               self.stat_sv_airstrafeaccel_qw = AdjustAirAccelQW(autocvar_sv_airstrafeaccel_qw, maxspd_mod);
+       else
+               self.stat_sv_airstrafeaccel_qw = 0;
+       self.stat_sv_airspeedlimit_nonqw = autocvar_sv_airspeedlimit_nonqw * maxspd_mod;
+       self.stat_sv_maxspeed = autocvar_sv_maxspeed * maxspd_mod; // also slow walking
+       self.stat_movement_highspeed = PHYS_HIGHSPEED; // TODO: remove this!
+
+       self.stat_doublejump = PHYS_DOUBLEJUMP;
+
+       self.stat_jetpack_antigravity = PHYS_JETPACK_ANTIGRAVITY;
+       self.stat_jetpack_accel_up = PHYS_JETPACK_ACCEL_UP;
+       self.stat_jetpack_accel_side = PHYS_JETPACK_ACCEL_SIDE;
+       self.stat_jetpack_maxspeed_side = PHYS_JETPACK_MAXSPEED_SIDE;
+       self.stat_jetpack_maxspeed_up = PHYS_JETPACK_MAXSPEED_UP;
+       self.stat_jetpack_fuel = PHYS_JETPACK_FUEL;
+
+       self.stat_jumpspeedcap_min = PHYS_JUMPSPEEDCAP_MIN;
+       self.stat_jumpspeedcap_max = PHYS_JUMPSPEEDCAP_MAX;
+       self.stat_jumpspeedcap_disable_onramps = PHYS_JUMPSPEEDCAP_DISABLE_ONRAMPS;
+
+       self.stat_sv_friction_on_land = PHYS_FRICTION_ONLAND;
+       self.stat_sv_friction_slick = PHYS_FRICTION_SLICK;
+
+       self.stat_gameplayfix_easierwaterjump = GAMEPLAYFIX_EASIERWATERJUMP;
+}
+#endif
+
+float IsMoveInDirection(vector mv, float angle) // key mix factor
+{
+       if (mv_x == 0 && mv_y == 0)
+               return 0; // avoid division by zero
+       angle -= RAD2DEG * atan2(mv_y, mv_x);
+       angle = remainder(angle, 360) / 45;
+       return angle > 1 ? 0 : angle < -1 ? 0 : 1 - fabs(angle);
+}
+
+float GeomLerp(float a, float lerp, float b)
+{
+       return a == 0 ? (lerp < 1 ? 0 : b)
+               : b == 0 ? (lerp > 0 ? 0 : a)
+               : a * pow(fabs(b / a), lerp);
+}
+
+noref float pmove_waterjumptime;
+
+const float unstick_count = 27;
+vector unstick_offsets[unstick_count] =
+{
+// 1 no nudge (just return the original if this test passes)
+       '0.000   0.000  0.000',
+// 6 simple nudges
+       ' 0.000  0.000  0.125', '0.000  0.000 -0.125',
+       '-0.125  0.000  0.000', '0.125  0.000  0.000',
+       ' 0.000 -0.125  0.000', '0.000  0.125  0.000',
+// 4 diagonal flat nudges
+       '-0.125 -0.125  0.000', '0.125 -0.125  0.000',
+       '-0.125  0.125  0.000', '0.125  0.125  0.000',
+// 8 diagonal upward nudges
+       '-0.125  0.000  0.125', '0.125  0.000  0.125',
+       ' 0.000 -0.125  0.125', '0.000  0.125  0.125',
+       '-0.125 -0.125  0.125', '0.125 -0.125  0.125',
+       '-0.125  0.125  0.125', '0.125  0.125  0.125',
+// 8 diagonal downward nudges
+       '-0.125  0.000 -0.125', '0.125  0.000 -0.125',
+       ' 0.000 -0.125 -0.125', '0.000  0.125 -0.125',
+       '-0.125 -0.125 -0.125', '0.125 -0.125 -0.125',
+       '-0.125  0.125 -0.125', '0.125  0.125 -0.125',
+};
+
+void PM_ClientMovement_Unstick()
+{
+       float i;
+       for (i = 0; i < unstick_count; i++)
+       {
+               vector neworigin = unstick_offsets[i] + self.origin;
+               tracebox(neworigin, PL_CROUCH_MIN, PL_CROUCH_MAX, neworigin, MOVE_NORMAL, self);
+               if (!trace_startsolid)
+               {
+                       setorigin(self, neworigin);
+                       return;// true;
+               }
+       }
+}
+
+void PM_ClientMovement_UpdateStatus(bool ground)
+{
+       // make sure player is not stuck
+       PM_ClientMovement_Unstick();
+
+       // set crouched
+       if (PHYS_INPUT_BUTTON_CROUCH(self))
+       {
+               // wants to crouch, this always works..
+               if (!IS_DUCKED(self))
+                       SET_DUCKED(self);
+       }
+       else
+       {
+               // wants to stand, if currently crouching we need to check for a
+               // low ceiling first
+               if (IS_DUCKED(self))
+               {
+                       tracebox(self.origin, PL_MIN, PL_MAX, self.origin, MOVE_NORMAL, self);
+                       if (!trace_startsolid)
+                               UNSET_DUCKED(self);
+               }
+       }
+
+       // set onground
+       vector origin1 = self.origin + '0 0 1';
+       vector origin2 = self.origin - '0 0 1';
+
+       if(ground)
+       {
+               tracebox(origin1, self.mins, self.maxs, origin2, MOVE_NORMAL, self);
+               if (trace_fraction < 1.0 && trace_plane_normal_z > 0.7)
+               {
+                       SET_ONGROUND(self);
+
+                       // this code actually "predicts" an impact; so let's clip velocity first
+                       float f = self.velocity * trace_plane_normal;
+                       self.velocity -= f * trace_plane_normal;
+               }
+               else
+                       UNSET_ONGROUND(self);
+       }
+
+       // set watertype/waterlevel
+       origin1 = self.origin;
+       origin1_z += self.mins_z + 1;
+       self.waterlevel = WATERLEVEL_NONE;
+
+       self.watertype = (pointcontents(origin1) == CONTENT_WATER);
+
+       if(self.watertype)
+       {
+               self.waterlevel = WATERLEVEL_WETFEET;
+               origin1_z = self.origin_z + (self.mins_z + self.maxs_z) * 0.5;
+               if(pointcontents(origin1) == CONTENT_WATER)
+               {
+                       self.waterlevel = WATERLEVEL_SWIMMING;
+                       origin1_z = self.origin_z + 22;
+                       if(pointcontents(origin1) == CONTENT_WATER)
+                               self.waterlevel = WATERLEVEL_SUBMERGED;
+               }
+       }
+
+       if(IS_ONGROUND(self) || self.velocity_z <= 0 || pmove_waterjumptime <= 0)
+               pmove_waterjumptime = 0;
+}
+
+void PM_ClientMovement_Move()
+{
+#ifdef CSQC
+       float t = PHYS_INPUT_TIMELENGTH;
+       vector primalvelocity = self.velocity;
+       PM_ClientMovement_UpdateStatus(false);
+       float bump = 0;
+       for (bump = 0; bump < MAX_CLIP_PLANES && (self.velocity * self.velocity) > 0; bump++)
+       {
+               vector neworigin = self.origin + t * self.velocity;
+               tracebox(self.origin, self.mins, self.maxs, neworigin, MOVE_NORMAL, self);
+               float old_trace1_fraction = trace_fraction;
+               vector old_trace1_endpos = trace_endpos;
+               vector old_trace1_plane_normal = trace_plane_normal;
+               if (trace_fraction < 1 && trace_plane_normal_z == 0)
+               {
+                       // may be a step or wall, try stepping up
+                       // first move forward at a higher level
+                       vector currentorigin2 = self.origin;
+                       currentorigin2_z += PHYS_STEPHEIGHT;
+                       vector neworigin2 = neworigin;
+                       neworigin2_z = self.origin_z + PHYS_STEPHEIGHT;
+                       tracebox(currentorigin2, self.mins, self.maxs, neworigin2, MOVE_NORMAL, self);
+                       if (!trace_startsolid)
+                       {
+                               // then move down from there
+                               currentorigin2 = trace_endpos;
+                               neworigin2 = trace_endpos;
+                               neworigin2_z = self.origin_z;
+                               float old_trace2_fraction = trace_fraction;
+                               vector old_trace2_plane_normal = trace_plane_normal;
+                               tracebox(currentorigin2, self.mins, self.maxs, neworigin2, MOVE_NORMAL, self);
+                               // accept the new trace if it made some progress
+                               if (fabs(trace_endpos_x - old_trace1_endpos_x) >= 0.03125 || fabs(trace_endpos_y - old_trace1_endpos_y) >= 0.03125)
+                               {
+                                       trace_fraction = old_trace2_fraction;
+                                       trace_endpos = trace_endpos;
+                                       trace_plane_normal = old_trace2_plane_normal;
+                               }
+                               else
+                               {
+                                       trace_fraction = old_trace1_fraction;
+                                       trace_endpos = old_trace1_endpos;
+                                       trace_plane_normal = old_trace1_plane_normal;
+                               }
+                       }
+               }
+
+               // check if it moved at all
+               if (trace_fraction >= 0.001)
+                       setorigin(self, trace_endpos);
+
+               // check if it moved all the way
+               if (trace_fraction == 1)
+                       break;
+
+               // this is only really needed for nogravityonground combined with gravityunaffectedbyticrate
+               // <LordHavoc> I'm pretty sure I commented it out solely because it seemed redundant
+               // this got commented out in a change that supposedly makes the code match QW better
+               // so if this is broken, maybe put it in an if (cls.protocol != PROTOCOL_QUAKEWORLD) block
+               if (trace_plane_normal_z > 0.7)
+                       SET_ONGROUND(self);
+
+               t -= t * trace_fraction;
+
+               float f = self.velocity * trace_plane_normal;
+               self.velocity -= f * trace_plane_normal;
+       }
+       if (pmove_waterjumptime > 0)
+               self.velocity = primalvelocity;
+#endif
+}
+
+void CPM_PM_Aircontrol(vector wishdir, float wishspeed)
+{
+       float k = 32 * (2 * IsMoveInDirection(PHYS_INPUT_MOVEVALUES(self), 0) - 1);
+       if (k <= 0)
+               return;
+
+       k *= bound(0, wishspeed / PHYS_MAXAIRSPEED, 1);
+
+       float zspeed = self.velocity_z;
+       self.velocity_z = 0;
+       float xyspeed = vlen(self.velocity);
+       self.velocity = normalize(self.velocity);
+
+       float dot = self.velocity * wishdir;
+
+       if (dot > 0) // we can't change direction while slowing down
+       {
+               k *= pow(dot, PHYS_AIRCONTROL_POWER) * PHYS_INPUT_TIMELENGTH;
+               xyspeed = max(0, xyspeed - PHYS_AIRCONTROL_PENALTY * sqrt(max(0, 1 - dot*dot)) * k/32);
+               k *= PHYS_AIRCONTROL;
+               self.velocity = normalize(self.velocity * xyspeed + wishdir * k);
+       }
+
+       self.velocity = self.velocity * xyspeed;
+       self.velocity_z = zspeed;
+}
+
+float AdjustAirAccelQW(float accelqw, float factor)
+{
+       return copysign(bound(0.000001, 1 - (1 - fabs(accelqw)) * factor, 1), accelqw);
+}
+
+// example config for alternate speed clamping:
+//   sv_airaccel_qw 0.8
+//   sv_airaccel_sideways_friction 0
+//   prvm_globalset server speedclamp_mode 1
+//     (or 2)
+void PM_Accelerate(vector wishdir, float wishspeed, float wishspeed0, float accel, float accelqw, float stretchfactor, float sidefric, float speedlimit)
+{
+       float speedclamp = stretchfactor > 0 ? stretchfactor
+       : accelqw < 0 ? 1 // full clamping, no stretch
+       : -1; // no clamping
+
+       accelqw = fabs(accelqw);
+
+       if (GAMEPLAYFIX_Q2AIRACCELERATE)
+               wishspeed0 = wishspeed; // don't need to emulate this Q1 bug
+
+       float vel_straight = self.velocity * wishdir;
+       float vel_z = self.velocity_z;
+       vector vel_xy = vec2(self.velocity);
+       vector vel_perpend = vel_xy - vel_straight * wishdir;
+
+       float step = accel * PHYS_INPUT_TIMELENGTH * wishspeed0;
+
+       float vel_xy_current  = vlen(vel_xy);
+       if (speedlimit)
+               accelqw = AdjustAirAccelQW(accelqw, (speedlimit - bound(wishspeed, vel_xy_current, speedlimit)) / max(1, speedlimit - wishspeed));
+       float vel_xy_forward =  vel_xy_current  + bound(0, wishspeed - vel_xy_current, step) * accelqw + step * (1 - accelqw);
+       float vel_xy_backward = vel_xy_current  - bound(0, wishspeed + vel_xy_current, step) * accelqw - step * (1 - accelqw);
+       vel_xy_backward = max(0, vel_xy_backward); // not that it REALLY occurs that this would cause wrong behaviour afterwards
+       vel_straight =          vel_straight    + bound(0, wishspeed - vel_straight,   step) * accelqw + step * (1 - accelqw);
+
+       if (sidefric < 0 && (vel_perpend*vel_perpend))
+               // negative: only apply so much sideways friction to stay below the speed you could get by "braking"
+       {
+               float f = max(0, 1 + PHYS_INPUT_TIMELENGTH * wishspeed * sidefric);
+               float fmin = (vel_xy_backward * vel_xy_backward - vel_straight * vel_straight) / (vel_perpend * vel_perpend);
+               // assume: fmin > 1
+               // vel_xy_backward*vel_xy_backward - vel_straight*vel_straight > vel_perpend*vel_perpend
+               // vel_xy_backward*vel_xy_backward > vel_straight*vel_straight + vel_perpend*vel_perpend
+               // vel_xy_backward*vel_xy_backward > vel_xy * vel_xy
+               // obviously, this cannot be
+               if (fmin <= 0)
+                       vel_perpend *= f;
+               else
+               {
+                       fmin = sqrt(fmin);
+                       vel_perpend *= max(fmin, f);
+               }
+       }
+       else
+               vel_perpend *= max(0, 1 - PHYS_INPUT_TIMELENGTH * wishspeed * sidefric);
+
+       vel_xy = vel_straight * wishdir + vel_perpend;
+
+       if (speedclamp >= 0)
+       {
+               float vel_xy_preclamp;
+               vel_xy_preclamp = vlen(vel_xy);
+               if (vel_xy_preclamp > 0) // prevent division by zero
+               {
+                       vel_xy_current += (vel_xy_forward - vel_xy_current) * speedclamp;
+                       if (vel_xy_current < vel_xy_preclamp)
+                               vel_xy *= (vel_xy_current / vel_xy_preclamp);
+               }
+       }
+
+       self.velocity = vel_xy + vel_z * '0 0 1';
+}
+
+void PM_AirAccelerate(vector wishdir, float wishspeed)
+{
+       if (wishspeed == 0)
+               return;
+
+       vector curvel = self.velocity;
+       curvel_z = 0;
+       float curspeed = vlen(curvel);
+
+       if (wishspeed > curspeed * 1.01)
+               wishspeed = min(wishspeed, curspeed + PHYS_WARSOWBUNNY_AIRFORWARDACCEL * PHYS_MAXSPEED(self) * PHYS_INPUT_TIMELENGTH);
+       else
+       {
+               float f = max(0, (PHYS_WARSOWBUNNY_TOPSPEED - curspeed) / (PHYS_WARSOWBUNNY_TOPSPEED - PHYS_MAXSPEED(self)));
+               wishspeed = max(curspeed, PHYS_MAXSPEED(self)) + PHYS_WARSOWBUNNY_ACCEL * f * PHYS_MAXSPEED(self) * PHYS_INPUT_TIMELENGTH;
+       }
+       vector wishvel = wishdir * wishspeed;
+       vector acceldir = wishvel - curvel;
+       float addspeed = vlen(acceldir);
+       acceldir = normalize(acceldir);
+
+       float accelspeed = min(addspeed, PHYS_WARSOWBUNNY_TURNACCEL * PHYS_MAXSPEED(self) * PHYS_INPUT_TIMELENGTH);
+
+       if (PHYS_WARSOWBUNNY_BACKTOSIDERATIO < 1)
+       {
+               vector curdir = normalize(curvel);
+               float dot = acceldir * curdir;
+               if (dot < 0)
+                       acceldir -= (1 - PHYS_WARSOWBUNNY_BACKTOSIDERATIO) * dot * curdir;
+       }
+
+       self.velocity += accelspeed * acceldir;
+}
+
+
+/*
+=============
+PlayerJump
+
+When you press the jump key
+returns true if handled
+=============
+*/
+float PlayerJump (void)
+{
+       if (PHYS_FROZEN(self))
+               return true; // no jumping in freezetag when frozen
+
+#ifdef SVQC
+       if (self.player_blocked)
+               return true; // no jumping while blocked
+#endif
+
+       float doublejump = false;
+       float mjumpheight = PHYS_JUMPVELOCITY;
+
+       player_multijump = doublejump;
+       player_jumpheight = mjumpheight;
+#ifdef SVQC
+       if (MUTATOR_CALLHOOK(PlayerJump))
+#elif defined(CSQC)
+       if(PM_multijump_checkjump())
+#endif
+               return true;
+
+       doublejump = player_multijump;
+       mjumpheight = player_jumpheight;
+
+       if (PHYS_DOUBLEJUMP)
+       {
+               tracebox(self.origin + '0 0 0.01', self.mins, self.maxs, self.origin - '0 0 0.01', MOVE_NORMAL, self);
+               if (trace_fraction < 1 && trace_plane_normal_z > 0.7)
+               {
+                       doublejump = true;
+
+                       // we MUST clip velocity here!
+                       float f;
+                       f = self.velocity * trace_plane_normal;
+                       if (f < 0)
+                               self.velocity -= f * trace_plane_normal;
+               }
+       }
+
+       if (self.waterlevel >= WATERLEVEL_SWIMMING)
+       {
+               self.velocity_z = PHYS_MAXSPEED(self) * 0.7;
+               return true;
+       }
+
+       if (!doublejump)
+               if (!IS_ONGROUND(self))
+                       return IS_JUMP_HELD(self);
+
+       if (PHYS_TRACK_CANJUMP(self))
+               if (IS_JUMP_HELD(self))
+                       return true;
+
+       // sv_jumpspeedcap_min/sv_jumpspeedcap_max act as baseline
+       // velocity bounds.  Final velocity is bound between (jumpheight *
+       // min + jumpheight) and (jumpheight * max + jumpheight);
+
+       if(PHYS_JUMPSPEEDCAP_MIN)
+       {
+               float minjumpspeed = mjumpheight * PHYS_JUMPSPEEDCAP_MIN;
+
+               if (self.velocity_z < minjumpspeed)
+                       mjumpheight += minjumpspeed - self.velocity_z;
+       }
+
+       if(PHYS_JUMPSPEEDCAP_MAX)
+       {
+               // don't do jump speedcaps on ramps to preserve old xonotic ramjump style
+               tracebox(self.origin + '0 0 0.01', self.mins, self.maxs, self.origin - '0 0 0.01', MOVE_NORMAL, self);
+
+               if (!(trace_fraction < 1 && trace_plane_normal_z < 0.98 && PHYS_JUMPSPEEDCAP_DISABLE_ONRAMPS))
+               {
+                       float maxjumpspeed = mjumpheight * PHYS_JUMPSPEEDCAP_MAX;
+
+                       if (self.velocity_z > maxjumpspeed)
+                               mjumpheight -= self.velocity_z - maxjumpspeed;
+               }
+       }
+
+       if (!WAS_ONGROUND(self))
+       {
+               if(autocvar_speedmeter)
+                       dprint(strcat("landing velocity: ", vtos(self.velocity), " (abs: ", ftos(vlen(self.velocity)), ")\n"));
+               if(self.lastground < time - 0.3)
+               {
+                       self.velocity_x *= (1 - PHYS_FRICTION_ONLAND);
+                       self.velocity_y *= (1 - PHYS_FRICTION_ONLAND);
+               }
+               if(self.jumppadcount > 1)
+                       dprint(strcat(ftos(self.jumppadcount), "x jumppad combo\n"));
+               self.jumppadcount = 0;
+       }
+
+       self.velocity_z += mjumpheight;
+
+       UNSET_ONGROUND(self);
+       SET_JUMP_HELD(self);
+
+#ifdef SVQC
+
+       self.oldvelocity_z = self.velocity_z;
+
+       animdecide_setaction(self, ANIMACTION_JUMP, true);
+
+       if (autocvar_g_jump_grunt)
+               PlayerSound(playersound_jump, CH_PLAYER, VOICETYPE_PLAYERSOUND);
+#endif
+       return true;
+}
+
+void CheckWaterJump()
+{
+// check for a jump-out-of-water
+       makevectors(PHYS_INPUT_ANGLES(self));
+       vector start = self.origin;
+       start_z += 8;
+       v_forward_z = 0;
+       normalize(v_forward);
+       vector end = start + v_forward*24;
+       traceline (start, end, true, self);
+       if (trace_fraction < 1)
+       {       // solid at waist
+               start_z = start_z + self.maxs_z - 8;
+               end = start + v_forward*24;
+               self.movedir = trace_plane_normal * -50;
+               traceline(start, end, true, self);
+               if (trace_fraction == 1)
+               {       // open at eye level
+                       self.velocity_z = 225;
+#ifdef SVQC
+                       self.flags |= FL_WATERJUMP;
+                       SET_JUMP_HELD(self);
+                       self.teleport_time = time + 2;  // safety net
+#endif
+               }
+       }
+}
+
+
+#ifdef SVQC
+       #define JETPACK_JUMP(s) s.cvar_cl_jetpack_jump
+#elif defined(CSQC)
+       float autocvar_cl_jetpack_jump;
+       #define JETPACK_JUMP(s) autocvar_cl_jetpack_jump
+#endif
+.float jetpack_stopped;
+// Hack: shouldn't need to know about this
+.float multijump_count;
+void CheckPlayerJump()
+{
+#ifdef SVQC
+       float was_flying = ITEMS(self) & IT_USING_JETPACK;
+#endif
+       if (JETPACK_JUMP(self) < 2)
+#ifdef SVQC
+               ITEMS(self) &= ~IT_USING_JETPACK;
+#endif
+
+       if(PHYS_INPUT_BUTTON_JUMP(self) || PHYS_INPUT_BUTTON_JETPACK(self))
+       {
+#ifdef SVQC
+               float air_jump = !PlayerJump() || self.multijump_count > 0; // PlayerJump() has important side effects
+               float activate = JETPACK_JUMP(self) && air_jump && PHYS_INPUT_BUTTON_JUMP(self) || PHYS_INPUT_BUTTON_JETPACK(self);
+               float has_fuel = !autocvar_g_jetpack_fuel || self.ammo_fuel || ITEMS(self) & IT_UNLIMITED_WEAPON_AMMO;
+#else
+               PlayerJump(); // Client only
+               float has_fuel = true; // TODO
+#endif
+               if (!(ITEMS(self) & IT_JETPACK)) { }
+               else if (self.jetpack_stopped) { }
+               else if (!has_fuel)
+               {
+#ifdef SVQC
+                       if (was_flying) // TODO: ran out of fuel message
+                               Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_JETPACK_NOFUEL);
+                       else if (activate)
+                               Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_JETPACK_NOFUEL);
+#endif
+                       self.jetpack_stopped = true;
+#ifdef SVQC
+                       ITEMS(self) &= ~IT_USING_JETPACK;
+#endif
+               }
+#ifdef SVQC
+               else if (activate && !PHYS_FROZEN(self))
+                       ITEMS(self) |= IT_USING_JETPACK;
+#endif
+       }
+       else
+       {
+               self.jetpack_stopped = false;
+#ifdef SVQC
+               ITEMS(self) &= ~IT_USING_JETPACK;
+#endif
+       }
+       if (!PHYS_INPUT_BUTTON_JUMP(self))
+               UNSET_JUMP_HELD(self);
+
+       if (self.waterlevel == WATERLEVEL_SWIMMING)
+               CheckWaterJump();
+}
+
+float racecar_angle(float forward, float down)
+{
+       if (forward < 0)
+       {
+               forward = -forward;
+               down = -down;
+       }
+
+       float ret = vectoyaw('0 1 0' * down + '1 0 0' * forward);
+
+       float angle_mult = forward / (800 + forward);
+
+       if (ret > 180)
+               return ret * angle_mult + 360 * (1 - angle_mult);
+       else
+               return ret * angle_mult;
+}
+
+void RaceCarPhysics()
+{
+#ifdef SVQC
+       // using this move type for "big rigs"
+       // the engine does not push the entity!
+
+       vector rigvel;
+
+       vector angles_save = self.angles;
+       float accel = bound(-1, PHYS_INPUT_MOVEVALUES(self).x / PHYS_MAXSPEED(self), 1);
+       float steer = bound(-1, PHYS_INPUT_MOVEVALUES(self).y / PHYS_MAXSPEED(self), 1);
+
+       if (g_bugrigs_reverse_speeding)
+       {
+               if (accel < 0)
+               {
+                       // back accel is DIGITAL
+                       // to prevent speedhack
+                       if (accel < -0.5)
+                               accel = -1;
+                       else
+                               accel = 0;
+               }
+       }
+
+       self.angles_x = 0;
+       self.angles_z = 0;
+       makevectors(self.angles); // new forward direction!
+
+       if (IS_ONGROUND(self) || g_bugrigs_air_steering)
+       {
+               float myspeed = self.velocity * v_forward;
+               float upspeed = self.velocity * v_up;
+
+               // responsiveness factor for steering and acceleration
+               float f = 1 / (1 + pow(max(-myspeed, myspeed) / g_bugrigs_speed_ref, g_bugrigs_speed_pow));
+               //MAXIMA: f(v) := 1 / (1 + (v / g_bugrigs_speed_ref) ^ g_bugrigs_speed_pow);
+
+               float steerfactor;
+               if (myspeed < 0 && g_bugrigs_reverse_spinning)
+                       steerfactor = -myspeed * g_bugrigs_steer;
+               else
+                       steerfactor = -myspeed * f * g_bugrigs_steer;
+
+               float accelfactor;
+               if (myspeed < 0 && g_bugrigs_reverse_speeding)
+                       accelfactor = g_bugrigs_accel;
+               else
+                       accelfactor = f * g_bugrigs_accel;
+               //MAXIMA: accel(v) := f(v) * g_bugrigs_accel;
+
+               if (accel < 0)
+               {
+                       if (myspeed > 0)
+                       {
+                               myspeed = max(0, myspeed - PHYS_INPUT_TIMELENGTH * (g_bugrigs_friction_floor - g_bugrigs_friction_brake * accel));
+                       }
+                       else
+                       {
+                               if (!g_bugrigs_reverse_speeding)
+                                       myspeed = min(0, myspeed + PHYS_INPUT_TIMELENGTH * g_bugrigs_friction_floor);
+                       }
+               }
+               else
+               {
+                       if (myspeed >= 0)
+                       {
+                               myspeed = max(0, myspeed - PHYS_INPUT_TIMELENGTH * g_bugrigs_friction_floor);
+                       }
+                       else
+                       {
+                               if (g_bugrigs_reverse_stopping)
+                                       myspeed = 0;
+                               else
+                                       myspeed = min(0, myspeed + PHYS_INPUT_TIMELENGTH * (g_bugrigs_friction_floor + g_bugrigs_friction_brake * accel));
+                       }
+               }
+               // terminal velocity = velocity at which 50 == accelfactor, that is, 1549 units/sec
+               //MAXIMA: friction(v) := g_bugrigs_friction_floor;
+
+               self.angles_y += steer * PHYS_INPUT_TIMELENGTH * steerfactor; // apply steering
+               makevectors(self.angles); // new forward direction!
+
+               myspeed += accel * accelfactor * PHYS_INPUT_TIMELENGTH;
+
+               rigvel = myspeed * v_forward + '0 0 1' * upspeed;
+       }
+       else
+       {
+               float myspeed = vlen(self.velocity);
+
+               // responsiveness factor for steering and acceleration
+               float f = 1 / (1 + pow(max(0, myspeed / g_bugrigs_speed_ref), g_bugrigs_speed_pow));
+               float steerfactor = -myspeed * f;
+               self.angles_y += steer * PHYS_INPUT_TIMELENGTH * steerfactor; // apply steering
+
+               rigvel = self.velocity;
+               makevectors(self.angles); // new forward direction!
+       }
+
+       rigvel *= max(0, 1 - vlen(rigvel) * g_bugrigs_friction_air * PHYS_INPUT_TIMELENGTH);
+       //MAXIMA: airfriction(v) := v * v * g_bugrigs_friction_air;
+       //MAXIMA: total_acceleration(v) := accel(v) - friction(v) - airfriction(v);
+       //MAXIMA: solve(total_acceleration(v) = 0, v);
+
+       if (g_bugrigs_planar_movement)
+       {
+               vector rigvel_xy, neworigin, up;
+               float mt;
+
+               rigvel_z -= PHYS_INPUT_TIMELENGTH * PHYS_GRAVITY; // 4x gravity plays better
+               rigvel_xy = vec2(rigvel);
+
+               if (g_bugrigs_planar_movement_car_jumping)
+                       mt = MOVE_NORMAL;
+               else
+                       mt = MOVE_NOMONSTERS;
+
+               tracebox(self.origin, self.mins, self.maxs, self.origin + '0 0 1024', mt, self);
+               up = trace_endpos - self.origin;
+
+               // BUG RIGS: align the move to the surface instead of doing collision testing
+               // can we move?
+               tracebox(trace_endpos, self.mins, self.maxs, trace_endpos + rigvel_xy * PHYS_INPUT_TIMELENGTH, mt, self);
+
+               // align to surface
+               tracebox(trace_endpos, self.mins, self.maxs, trace_endpos - up + '0 0 1' * rigvel_z * PHYS_INPUT_TIMELENGTH, mt, self);
+
+               if (trace_fraction < 0.5)
+               {
+                       trace_fraction = 1;
+                       neworigin = self.origin;
+               }
+               else
+                       neworigin = trace_endpos;
+
+               if (trace_fraction < 1)
+               {
+                       // now set angles_x so that the car points parallel to the surface
+                       self.angles = vectoangles(
+                                       '1 0 0' * v_forward_x * trace_plane_normal_z
+                                       +
+                                       '0 1 0' * v_forward_y * trace_plane_normal_z
+                                       +
+                                       '0 0 1' * -(v_forward_x * trace_plane_normal_x + v_forward_y * trace_plane_normal_y)
+                                       );
+                       SET_ONGROUND(self);
+               }
+               else
+               {
+                       // now set angles_x so that the car points forward, but is tilted in velocity direction
+                       UNSET_ONGROUND(self);
+               }
+
+               self.velocity = (neworigin - self.origin) * (1.0 / PHYS_INPUT_TIMELENGTH);
+               self.movetype = MOVETYPE_NOCLIP;
+       }
+       else
+       {
+               rigvel_z -= PHYS_INPUT_TIMELENGTH * PHYS_GRAVITY; // 4x gravity plays better
+               self.velocity = rigvel;
+               self.movetype = MOVETYPE_FLY;
+       }
+
+       trace_fraction = 1;
+       tracebox(self.origin, self.mins, self.maxs, self.origin - '0 0 4', MOVE_NORMAL, self);
+       if (trace_fraction != 1)
+       {
+               self.angles = vectoangles2(
+                               '1 0 0' * v_forward_x * trace_plane_normal_z
+                               +
+                               '0 1 0' * v_forward_y * trace_plane_normal_z
+                               +
+                               '0 0 1' * -(v_forward_x * trace_plane_normal_x + v_forward_y * trace_plane_normal_y),
+                               trace_plane_normal
+                               );
+       }
+       else
+       {
+               vector vel_local;
+
+               vel_local_x = v_forward * self.velocity;
+               vel_local_y = v_right * self.velocity;
+               vel_local_z = v_up * self.velocity;
+
+               self.angles_x = racecar_angle(vel_local_x, vel_local_z);
+               self.angles_z = racecar_angle(-vel_local_y, vel_local_z);
+       }
+
+       // smooth the angles
+       vector vf1, vu1, smoothangles;
+       makevectors(self.angles);
+       float f = bound(0, PHYS_INPUT_TIMELENGTH * g_bugrigs_angle_smoothing, 1);
+       if (f == 0)
+               f = 1;
+       vf1 = v_forward * f;
+       vu1 = v_up * f;
+       makevectors(angles_save);
+       vf1 = vf1 + v_forward * (1 - f);
+       vu1 = vu1 + v_up * (1 - f);
+       smoothangles = vectoangles2(vf1, vu1);
+       self.angles_x = -smoothangles_x;
+       self.angles_z =  smoothangles_z;
+#endif
+}
+
+string specialcommand = "xwxwxsxsxaxdxaxdx1x ";
+.float specialcommand_pos;
+void SpecialCommand()
+{
+#ifdef SVQC
+#ifdef TETRIS
+       TetrisImpulse();
+#else
+       if (!CheatImpulse(99))
+               print("A hollow voice says \"Plugh\".\n");
+#endif
+#endif
+}
+
+float PM_check_keepaway(void)
+{
+#ifdef SVQC
+       return (self.ballcarried && g_keepaway) ? autocvar_g_keepaway_ballcarrier_highspeed : 1;
+#else
+       return 1;
+#endif
+}
+
+void PM_check_race_movetime(void)
+{
+#ifdef SVQC
+       self.race_movetime_frac += PHYS_INPUT_TIMELENGTH;
+       float f = floor(self.race_movetime_frac);
+       self.race_movetime_frac -= f;
+       self.race_movetime_count += f;
+       self.race_movetime = self.race_movetime_frac + self.race_movetime_count;
+#endif
+}
+
+float PM_check_specialcommand(float buttons)
+{
+#ifdef SVQC
+       string c;
+       if (!buttons)
+               c = "x";
+       else if (buttons == 1)
+               c = "1";
+       else if (buttons == 2)
+               c = " ";
+       else if (buttons == 128)
+               c = "s";
+       else if (buttons == 256)
+               c = "w";
+       else if (buttons == 512)
+               c = "a";
+       else if (buttons == 1024)
+               c = "d";
+       else
+               c = "?";
+
+       if (c == substring(specialcommand, self.specialcommand_pos, 1))
+       {
+               self.specialcommand_pos += 1;
+               if (self.specialcommand_pos >= strlen(specialcommand))
+               {
+                       self.specialcommand_pos = 0;
+                       SpecialCommand();
+                       return true;
+               }
+       }
+       else if (self.specialcommand_pos && (c != substring(specialcommand, self.specialcommand_pos - 1, 1)))
+               self.specialcommand_pos = 0;
+#endif
+       return false;
+}
+
+void PM_check_nickspam(void)
+{
+#ifdef SVQC
+       if (time >= self.nickspamtime)
+               return;
+       if (self.nickspamcount >= autocvar_g_nick_flood_penalty_yellow)
+       {
+               // slight annoyance for nick change scripts
+               PHYS_INPUT_MOVEVALUES(self) = -1 * PHYS_INPUT_MOVEVALUES(self);
+               self.BUTTON_ATCK = self.BUTTON_JUMP = self.BUTTON_ATCK2 = self.BUTTON_ZOOM = self.BUTTON_CROUCH = self.BUTTON_HOOK = self.BUTTON_USE = 0;
+
+               if (self.nickspamcount >= autocvar_g_nick_flood_penalty_red) // if you are persistent and the slight annoyance above does not stop you, I'll show you!
+               {
+                       PHYS_INPUT_ANGLES(self)_x = random() * 360;
+                       PHYS_INPUT_ANGLES(self)_y = random() * 360;
+                       // at least I'm not forcing retardedview by also assigning to angles_z
+                       self.fixangle = true;
+               }
+       }
+#endif
+}
+
+void PM_check_punch()
+{
+#ifdef SVQC
+       if (self.punchangle != '0 0 0')
+       {
+               float f = vlen(self.punchangle) - 10 * PHYS_INPUT_TIMELENGTH;
+               if (f > 0)
+                       self.punchangle = normalize(self.punchangle) * f;
+               else
+                       self.punchangle = '0 0 0';
+       }
+
+       if (self.punchvector != '0 0 0')
+       {
+               float f = vlen(self.punchvector) - 30 * PHYS_INPUT_TIMELENGTH;
+               if (f > 0)
+                       self.punchvector = normalize(self.punchvector) * f;
+               else
+                       self.punchvector = '0 0 0';
+       }
+#endif
+}
+
+void PM_check_spider(void)
+{
+#ifdef SVQC
+       if (time >= self.spider_slowness)
+               return;
+       PHYS_MAXSPEED(self) *= 0.5; // half speed while slow from spider
+       self.stat_sv_airspeedlimit_nonqw *= 0.5;
+#endif
+}
+
+// predict frozen movement, as frozen players CAN move in some cases
+void PM_check_frozen(void)
+{
+       if (!PHYS_FROZEN(self))
+               return;
+       if (PHYS_DODGING_FROZEN
+#ifdef SVQC
+       && IS_REAL_CLIENT(self)
+#endif
+       )
+       {
+               PHYS_INPUT_MOVEVALUES(self)_x = bound(-5, PHYS_INPUT_MOVEVALUES(self).x, 5);
+               PHYS_INPUT_MOVEVALUES(self)_y = bound(-5, PHYS_INPUT_MOVEVALUES(self).y, 5);
+               PHYS_INPUT_MOVEVALUES(self)_z = bound(-5, PHYS_INPUT_MOVEVALUES(self).z, 5);
+       }
+       else
+               PHYS_INPUT_MOVEVALUES(self) = '0 0 0';
+
+       vector midpoint = ((self.absmin + self.absmax) * 0.5);
+       if (pointcontents(midpoint) == CONTENT_WATER)
+       {
+               self.velocity = self.velocity * 0.5;
+
+               if (pointcontents(midpoint + '0 0 16') == CONTENT_WATER)
+                       self.velocity_z = 200;
+       }
+}
+
+void PM_check_hitground()
+{
+#ifdef SVQC
+       if (IS_ONGROUND(self))
+       if (IS_PLAYER(self)) // no fall sounds for observers thank you very much
+       if (self.wasFlying)
+       {
+               self.wasFlying = 0;
+               if (self.waterlevel < WATERLEVEL_SWIMMING)
+               if (time >= self.ladder_time)
+               if (!self.hook)
+               {
+                       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_dphitq3surfaceflags & Q3SURFACEFLAG_NOSTEPS))
+                       {
+                               if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_METALSTEPS)
+                                       GlobalSound(globalsound_metalfall, CH_PLAYER, VOICETYPE_PLAYERSOUND);
+                               else
+                                       GlobalSound(globalsound_fall, CH_PLAYER, VOICETYPE_PLAYERSOUND);
+                       }
+               }
+       }
+#endif
+}
+
+void PM_check_blocked(void)
+{
+#ifdef SVQC
+       if (!self.player_blocked)
+               return;
+       PHYS_INPUT_MOVEVALUES(self) = '0 0 0';
+       self.disableclientprediction = 1;
+#endif
+}
+
+#ifdef SVQC
+float speedaward_lastsent;
+float speedaward_lastupdate;
+#endif
+void PM_check_race(void)
+{
+#ifdef SVQC
+       if(!(g_cts || g_race))
+               return;
+       if (vlen(self.velocity - self.velocity_z * '0 0 1') > speedaward_speed)
+       {
+               speedaward_speed = vlen(self.velocity - self.velocity_z * '0 0 1');
+               speedaward_holder = self.netname;
+               speedaward_uid = self.crypto_idfp;
+               speedaward_lastupdate = time;
+       }
+       if (speedaward_speed > speedaward_lastsent && time - speedaward_lastupdate > 1)
+       {
+               string rr = (g_cts) ? CTS_RECORD : RACE_RECORD;
+               race_send_speedaward(MSG_ALL);
+               speedaward_lastsent = speedaward_speed;
+               if (speedaward_speed > speedaward_alltimebest && speedaward_uid != "")
+               {
+                       speedaward_alltimebest = speedaward_speed;
+                       speedaward_alltimebest_holder = speedaward_holder;
+                       speedaward_alltimebest_uid = speedaward_uid;
+                       db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/speed"), ftos(speedaward_alltimebest));
+                       db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/crypto_idfp"), speedaward_alltimebest_uid);
+                       race_send_speedaward_alltimebest(MSG_ALL);
+               }
+       }
+#endif
+}
+
+void PM_check_vortex(void)
+{
+#ifdef SVQC
+       // WEAPONTODO
+       float xyspeed = vlen(vec2(self.velocity));
+       if (self.weapon == WEP_VORTEX && WEP_CVAR(vortex, charge) && WEP_CVAR(vortex, charge_velocity_rate) && xyspeed > WEP_CVAR(vortex, charge_minspeed))
+       {
+               // add a maximum of charge_velocity_rate when going fast (f = 1), gradually increasing from minspeed (f = 0) to maxspeed
+               xyspeed = min(xyspeed, WEP_CVAR(vortex, charge_maxspeed));
+               float f = (xyspeed - WEP_CVAR(vortex, charge_minspeed)) / (WEP_CVAR(vortex, charge_maxspeed) - WEP_CVAR(vortex, charge_minspeed));
+               // add the extra charge
+               self.vortex_charge = min(1, self.vortex_charge + WEP_CVAR(vortex, charge_velocity_rate) * f * PHYS_INPUT_TIMELENGTH);
+       }
+#endif
+}
+
+void PM_fly(float maxspd_mod)
+{
+       // noclipping or flying
+       UNSET_ONGROUND(self);
+
+       self.velocity = self.velocity * (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION);
+       makevectors(PHYS_INPUT_ANGLES(self));
+       //wishvel = v_forward * PHYS_INPUT_MOVEVALUES(self).x + v_right * PHYS_INPUT_MOVEVALUES(self).y + v_up * PHYS_INPUT_MOVEVALUES(self).z;
+       vector wishvel = v_forward * PHYS_INPUT_MOVEVALUES(self).x
+                                       + v_right * PHYS_INPUT_MOVEVALUES(self).y
+                                       + '0 0 1' * PHYS_INPUT_MOVEVALUES(self).z;
+       // acceleration
+       vector wishdir = normalize(wishvel);
+       float wishspeed = min(vlen(wishvel), PHYS_MAXSPEED(self) * maxspd_mod);
+       if (time >= self.teleport_time)
+               PM_Accelerate(wishdir, wishspeed, wishspeed, PHYS_ACCELERATE * maxspd_mod, 1, 0, 0, 0);
+}
+
+void PM_swim(float maxspd_mod)
+{
+       // swimming
+       UNSET_ONGROUND(self);
+
+       float jump = PHYS_INPUT_BUTTON_JUMP(self);
+       // water jump only in certain situations
+       // this mimics quakeworld code
+       if (jump && self.waterlevel == WATERLEVEL_SWIMMING && self.velocity_z >= -180)
+       {
+               vector yawangles = '0 1 0' * PHYS_INPUT_ANGLES(self).y;
+               makevectors(yawangles);
+               vector forward = v_forward;
+               vector spot = self.origin + 24 * forward;
+               spot_z += 8;
+               traceline(spot, spot, MOVE_NOMONSTERS, self);
+               if (trace_startsolid)
+               {
+                       spot_z += 24;
+                       traceline(spot, spot, MOVE_NOMONSTERS, self);
+                       if (!trace_startsolid)
+                       {
+                               self.velocity = forward * 50;
+                               self.velocity_z = 310;
+                               pmove_waterjumptime = 2;
+                               UNSET_ONGROUND(self);
+                               SET_JUMP_HELD(self);
+                       }
+               }
+       }
+       makevectors(PHYS_INPUT_ANGLES(self));
+       //wishvel = v_forward * PHYS_INPUT_MOVEVALUES(self).x + v_right * PHYS_INPUT_MOVEVALUES(self).y + v_up * PHYS_INPUT_MOVEVALUES(self).z;
+       vector wishvel = v_forward * PHYS_INPUT_MOVEVALUES(self).x
+                                       + v_right * PHYS_INPUT_MOVEVALUES(self).y
+                                       + '0 0 1' * PHYS_INPUT_MOVEVALUES(self).z;
+       if (wishvel == '0 0 0')
+               wishvel = '0 0 -60'; // drift towards bottom
+
+       vector wishdir = normalize(wishvel);
+       float wishspeed = min(vlen(wishvel), PHYS_MAXSPEED(self) * maxspd_mod) * 0.7;
+
+       if (IS_DUCKED(self))
+       wishspeed *= 0.5;
+
+//     if (pmove_waterjumptime <= 0) // TODO: use
+    {
+               // water friction
+               float f = 1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION;
+               f = min(max(0, f), 1);
+               self.velocity *= f;
+
+               f = wishspeed - self.velocity * wishdir;
+               if (f > 0)
+               {
+                       float accelspeed = min(PHYS_ACCELERATE * PHYS_INPUT_TIMELENGTH * wishspeed, f);
+                       self.velocity += accelspeed * wishdir;
+               }
+
+               // holding jump button swims upward slowly
+               if (jump)
+               {
+#if 0
+                       if (self.watertype & CONTENT_LAVA)
+                               self.velocity_z =  50;
+                       else if (self.watertype & CONTENT_SLIME)
+                               self.velocity_z =  80;
+                       else
+                       {
+                               if (IS_NEXUIZ_DERIVED(gamemode))
+#endif
+                                       self.velocity_z = 200;
+#if 0
+                               else
+                                       self.velocity_z = 100;
+                       }
+#endif
+               }
+       }
+       PM_ClientMovement_Move();
+       // water acceleration
+       PM_Accelerate(wishdir, wishspeed, wishspeed, PHYS_ACCELERATE * maxspd_mod, 1, 0, 0, 0);
+}
+
+void PM_ladder(float maxspd_mod)
+{
+       // on a spawnfunc_func_ladder or swimming in spawnfunc_func_water
+       UNSET_ONGROUND(self);
+
+       float g;
+       g = PHYS_GRAVITY * PHYS_INPUT_TIMELENGTH;
+       if (PHYS_ENTGRAVITY(self))
+               g *= PHYS_ENTGRAVITY(self);
+       if (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
+       {
+               g *= 0.5;
+               self.velocity_z += g;
+       }
+
+       self.velocity = self.velocity * (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION);
+       makevectors(PHYS_INPUT_ANGLES(self));
+       //wishvel = v_forward * PHYS_INPUT_MOVEVALUES(self).x + v_right * PHYS_INPUT_MOVEVALUES(self).y + v_up * PHYS_INPUT_MOVEVALUES(self).z;
+       vector wishvel = v_forward * PHYS_INPUT_MOVEVALUES(self)_x
+                                       + v_right * PHYS_INPUT_MOVEVALUES(self)_y
+                                       + '0 0 1' * PHYS_INPUT_MOVEVALUES(self)_z;
+       self.velocity_z += g;
+       if (self.ladder_entity.classname == "func_water")
+       {
+               float f = vlen(wishvel);
+               if (f > self.ladder_entity.speed)
+                       wishvel *= (self.ladder_entity.speed / f);
+
+               self.watertype = self.ladder_entity.skin;
+               f = self.ladder_entity.origin_z + self.ladder_entity.maxs_z;
+               if ((self.origin_z + self.view_ofs_z) < f)
+                       self.waterlevel = WATERLEVEL_SUBMERGED;
+               else if ((self.origin_z + (self.mins_z + self.maxs_z) * 0.5) < f)
+                       self.waterlevel = WATERLEVEL_SWIMMING;
+               else if ((self.origin_z + self.mins_z + 1) < f)
+                       self.waterlevel = WATERLEVEL_WETFEET;
+               else
+               {
+                       self.waterlevel = WATERLEVEL_NONE;
+                       self.watertype = CONTENT_EMPTY;
+               }
+       }
+       // acceleration
+       vector wishdir = normalize(wishvel);
+       float wishspeed = min(vlen(wishvel), PHYS_MAXSPEED(self) * maxspd_mod);
+       PM_ClientMovement_Move();
+#ifdef SVQC
+       if (time >= self.teleport_time)
+#endif
+               // water acceleration
+               PM_Accelerate(wishdir, wishspeed, wishspeed, PHYS_ACCELERATE*maxspd_mod, 1, 0, 0, 0);
+}
+
+void PM_jetpack(float maxspd_mod)
+{
+       //makevectors(PHYS_INPUT_ANGLES(self).y * '0 1 0');
+       makevectors(PHYS_INPUT_ANGLES(self));
+       vector wishvel = v_forward * PHYS_INPUT_MOVEVALUES(self)_x
+                                       + v_right * PHYS_INPUT_MOVEVALUES(self)_y;
+       // add remaining speed as Z component
+       float maxairspd = PHYS_MAXAIRSPEED * max(1, maxspd_mod);
+       // fix speedhacks :P
+       wishvel = normalize(wishvel) * min(1, vlen(wishvel) / maxairspd);
+       // add the unused velocity as up component
+       wishvel_z = 0;
+
+       // if (self.BUTTON_JUMP)
+               wishvel_z = sqrt(max(0, 1 - wishvel * wishvel));
+
+       // it is now normalized, so...
+       float a_side = PHYS_JETPACK_ACCEL_SIDE;
+       float a_up = PHYS_JETPACK_ACCEL_UP;
+       float a_add = PHYS_JETPACK_ANTIGRAVITY * PHYS_GRAVITY;
+
+       wishvel_x *= a_side;
+       wishvel_y *= a_side;
+       wishvel_z *= a_up;
+       wishvel_z += a_add;
+
+       float best = 0;
+       //////////////////////////////////////////////////////////////////////////////////////
+       // finding the maximum over all vectors of above form
+       // with wishvel having an absolute value of 1
+       //////////////////////////////////////////////////////////////////////////////////////
+       // we're finding the maximum over
+       //   f(a_side, a_up, a_add, z) := a_side * (1 - z^2) + (a_add + a_up * z)^2;
+       // for z in the range from -1 to 1
+       //////////////////////////////////////////////////////////////////////////////////////
+       // maximum is EITHER attained at the single extreme point:
+       float a_diff = a_side * a_side - a_up * a_up;
+       float f;
+       if (a_diff != 0)
+       {
+               f = a_add * a_up / a_diff; // this is the zero of diff(f(a_side, a_up, a_add, z), z)
+               if (f > -1 && f < 1) // can it be attained?
+               {
+                       best = (a_diff + a_add * a_add) * (a_diff + a_up * a_up) / a_diff;
+                       //print("middle\n");
+               }
+       }
+       // OR attained at z = 1:
+       f = (a_up + a_add) * (a_up + a_add);
+       if (f > best)
+       {
+               best = f;
+               //print("top\n");
+       }
+       // OR attained at z = -1:
+       f = (a_up - a_add) * (a_up - a_add);
+       if (f > best)
+       {
+               best = f;
+               //print("bottom\n");
+       }
+       best = sqrt(best);
+       //////////////////////////////////////////////////////////////////////////////////////
+
+       //print("best possible acceleration: ", ftos(best), "\n");
+
+       float fxy, fz;
+       fxy = bound(0, 1 - (self.velocity * normalize(wishvel_x * '1 0 0' + wishvel_y * '0 1 0')) / PHYS_JETPACK_MAXSPEED_SIDE, 1);
+       if (wishvel_z - PHYS_GRAVITY > 0)
+               fz = bound(0, 1 - self.velocity_z / PHYS_JETPACK_MAXSPEED_UP, 1);
+       else
+               fz = bound(0, 1 + self.velocity_z / PHYS_JETPACK_MAXSPEED_UP, 1);
+
+       float fvel;
+       fvel = vlen(wishvel);
+       wishvel_x *= fxy;
+       wishvel_y *= fxy;
+       wishvel_z = (wishvel_z - PHYS_GRAVITY) * fz + PHYS_GRAVITY;
+
+       fvel = min(1, vlen(wishvel) / best);
+       if (PHYS_JETPACK_FUEL && !(ITEMS(self) & IT_UNLIMITED_WEAPON_AMMO))
+               f = min(1, PHYS_AMMO_FUEL(self) / (PHYS_JETPACK_FUEL * PHYS_INPUT_TIMELENGTH * fvel));
+       else
+               f = 1;
+
+       //print("this acceleration: ", ftos(vlen(wishvel) * f), "\n");
+
+       if (f > 0 && wishvel != '0 0 0')
+       {
+               self.velocity = self.velocity + wishvel * f * PHYS_INPUT_TIMELENGTH;
+               UNSET_ONGROUND(self);
+
+#ifdef SVQC
+               if (!(ITEMS(self) & IT_UNLIMITED_WEAPON_AMMO))
+                       self.ammo_fuel -= PHYS_JETPACK_FUEL * PHYS_INPUT_TIMELENGTH * fvel * f;
+
+               ITEMS(self) |= IT_USING_JETPACK;
+
+               // jetpack also inhibits health regeneration, but only for 1 second
+               self.pauseregen_finished = max(self.pauseregen_finished, time + autocvar_g_balance_pause_fuel_regen);
+#endif
+       }
+
+#ifdef CSQC
+       float g = PHYS_GRAVITY * PHYS_ENTGRAVITY(self) * PHYS_INPUT_TIMELENGTH;
+       if (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
+               self.velocity_z -= g * 0.5;
+       else
+               self.velocity_z -= g;
+       PM_ClientMovement_Move();
+       if (!IS_ONGROUND(self) || !(GAMEPLAYFIX_NOGRAVITYONGROUND))
+               if (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
+                       self.velocity_z -= g * 0.5;
+#endif
+}
+#ifdef SVQC
+void SV_WalkMove ()
+{
+       // if PHYS_INPUT_TIMELENGTH is 0 (due to client sending the same timestamp twice),
+       // don't move
+       if (PHYS_INPUT_TIMELENGTH <= 0)
+               return;
+
+//     if (autocvar_sv_gameplayfix_unstickplayers)
+//             SV_CheckStuck (self);
+
+//     applygravity = !SV_CheckWater(self) && self.movetype == MOVETYPE_WALK && !(self.flags & FL_WATERJUMP);
+
+//     hitsupercontentsmask = SV_GenericHitSuperContentsMask(self);
+
+//     SV_CheckVelocity(self);
+
+       // do a regular slide move unless it looks like you ran into a step
+//     float oldonground = self.flags & FL_ONGROUND;
+
+       vector start_origin = self.origin;
+       vector start_velocity = self.velocity;
+
+       float clip = 0;
+//     clip = SV_FlyMove (self, PHYS_INPUT_TIMELENGTH, applygravity, NULL, hitsupercontentsmask, sv_gameplayfix_stepmultipletimes.integer ? sv_stepheight.value : 0);
+
+//     if(sv_gameplayfix_downtracesupportsongroundflag.integer)
+//     if(!(clip & 1))
+       {
+               // only try this if there was no floor in the way in the trace (no,
+               // this check seems to be not REALLY necessary, because if clip & 1,
+               // our trace will hit that thing too)
+               vector upmove = self.origin;
+               upmove_z++;
+               vector downmove = self.origin;
+               upmove_z--;
+               float type;
+               if (self.movetype == MOVETYPE_FLYMISSILE)
+                       type = MOVE_MISSILE;
+               else if (self.movetype == MOVETYPE_FLY_WORLDONLY)
+                       type = MOVE_WORLDONLY;
+               else if (self.solid == SOLID_TRIGGER || self.solid == SOLID_NOT)
+                       type = MOVE_NOMONSTERS; // only clip against bmodels
+               else
+                       type = MOVE_NORMAL;
+               vector entmins = self.mins;
+               vector entmaxs = self.maxs;
+               tracebox(upmove, entmins, entmaxs, downmove, type, self);
+               if(trace_fraction < 1 && trace_plane_normal_z > 0.7)
+                       clip |= 1; // but we HAVE found a floor
+       }
+
+       // if the move did not hit the ground at any point, we're not on ground
+//     if(!(clip & 1))
+//             self.flags = self.flags & ~FL_ONGROUND;
+
+//     SV_CheckVelocity(self);
+//     SV_LinkEdict(self);
+//     SV_LinkEdict_TouchAreaGrid(self);
+
+       if(clip & 8) // teleport
+               return;
+
+       if (self.flags & FL_WATERJUMP)
+               return;
+
+//     if (autocvar_sv_nostep)
+//             return;
+
+       vector originalmove_origin = self.origin;
+       vector originalmove_velocity = self.velocity;
+       float originalmove_flags = self.flags;
+       entity originalmove_groundentity = self.groundentity;
+
+       // if move didn't block on a step, return
+       if (clip & 2)
+       {
+               // if move was not trying to move into the step, return
+               if (fabs(start_velocity_x) < 0.03125 && fabs(start_velocity_y) < 0.03125)
+                       return;
+
+               if (self.movetype != MOVETYPE_FLY)
+               {
+                       // return if gibbed by a trigger
+                       if (self.movetype != MOVETYPE_WALK)
+                               return;
+
+                       // return if attempting to jump while airborn (unless sv_jumpstep)
+//                     if (!autocvar_sv_jumpstep)
+//                             if (!oldonground && PRVM_serveredictfloat(self, waterlevel) == 0)
+//                                     return;
+               }
+
+               // try moving up and forward to go up a step
+               // back to start pos
+               setorigin(self, start_origin);
+               self.velocity = start_velocity;
+
+               // move up
+               vector upmove = '0 0 0';
+               upmove_z = autocvar_sv_stepheight;
+//             if(!SV_PushEntity(&trace, self, upmove, true))
+//             {
+//                     // we got teleported when upstepping... must abort the move
+//                     return;
+//             }
+
+               // move forward
+               self.velocity_z = 0;
+//             clip = SV_FlyMove (self, PHYS_INPUT_TIMELENGTH, applygravity, stepnormal, hitsupercontentsmask, 0);
+               self.velocity_z += start_velocity_z;
+//             if(clip & 8)
+//             {
+//                     // we got teleported when upstepping... must abort the move
+//                     // note that z velocity handling may not be what QC expects here, but we cannot help it
+//                     return;
+//             }
+
+//             SV_CheckVelocity(self);
+//             SV_LinkEdict(self);
+//             SV_LinkEdict_TouchAreaGrid(self);
+
+               // check for stuckness, possibly due to the limited precision of floats
+               // in the clipping hulls
+               if (clip
+                && fabs(originalmove_origin_y - self.origin_y < 0.03125)
+                && fabs(originalmove_origin_x - self.origin_x < 0.03125))
+               {
+                       //Con_Printf("wall\n");
+                       // stepping up didn't make any progress, revert to original move
+                       setorigin(self, originalmove_origin);
+                       self.velocity = originalmove_velocity;
+                       self.flags = originalmove_flags;
+                       self.groundentity = originalmove_groundentity;
+                       return;
+               }
+
+               //Con_Printf("step - ");
+
+               // extra friction based on view angle
+//             if (clip & 2 && sv_wallfriction.integer)
+//                     SV_WallFriction (self, stepnormal);
+       }
+       // don't do the down move if stepdown is disabled, moving upward, not in water, or the move started offground or ended onground
+//     else if (!autocvar_sv_gameplayfix_stepdown || self.waterlevel >= 3 || start_velocity_z >= (1.0 / 32.0) || !oldonground || (self.flags & FL_ONGROUND))
+//             return;
+
+       // move down
+       vector downmove = '0 0 0';
+       downmove_z = -autocvar_sv_stepheight + start_velocity_z*PHYS_INPUT_TIMELENGTH;
+//     if(!SV_PushEntity (&downtrace, self, downmove, true))
+//     {
+//             // we got teleported when downstepping... must abort the move
+//             return;
+//     }
+
+       if (trace_fraction < 1 && trace_plane_normal_z > 0.7)
+       {
+               // this has been disabled so that you can't jump when you are stepping
+               // up while already jumping (also known as the Quake2 double jump bug)
+       }
+       else
+       {
+               //Con_Printf("slope\n");
+               // if the push down didn't end up on good ground, use the move without
+               // the step up.  This happens near wall / slope combinations, and can
+               // cause the player to hop up higher on a slope too steep to climb
+               setorigin(self, originalmove_origin);
+               self.velocity = originalmove_velocity;
+               self.flags = originalmove_flags;
+               self.groundentity = originalmove_groundentity;
+       }
+
+//     SV_CheckVelocity(self);
+//     SV_LinkEdict(self);
+//     SV_LinkEdict_TouchAreaGrid(self);
+}
+#endif
+void PM_walk(float buttons_prev, float maxspd_mod)
+{
+       if (!WAS_ONGROUND(self))
+       {
+               if (autocvar_speedmeter)
+                       dprint(strcat("landing velocity: ", vtos(self.velocity), " (abs: ", ftos(vlen(self.velocity)), ")\n"));
+               if (self.lastground < time - 0.3)
+                       self.velocity *= (1 - PHYS_FRICTION_ONLAND);
+               if (self.jumppadcount > 1)
+                       dprint(strcat(ftos(self.jumppadcount), "x jumppad combo\n"));
+               self.jumppadcount = 0;
+       }
+       // walking
+       makevectors(PHYS_INPUT_ANGLES(self).y * '0 1 0');
+       vector wishvel = v_forward * PHYS_INPUT_MOVEVALUES(self).x
+                                       + v_right * PHYS_INPUT_MOVEVALUES(self).y;
+       // acceleration
+       vector wishdir = normalize(wishvel);
+       float wishspeed = vlen(wishvel);
+
+       wishspeed = min(wishspeed, PHYS_MAXSPEED(self) * maxspd_mod);
+       if (IS_DUCKED(self))
+               wishspeed *= 0.5;
+
+       // apply edge friction
+       float f = vlen(vec2(self.velocity));
+       if (f > 0)
+       {
+               float realfriction;
+               trace_dphitq3surfaceflags = 0;
+               tracebox(self.origin, self.mins, self.maxs, self.origin - '0 0 1', MOVE_NOMONSTERS, self);
+               // TODO: apply edge friction
+               // apply ground friction
+               if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SLICK)
+                       realfriction = PHYS_FRICTION_SLICK;
+               else
+                       realfriction = PHYS_FRICTION;
+
+               f = 1 - PHYS_INPUT_TIMELENGTH * realfriction * ((f < PHYS_STOPSPEED) ? (PHYS_STOPSPEED / f) : 1);
+               f = max(0, f);
+               self.velocity *= f;
+               /*
+                  Mathematical analysis time!
+
+                  Our goal is to invert this mess.
+
+                  For the two cases we get:
+                       v = v0 * (1 - PHYS_INPUT_TIMELENGTH * (PHYS_STOPSPEED / v0) * PHYS_FRICTION)
+                         = v0 - PHYS_INPUT_TIMELENGTH * PHYS_STOPSPEED * PHYS_FRICTION
+                       v0 = v + PHYS_INPUT_TIMELENGTH * PHYS_STOPSPEED * PHYS_FRICTION
+                  and
+                       v = v0 * (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION)
+                       v0 = v / (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION)
+
+                  These cases would be chosen ONLY if:
+                       v0 < PHYS_STOPSPEED
+                       v + PHYS_INPUT_TIMELENGTH * PHYS_STOPSPEED * PHYS_FRICTION < PHYS_STOPSPEED
+                       v < PHYS_STOPSPEED * (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION)
+                  and, respectively:
+                       v0 >= PHYS_STOPSPEED
+                       v / (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION) >= PHYS_STOPSPEED
+                       v >= PHYS_STOPSPEED * (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION)
+                */
+       }
+       float addspeed = wishspeed - self.velocity * wishdir;
+       if (addspeed > 0)
+       {
+               float accelspeed = min(PHYS_ACCELERATE * PHYS_INPUT_TIMELENGTH * wishspeed, addspeed);
+               self.velocity += accelspeed * wishdir;
+       }
+       float g = PHYS_GRAVITY * PHYS_ENTGRAVITY(self) * PHYS_INPUT_TIMELENGTH;
+       if (!(GAMEPLAYFIX_NOGRAVITYONGROUND))
+               self.velocity_z -= g * (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE ? 0.5 : 1);
+       if (self.velocity * self.velocity)
+               PM_ClientMovement_Move();
+       if (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
+               if (!IS_ONGROUND(self) || !GAMEPLAYFIX_NOGRAVITYONGROUND)
+                       self.velocity_z -= g * 0.5;
+}
+
+void PM_air(float buttons_prev, float maxspd_mod)
+{
+       makevectors(PHYS_INPUT_ANGLES(self).y * '0 1 0');
+       vector wishvel = v_forward * PHYS_INPUT_MOVEVALUES(self).x
+                                       + v_right * PHYS_INPUT_MOVEVALUES(self).y;
+       // acceleration
+       vector wishdir = normalize(wishvel);
+       float wishspeed = vlen(wishvel);
+
+#ifdef SVQC
+       if (time >= self.teleport_time)
+#else
+       if (pmove_waterjumptime <= 0)
+#endif
+       {
+               float maxairspd = PHYS_MAXAIRSPEED * min(maxspd_mod, 1);
+
+               // apply air speed limit
+               float airaccelqw = PHYS_AIRACCEL_QW(self);
+               float wishspeed0 = wishspeed;
+               wishspeed = min(wishspeed, maxairspd);
+               if (IS_DUCKED(self))
+                       wishspeed *= 0.5;
+               float airaccel = PHYS_AIRACCELERATE * min(maxspd_mod, 1);
+
+               float accelerating = (self.velocity * wishdir > 0);
+               float wishspeed2 = wishspeed;
+
+               // CPM: air control
+               if (PHYS_AIRSTOPACCELERATE)
+               {
+                       vector curdir = normalize(vec2(self.velocity));
+                       airaccel += (PHYS_AIRSTOPACCELERATE*maxspd_mod - airaccel) * max(0, -(curdir * wishdir));
+               }
+               // note that for straight forward jumping:
+               // step = accel * PHYS_INPUT_TIMELENGTH * wishspeed0;
+               // accel  = bound(0, wishspeed - vel_xy_current, step) * accelqw + step * (1 - accelqw);
+               // -->
+               // dv/dt = accel * maxspeed (when slow)
+               // dv/dt = accel * maxspeed * (1 - accelqw) (when fast)
+               // log dv/dt = logaccel + logmaxspeed (when slow)
+               // log dv/dt = logaccel + logmaxspeed + log(1 - accelqw) (when fast)
+               float strafity = IsMoveInDirection(PHYS_INPUT_MOVEVALUES(self), -90) + IsMoveInDirection(PHYS_INPUT_MOVEVALUES(self), +90); // if one is nonzero, other is always zero
+               if (PHYS_MAXAIRSTRAFESPEED)
+                       wishspeed = min(wishspeed, GeomLerp(PHYS_MAXAIRSPEED*maxspd_mod, strafity, PHYS_MAXAIRSTRAFESPEED*maxspd_mod));
+               if (PHYS_AIRSTRAFEACCELERATE)
+                       airaccel = GeomLerp(airaccel, strafity, PHYS_AIRSTRAFEACCELERATE*maxspd_mod);
+               if (PHYS_AIRSTRAFEACCEL_QW(self))
+                       airaccelqw =
+               (((strafity > 0.5 ? PHYS_AIRSTRAFEACCEL_QW(self) : PHYS_AIRACCEL_QW(self)) >= 0) ? +1 : -1)
+               *
+               (1 - GeomLerp(1 - fabs(PHYS_AIRACCEL_QW(self)), strafity, 1 - fabs(PHYS_AIRSTRAFEACCEL_QW(self))));
+               // !CPM
+
+               if (PHYS_WARSOWBUNNY_TURNACCEL && accelerating && PHYS_INPUT_MOVEVALUES(self).y == 0 && PHYS_INPUT_MOVEVALUES(self).x != 0)
+                       PM_AirAccelerate(wishdir, wishspeed2);
+               else
+                       PM_Accelerate(wishdir, wishspeed, wishspeed0, airaccel, airaccelqw, PHYS_AIRACCEL_QW_STRETCHFACTOR(self), PHYS_AIRACCEL_SIDEWAYS_FRICTION / maxairspd, PHYS_AIRSPEEDLIMIT_NONQW(self));
+
+               if (PHYS_AIRCONTROL)
+                       CPM_PM_Aircontrol(wishdir, wishspeed2);
+       }
+       float g = PHYS_GRAVITY * PHYS_ENTGRAVITY(self) * PHYS_INPUT_TIMELENGTH;
+       if (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
+               self.velocity_z -= g * 0.5;
+       else
+               self.velocity_z -= g;
+       PM_ClientMovement_Move();
+       if (!IS_ONGROUND(self) || !(GAMEPLAYFIX_NOGRAVITYONGROUND))
+               if (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
+                       self.velocity_z -= g * 0.5;
+}
+
+// used for calculating airshots
+bool IsFlying(entity a)
+{
+       if(IS_ONGROUND(a))
+               return false;
+       if(a.waterlevel >= WATERLEVEL_SWIMMING)
+               return false;
+       traceline(a.origin, a.origin - '0 0 48', MOVE_NORMAL, a);
+       if(trace_fraction < 1)
+               return false;
+       return true;
+}
+
+void PM_Main()
+{
+       float buttons = PHYS_INPUT_BUTTON_MASK(self);
+#ifdef CSQC
+       self.team = myteam + 1; // is this correct?
+       if (!(PHYS_INPUT_BUTTON_JUMP(self))) // !jump
+               UNSET_JUMP_HELD(self); // canjump = true
+       pmove_waterjumptime -= PHYS_INPUT_TIMELENGTH;
+
+       PM_ClientMovement_UpdateStatus(true);
+#endif
+       
+
+#ifdef SVQC
+       WarpZone_PlayerPhysics_FixVAngle();
+#endif
+       float maxspeed_mod = 1;
+       maxspeed_mod *= PM_check_keepaway();
+       maxspeed_mod *= PHYS_HIGHSPEED;
+
+#ifdef SVQC
+       Physics_UpdateStats(maxspeed_mod);
+
+       if (self.PlayerPhysplug)
+               if (self.PlayerPhysplug())
+                       return;
+#endif
+
+       PM_check_race_movetime();
+#ifdef SVQC
+       anticheat_physics();
+#endif
+
+       if (PM_check_specialcommand(buttons))
+               return;
+#ifdef SVQC
+       if (sv_maxidle > 0)
+       {
+               if (buttons != self.buttons_old || PHYS_INPUT_MOVEVALUES(self) != self.movement_old || PHYS_INPUT_ANGLES(self) != self.v_angle_old)
+                       self.parm_idlesince = time;
+       }
+#endif
+       float buttons_prev = self.buttons_old;
+       self.buttons_old = buttons;
+       self.movement_old = PHYS_INPUT_MOVEVALUES(self);
+       self.v_angle_old = PHYS_INPUT_ANGLES(self);
+
+       PM_check_nickspam();
+
+       PM_check_punch();
+#ifdef SVQC
+       if (IS_BOT_CLIENT(self))
+       {
+               if (playerdemo_read())
+                       return;
+               bot_think();
+       }
+
+       if (IS_PLAYER(self))
+#endif
+       {
+#ifdef SVQC
+               if (self.race_penalty)
+                       if (time > self.race_penalty)
+                               self.race_penalty = 0;
+#endif
+
+               float not_allowed_to_move = 0;
+#ifdef SVQC
+               if (self.race_penalty)
+                       not_allowed_to_move = 1;
+#endif
+#ifdef SVQC
+               if (time < game_starttime)
+                       not_allowed_to_move = 1;
+#endif
+
+               if (not_allowed_to_move)
+               {
+                       self.velocity = '0 0 0';
+                       self.movetype = MOVETYPE_NONE;
+#ifdef SVQC
+                       self.disableclientprediction = 2;
+#endif
+               }
+#ifdef SVQC
+               else if (self.disableclientprediction == 2)
+               {
+                       if (self.movetype == MOVETYPE_NONE)
+                               self.movetype = MOVETYPE_WALK;
+                       self.disableclientprediction = 0;
+               }
+#endif
+       }
+
+#ifdef SVQC
+       if (self.movetype == MOVETYPE_NONE)
+               return;
+
+       // when we get here, disableclientprediction cannot be 2
+       self.disableclientprediction = 0;
+#endif
+
+       PM_check_spider();
+
+       PM_check_frozen();
+
+       PM_check_blocked();
+
+       maxspeed_mod = 1;
+
+       if (self.in_swamp)
+               maxspeed_mod *= self.swamp_slowdown; //cvar("g_balance_swamp_moverate");
+
+       // conveyors: first fix velocity
+       if (self.conveyor.state)
+               self.velocity -= self.conveyor.movedir;
+
+#ifdef SVQC
+       MUTATOR_CALLHOOK(PlayerPhysics);
+#endif
+#ifdef CSQC
+       PM_multijump();
+#endif
+
+//     float forcedodge = 1;
+//     if(forcedodge) {
+//#ifdef CSQC
+//             PM_dodging_checkpressedkeys();
+//#endif
+//             PM_dodging();
+//             PM_ClientMovement_Move();
+//             return;
+//     }
+
+#ifdef SVQC
+       if (!IS_PLAYER(self))
+       {
+               maxspeed_mod *= autocvar_sv_spectator_speed_multiplier;
+               if (!self.spectatorspeed)
+                       self.spectatorspeed = maxspeed_mod;
+               if (self.impulse && self.impulse <= 19 || (self.impulse >= 200 && self.impulse <= 209) || (self.impulse >= 220 && self.impulse <= 229))
+               {
+                       if (self.lastclassname != "player")
+                       {
+                               if (self.impulse == 10 || self.impulse == 15 || self.impulse == 18 || (self.impulse >= 200 && self.impulse <= 209))
+                                       self.spectatorspeed = bound(1, self.spectatorspeed + 0.5, 5);
+                               else if (self.impulse == 11)
+                                       self.spectatorspeed = maxspeed_mod;
+                               else if (self.impulse == 12 || self.impulse == 16  || self.impulse == 19 || (self.impulse >= 220 && self.impulse <= 229))
+                                       self.spectatorspeed = bound(1, self.spectatorspeed - 0.5, 5);
+                               else if (self.impulse >= 1 && self.impulse <= 9)
+                                       self.spectatorspeed = 1 + 0.5 * (self.impulse - 1);
+                       } // otherwise just clear
+                       self.impulse = 0;
+               }
+               maxspeed_mod *= self.spectatorspeed;
+       }
+#endif
+
+       if(PHYS_DEAD(self))
+               goto end;
+
+#ifdef SVQC
+       if (!self.fixangle && !g_bugrigs)
+               self.angles = '0 1 0' * PHYS_INPUT_ANGLES(self).y;
+#endif
+
+       PM_check_hitground();
+
+       if(IsFlying(self))
+               self.wasFlying = 1;
+
+       if (IS_PLAYER(self))
+               CheckPlayerJump();
+
+       if (self.flags & /* FL_WATERJUMP */ 2048)
+       {
+               self.velocity_x = self.movedir_x;
+               self.velocity_y = self.movedir_y;
+               if (time > self.teleport_time || self.waterlevel == WATERLEVEL_NONE)
+               {
+                       self.flags &= ~/* FL_WATERJUMP */ 2048;
+                       self.teleport_time = 0;
+               }
+       }
+
+#ifdef SVQC
+       else if (g_bugrigs && IS_PLAYER(self))
+               RaceCarPhysics();
+#endif
+
+       else if (self.movetype == MOVETYPE_NOCLIP || self.movetype == MOVETYPE_FLY || self.movetype == MOVETYPE_FLY_WORLDONLY)
+               PM_fly(maxspeed_mod);
+
+       else if (self.waterlevel >= WATERLEVEL_SWIMMING)
+               PM_swim(maxspeed_mod);
+
+       else if (time < self.ladder_time)
+               PM_ladder(maxspeed_mod);
+
+       else if (ITEMS(self) & IT_USING_JETPACK)
+               PM_jetpack(maxspeed_mod);
+
+       else if (IS_ONGROUND(self))
+               PM_walk(buttons_prev, maxspeed_mod);
+
+       else
+               PM_air(buttons_prev, maxspeed_mod);
+
+#ifdef SVQC
+       if (!IS_OBSERVER(self))
+               PM_check_race();
+#endif
+       PM_check_vortex();
+
+:end
+       if (IS_ONGROUND(self))
+               self.lastground = time;
+
+       // conveyors: then break velocity again
+       if(self.conveyor.state)
+               self.velocity += self.conveyor.movedir;
+
+       self.lastflags = FLAGS(self);
+
+       self.lastclassname = self.classname;
+}
+
+#ifdef SVQC
+void SV_PlayerPhysics(void)
+#elif defined(CSQC)
+void CSQC_ClientMovement_PlayerMove_Frame(void)
+#endif
+{
+       PM_Main();
+
+#ifdef CSQC
+       self.pmove_flags = self.pm_flags;
+#endif
+}
diff --git a/qcsrc/common/physics.qh b/qcsrc/common/physics.qh
new file mode 100644 (file)
index 0000000..21ddcec
--- /dev/null
@@ -0,0 +1,265 @@
+#ifndef COMMON_PHYSICS_H
+#define COMMON_PHYSICS_H
+
+// Client/server mappings
+
+.entity conveyor;
+
+.float race_penalty;
+
+.float gravity;
+.float swamp_slowdown;
+.float lastflags;
+.float lastground;
+.float wasFlying;
+.float spectatorspeed;
+
+.vector movement_old;
+.float buttons_old;
+.vector v_angle_old;
+.string lastclassname;
+
+.float() PlayerPhysplug;
+float AdjustAirAccelQW(float accelqw, float factor);
+
+bool IsFlying(entity a);
+
+#ifdef CSQC
+
+       .int pm_flags;
+
+       float PM_multijump_checkjump();
+       void PM_multijump();
+
+       .float watertype;
+
+// TODO
+       #define IS_CLIENT(s)                                            (s).isplayermodel
+       #define IS_PLAYER(s)                                            (s).isplayermodel
+       #define isPushable(s)                                           (s).isplayermodel
+
+       float player_multijump;
+       float player_jumpheight;
+
+       #define PHYS_INPUT_ANGLES(s)                            input_angles
+// TODO
+       #define PHYS_WORLD_ANGLES(s)                            input_angles
+
+       #define PHYS_INPUT_TIMELENGTH                           input_timelength
+       #define PHYS_INPUT_FRAMETIME                            serverdeltatime
+
+       #define PHYS_INPUT_MOVEVALUES(s)                        input_movevalues
+
+       #define PHYS_INPUT_BUTTON_MASK(s)               (input_buttons | 128 * (input_movevalues_x < 0) | 256 * (input_movevalues_x > 0) | 512 * (input_movevalues_y < 0) | 1024 * (input_movevalues_y > 0))
+       #define PHYS_INPUT_BUTTON_ATCK(s)                       !!(input_buttons & 1)
+       #define PHYS_INPUT_BUTTON_JUMP(s)                       !!(input_buttons & 2)
+       #define PHYS_INPUT_BUTTON_ATCK2(s)                      !!(input_buttons & 4)
+       #define PHYS_INPUT_BUTTON_ZOOM(s)                       !!(input_buttons & 8)
+       #define PHYS_INPUT_BUTTON_CROUCH(s)                     !!(input_buttons & 16)
+       #define PHYS_INPUT_BUTTON_HOOK(s)                       !!(input_buttons & 32)
+       #define PHYS_INPUT_BUTTON_USE(s)                        !!(input_buttons & 64)
+       #define PHYS_INPUT_BUTTON_BACKWARD(s)           !!(input_buttons & 128)
+       #define PHYS_INPUT_BUTTON_FORWARD(s)            !!(input_buttons & 256)
+       #define PHYS_INPUT_BUTTON_LEFT(s)                       !!(input_buttons & 512)
+       #define PHYS_INPUT_BUTTON_RIGHT(s)                      !!(input_buttons & 1024)
+       #define PHYS_INPUT_BUTTON_JETPACK(s)            !!(input_buttons & 2048)
+
+       #define PHYS_DEAD(s)                                            s.csqcmodel_isdead
+
+       #define GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE  !!(moveflags & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE)
+       #define GAMEPLAYFIX_NOGRAVITYONGROUND                   cvar("sv_gameplayfix_nogravityonground")
+       #define GAMEPLAYFIX_Q2AIRACCELERATE                             cvar("sv_gameplayfix_q2airaccelerate")
+       #define GAMEPLAYFIX_EASIERWATERJUMP                     getstati(STAT_GAMEPLAYFIX_EASIERWATERJUMP)
+
+       #define IS_DUCKED(s)                                            !!(s.pm_flags & PMF_DUCKED)
+       #define SET_DUCKED(s)                                           s.pm_flags |= PMF_DUCKED
+       #define UNSET_DUCKED(s)                                         s.pm_flags &= ~PMF_DUCKED
+
+       #define IS_JUMP_HELD(s)                                         !!(s.pm_flags & PMF_JUMP_HELD)
+       #define SET_JUMP_HELD(s)                                        s.pm_flags |= PMF_JUMP_HELD
+       #define UNSET_JUMP_HELD(s)                                      s.pm_flags &= ~PMF_JUMP_HELD
+
+       #define IS_ONGROUND(s)                                          !!(s.pm_flags & PMF_ONGROUND)
+       #define SET_ONGROUND(s)                                         s.pm_flags |= PMF_ONGROUND
+       #define UNSET_ONGROUND(s)                                       s.pm_flags &= ~PMF_ONGROUND
+
+       #define WAS_ONGROUND(s)                                         !!(s.lastflags & PMF_ONGROUND)
+
+       #define ITEMS(s)                                                        getstati(STAT_ITEMS, 0, 24)
+
+       #define FLAGS(s)                                                        (s).pm_flags
+
+       #define PHYS_AMMO_FUEL(s)                                       getstati(STAT_FUEL)
+
+       #define PHYS_FROZEN(s)                                          getstati(STAT_FROZEN)
+
+       #define PHYS_DOUBLEJUMP                                         getstati(STAT_DOUBLEJUMP)
+
+       #define PHYS_JUMPSPEEDCAP_MIN                           getstatf(STAT_MOVEVARS_JUMPSPEEDCAP_MIN)
+       #define PHYS_JUMPSPEEDCAP_MAX                           getstatf(STAT_MOVEVARS_JUMPSPEEDCAP_MAX)
+       #define PHYS_JUMPSPEEDCAP_DISABLE_ONRAMPS       getstati(STAT_MOVEVARS_JUMPSPEEDCAP_DISABLE_ONRAMPS)
+
+       #define PHYS_TRACK_CANJUMP(s)                           getstati(STAT_MOVEVARS_TRACK_CANJUMP)
+       #define PHYS_ACCELERATE                                         getstatf(STAT_MOVEVARS_ACCELERATE)
+       #define PHYS_AIRACCEL_QW(s)                                     getstatf(STAT_MOVEVARS_AIRACCEL_QW)
+       #define PHYS_AIRACCEL_QW_STRETCHFACTOR(s)       getstatf(STAT_MOVEVARS_AIRACCEL_QW_STRETCHFACTOR)
+       #define PHYS_AIRACCEL_SIDEWAYS_FRICTION         getstatf(STAT_MOVEVARS_AIRACCEL_SIDEWAYS_FRICTION)
+       #define PHYS_AIRACCELERATE                                      getstatf(STAT_MOVEVARS_AIRACCELERATE)
+       #define PHYS_AIRCONTROL                                         getstatf(STAT_MOVEVARS_AIRCONTROL)
+       #define PHYS_AIRCONTROL_PENALTY                         getstatf(STAT_MOVEVARS_AIRCONTROL_PENALTY)
+       #define PHYS_AIRCONTROL_POWER                           getstatf(STAT_MOVEVARS_AIRCONTROL_POWER)
+       #define PHYS_AIRSPEEDLIMIT_NONQW(s)                     getstatf(STAT_MOVEVARS_AIRSPEEDLIMIT_NONQW)
+       #define PHYS_AIRSTOPACCELERATE                          getstatf(STAT_MOVEVARS_AIRSTOPACCELERATE)
+       #define PHYS_AIRSTRAFEACCEL_QW(s)                       getstatf(STAT_MOVEVARS_AIRSTRAFEACCEL_QW)
+       #define PHYS_AIRSTRAFEACCELERATE                        getstatf(STAT_MOVEVARS_AIRSTRAFEACCELERATE)
+       #define PHYS_ENTGRAVITY(s)                                      getstatf(STAT_MOVEVARS_ENTGRAVITY)
+       #define PHYS_FRICTION                                           getstatf(STAT_MOVEVARS_FRICTION)
+       #define PHYS_FRICTION_SLICK                                     getstatf(STAT_MOVEVARS_FRICTION_SLICK)
+       #define PHYS_FRICTION_ONLAND                            getstatf(STAT_MOVEVARS_FRICTION_ONLAND)
+       #define PHYS_GRAVITY                                            getstatf(STAT_MOVEVARS_GRAVITY)
+       #define PHYS_HIGHSPEED                                          getstatf(STAT_MOVEVARS_HIGHSPEED)
+       #define PHYS_JUMPVELOCITY                                       getstatf(STAT_MOVEVARS_JUMPVELOCITY)
+       #define PHYS_MAXAIRSPEED                                        getstatf(STAT_MOVEVARS_MAXAIRSPEED)
+       #define PHYS_MAXAIRSTRAFESPEED                          getstatf(STAT_MOVEVARS_MAXAIRSTRAFESPEED)
+       #define PHYS_MAXSPEED(s)                                        getstatf(STAT_MOVEVARS_MAXSPEED)
+       #define PHYS_STEPHEIGHT                                         getstatf(STAT_MOVEVARS_STEPHEIGHT)
+       #define PHYS_STOPSPEED                                          getstatf(STAT_MOVEVARS_STOPSPEED)
+       #define PHYS_WARSOWBUNNY_ACCEL                          getstatf(STAT_MOVEVARS_WARSOWBUNNY_ACCEL)
+       #define PHYS_WARSOWBUNNY_BACKTOSIDERATIO        getstatf(STAT_MOVEVARS_WARSOWBUNNY_BACKTOSIDERATIO)
+       #define PHYS_WARSOWBUNNY_AIRFORWARDACCEL        getstatf(STAT_MOVEVARS_WARSOWBUNNY_AIRFORWARDACCEL)
+       #define PHYS_WARSOWBUNNY_TOPSPEED                       getstatf(STAT_MOVEVARS_WARSOWBUNNY_TOPSPEED)
+       #define PHYS_WARSOWBUNNY_TURNACCEL                      getstatf(STAT_MOVEVARS_WARSOWBUNNY_TURNACCEL)
+
+       #define PHYS_JETPACK_ACCEL_UP                           getstatf(STAT_JETPACK_ACCEL_UP)
+       #define PHYS_JETPACK_ACCEL_SIDE                         getstatf(STAT_JETPACK_ACCEL_SIDE)
+       #define PHYS_JETPACK_ANTIGRAVITY                        getstatf(STAT_JETPACK_ANTIGRAVITY)
+       #define PHYS_JETPACK_FUEL                                       getstatf(STAT_JETPACK_FUEL)
+       #define PHYS_JETPACK_MAXSPEED_UP                        getstatf(STAT_JETPACK_MAXSPEED_UP)
+       #define PHYS_JETPACK_MAXSPEED_SIDE                      getstatf(STAT_JETPACK_MAXSPEED_SIDE)
+
+       #define PHYS_DODGING_FROZEN                                     getstati(STAT_DODGING_FROZEN)
+
+#elif defined(SVQC)
+
+       .float stat_sv_airaccel_qw;
+       .float stat_sv_airstrafeaccel_qw;
+       .float stat_sv_airspeedlimit_nonqw;
+       .float stat_sv_maxspeed;
+       .float stat_movement_highspeed;
+
+       .float stat_sv_friction_on_land;
+       .float stat_sv_friction_slick;
+
+       .float stat_doublejump;
+
+       .float stat_jumpspeedcap_min;
+       .float stat_jumpspeedcap_max;
+       .float stat_jumpspeedcap_disable_onramps;
+
+       .float stat_jetpack_accel_side;
+       .float stat_jetpack_accel_up;
+       .float stat_jetpack_antigravity;
+       .float stat_jetpack_fuel;
+       .float stat_jetpack_maxspeed_up;
+       .float stat_jetpack_maxspeed_side;
+       .float stat_gameplayfix_easierwaterjump;
+
+       #define PHYS_INPUT_ANGLES(s)                            s.v_angle
+       #define PHYS_WORLD_ANGLES(s)                            s.angles
+
+       #define PHYS_INPUT_TIMELENGTH                           frametime
+       #define PHYS_INPUT_FRAMETIME                            sys_frametime
+
+       #define PHYS_INPUT_MOVEVALUES(s)                        s.movement
+       // TODO: cache
+       #define PHYS_INPUT_BUTTON_MASK(s)               (s.BUTTON_ATCK | 2 * s.BUTTON_JUMP | 4 * s.BUTTON_ATCK2 | 8 * s.BUTTON_ZOOM | 16 * s.BUTTON_CROUCH | 32 * s.BUTTON_HOOK | 64 * s.BUTTON_USE | 128 * (s.movement_x < 0) | 256 * (s.movement_x > 0) | 512 * (s.movement_y < 0) | 1024 * (s.movement_y > 0))
+       #define PHYS_INPUT_BUTTON_ATCK(s)                       s.BUTTON_ATCK
+       #define PHYS_INPUT_BUTTON_JUMP(s)                       s.BUTTON_JUMP
+       #define PHYS_INPUT_BUTTON_ATCK2(s)                      s.BUTTON_ATCK2
+       #define PHYS_INPUT_BUTTON_ZOOM(s)                       s.BUTTON_ZOOM
+       #define PHYS_INPUT_BUTTON_CROUCH(s)                     s.BUTTON_CROUCH
+       #define PHYS_INPUT_BUTTON_HOOK(s)                       s.BUTTON_HOOK
+       #define PHYS_INPUT_BUTTON_USE(s)                        s.BUTTON_USE
+       #define PHYS_INPUT_BUTTON_BACKWARD(s)           (s.movement_x < 0)
+       #define PHYS_INPUT_BUTTON_FORWARD(s)            (s.movement_x > 0)
+       #define PHYS_INPUT_BUTTON_LEFT(s)                       (s.movement_y < 0)
+       #define PHYS_INPUT_BUTTON_RIGHT(s)                      (s.movement_y > 0)
+       #define PHYS_INPUT_BUTTON_JETPACK(s)            s.BUTTON_JETPACK
+
+       #define PHYS_DEAD(s)                                            s.deadflag != DEAD_NO
+
+       #define GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE  autocvar_sv_gameplayfix_gravityunaffectedbyticrate
+       #define GAMEPLAYFIX_NOGRAVITYONGROUND                   cvar("sv_gameplayfix_nogravityonground")
+       #define GAMEPLAYFIX_Q2AIRACCELERATE                             autocvar_sv_gameplayfix_q2airaccelerate
+       #define GAMEPLAYFIX_EASIERWATERJUMP                             cvar("sv_gameplayfix_easierwaterjump")
+
+       #define IS_DUCKED(s)                                            s.crouch
+       #define SET_DUCKED(s)                                           s.crouch = true
+       #define UNSET_DUCKED(s)                                         s.crouch = false
+
+       #define IS_JUMP_HELD(s)                                         !(s.flags & FL_JUMPRELEASED)
+       #define SET_JUMP_HELD(s)                                        s.flags &= ~FL_JUMPRELEASED
+       #define UNSET_JUMP_HELD(s)                                      s.flags |= FL_JUMPRELEASED
+
+       #define IS_ONGROUND(s)                                          !!(self.flags & FL_ONGROUND)
+       #define SET_ONGROUND(s)                                         s.flags |= FL_ONGROUND
+       #define UNSET_ONGROUND(s)                                       s.flags &= ~FL_ONGROUND
+
+       #define WAS_ONGROUND(s)                                         !!((s).lastflags & FL_ONGROUND)
+
+       #define ITEMS(s)                                                        s.items
+
+       #define FLAGS(s)                                                        (s).flags
+
+       #define PHYS_AMMO_FUEL(s)                                       s.ammo_fuel
+
+       #define PHYS_FROZEN(s)                                          s.frozen
+
+       #define PHYS_DOUBLEJUMP                                         autocvar_sv_doublejump
+
+       #define PHYS_JUMPSPEEDCAP_MIN                           autocvar_sv_jumpspeedcap_min
+       #define PHYS_JUMPSPEEDCAP_MAX                           autocvar_sv_jumpspeedcap_max
+       #define PHYS_JUMPSPEEDCAP_DISABLE_ONRAMPS       autocvar_sv_jumpspeedcap_max_disable_on_ramps
+
+       #define PHYS_TRACK_CANJUMP(s)                           s.cvar_cl_movement_track_canjump
+       #define PHYS_ACCELERATE                                         autocvar_sv_accelerate
+       #define PHYS_AIRACCEL_QW(s)                                     s.stat_sv_airaccel_qw
+       #define PHYS_AIRACCEL_QW_STRETCHFACTOR(s)       autocvar_sv_airaccel_qw_stretchfactor
+       #define PHYS_AIRACCEL_SIDEWAYS_FRICTION         autocvar_sv_airaccel_sideways_friction
+       #define PHYS_AIRACCELERATE                                      autocvar_sv_airaccelerate
+       #define PHYS_AIRCONTROL                                         autocvar_sv_aircontrol
+       #define PHYS_AIRCONTROL_PENALTY                         autocvar_sv_aircontrol_penalty
+       #define PHYS_AIRCONTROL_POWER                           autocvar_sv_aircontrol_power
+       #define PHYS_AIRSPEEDLIMIT_NONQW(s)                     s.stat_sv_airspeedlimit_nonqw
+       #define PHYS_AIRSTOPACCELERATE                          autocvar_sv_airstopaccelerate
+       #define PHYS_AIRSTRAFEACCEL_QW(s)                       s.stat_sv_airstrafeaccel_qw
+       #define PHYS_AIRSTRAFEACCELERATE                        autocvar_sv_airstrafeaccelerate
+       #define PHYS_ENTGRAVITY(s)                                      s.gravity
+       #define PHYS_FRICTION                                           autocvar_sv_friction
+       #define PHYS_FRICTION_SLICK                                     autocvar_sv_friction_slick
+       #define PHYS_FRICTION_ONLAND                            autocvar_sv_friction_on_land
+       #define PHYS_GRAVITY                                            autocvar_sv_gravity
+       #define PHYS_HIGHSPEED                                          autocvar_g_movement_highspeed
+       #define PHYS_JUMPVELOCITY                                       autocvar_sv_jumpvelocity
+       #define PHYS_MAXAIRSPEED                                        autocvar_sv_maxairspeed
+       #define PHYS_MAXAIRSTRAFESPEED                          autocvar_sv_maxairstrafespeed
+       #define PHYS_MAXSPEED(s)                                        s.stat_sv_maxspeed
+       #define PHYS_STEPHEIGHT                                         autocvar_sv_stepheight
+       #define PHYS_STOPSPEED                                          autocvar_sv_stopspeed
+       #define PHYS_WARSOWBUNNY_ACCEL                          autocvar_sv_warsowbunny_accel
+       #define PHYS_WARSOWBUNNY_BACKTOSIDERATIO        autocvar_sv_warsowbunny_backtosideratio
+       #define PHYS_WARSOWBUNNY_AIRFORWARDACCEL        autocvar_sv_warsowbunny_airforwardaccel
+       #define PHYS_WARSOWBUNNY_TOPSPEED                       autocvar_sv_warsowbunny_topspeed
+       #define PHYS_WARSOWBUNNY_TURNACCEL                      autocvar_sv_warsowbunny_turnaccel
+
+       #define PHYS_JETPACK_ACCEL_UP                           autocvar_g_jetpack_acceleration_up
+       #define PHYS_JETPACK_ACCEL_SIDE                         autocvar_g_jetpack_acceleration_side
+       #define PHYS_JETPACK_ANTIGRAVITY                        autocvar_g_jetpack_antigravity
+       #define PHYS_JETPACK_FUEL                                       autocvar_g_jetpack_fuel
+       #define PHYS_JETPACK_MAXSPEED_UP                        autocvar_g_jetpack_maxspeed_up
+       #define PHYS_JETPACK_MAXSPEED_SIDE                      autocvar_g_jetpack_maxspeed_side
+
+       #define PHYS_DODGING_FROZEN                                     autocvar_sv_dodging_frozen
+
+#endif
+#endif
index 481713cc9cb8f17df86aad4be260f8fd6f5ba76a..0095c60a31f3614be5c68bfdcd72f840aaa7958d 100644 (file)
@@ -225,36 +225,36 @@ const int STAT_REVIVE_PROGRESS        = 106;
 // 187 empty?
 // 188 empty?
 // 189 empty?
-// 190 empty?
-// 191 empty?
-// 192 empty?
-// 193 empty?
-// 194 empty?
-// 195 empty?
-// 196 empty?
-// 197 empty?
-// 198 empty?
-// 199 empty?
-// 200 empty?
-// 201 empty?
-// 202 empty?
-// 203 empty?
-// 204 empty?
-// 205 empty?
-// 206 empty?
-// 207 empty?
-// 208 empty?
-// 209 empty?
-// 210 empty?
-// 211 empty?
-// 212 empty?
-// 213 empty?
-// 214 empty?
-// 215 empty?
-// 216 empty?
-// 217 empty?
-// 218 empty?
-// 219 empty?
+const int STAT_GAMEPLAYFIX_EASIERWATERJUMP                       = 190;
+const int STAT_MOVEVARS_FRICTION_SLICK                = 191;
+const int STAT_MOVEVARS_FRICTION_ONLAND               = 192;
+const int STAT_MOVEVARS_JUMPSPEEDCAP_DISABLE_ONRAMPS  = 193;
+const int STAT_MOVEVARS_JUMPSPEEDCAP_MAX              = 194;
+const int STAT_MOVEVARS_JUMPSPEEDCAP_MIN              = 195;
+const int STAT_DOUBLEJUMP                             = 196;
+const int STAT_MOVEVARS_TRACK_CANJUMP                 = 197;
+const int STAT_MULTIJUMP_ADD                          = 198;
+const int STAT_MULTIJUMP_SPEED                        = 199;
+const int STAT_MULTIJUMP                              = 200;
+const int STAT_DODGING_TIMEOUT                        = 201;
+const int STAT_DODGING_WALL                           = 202;
+const int STAT_DODGING_UP_SPEED                       = 203;
+const int STAT_DODGING_RAMP_TIME                      = 204;
+const int STAT_DODGING_HEIGHT_THRESHOLD               = 205;
+const int STAT_DODGING_DISTANCE_THRESHOLD             = 206;
+const int STAT_DODGING_HORIZ_SPEED                    = 207;
+const int STAT_DODGING_DELAY                          = 208;
+const int STAT_DODGING_FROZEN_NO_DOUBLETAP            = 209;
+const int STAT_DODGING_HORIZ_SPEED_FROZEN             = 210;
+const int STAT_DODGING                                = 211;
+const int STAT_DODGING_FROZEN                         = 212;
+const int STAT_JETPACK_MAXSPEED_UP                    = 213;
+const int STAT_JETPACK_MAXSPEED_SIDE                  = 214;
+const int STAT_JETPACK_FUEL                           = 215;
+const int STAT_JETPACK_ANTIGRAVITY                    = 216;
+const int STAT_JETPACK_ACCEL_SIDE                     = 217;
+const int STAT_JETPACK_ACCEL_UP                       = 218;
+const int STAT_MOVEVARS_HIGHSPEED                     = 219;
 const int STAT_MOVEVARS_AIRACCEL_QW_STRETCHFACTOR     = 220;
 const int STAT_MOVEVARS_AIRCONTROL_PENALTY            = 221;
 const int STAT_MOVEVARS_AIRSPEEDLIMIT_NONQW           = 222;
diff --git a/qcsrc/common/triggers/func/bobbing.qc b/qcsrc/common/triggers/func/bobbing.qc
new file mode 100644 (file)
index 0000000..9fb2f56
--- /dev/null
@@ -0,0 +1,85 @@
+#ifdef SVQC
+.float height;
+void func_bobbing_controller_think()
+{
+       vector v;
+       self.nextthink = time + 0.1;
+
+       if(self.owner.active != ACTIVE_ACTIVE)
+       {
+               self.owner.velocity = '0 0 0';
+               return;
+       }
+
+       // calculate sinewave using makevectors
+       makevectors((self.nextthink * self.owner.cnt + self.owner.phase * 360) * '0 1 0');
+       v = self.owner.destvec + self.owner.movedir * v_forward_y;
+       if(self.owner.classname == "func_bobbing") // don't brake stuff if the func_bobbing was killtarget'ed
+               // * 10 so it will arrive in 0.1 sec
+               self.owner.velocity = (v - self.owner.SUB_ORIGIN) * 10;
+}
+
+/*QUAKED spawnfunc_func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS
+Brush model that moves back and forth on one axis (default Z).
+speed : how long one cycle takes in seconds (default 4)
+height : how far the cycle moves (default 32)
+phase : cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
+noise : path/name of looping .wav file to play.
+dmg : Do this mutch dmg every .dmgtime intervall when blocked
+dmgtime : See above.
+*/
+void spawnfunc_func_bobbing()
+{
+       entity controller;
+       if (self.noise != "")
+       {
+               precache_sound(self.noise);
+               soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
+       }
+       if (!self.speed)
+               self.speed = 4;
+       if (!self.height)
+               self.height = 32;
+       // center of bobbing motion
+       self.destvec = self.origin;
+       // time scale to get degrees
+       self.cnt = 360 / self.speed;
+
+       self.active = ACTIVE_ACTIVE;
+
+       // damage when blocked
+       self.blocked = generic_plat_blocked;
+       if(self.dmg && (self.message == ""))
+               self.message = " was squished";
+    if(self.dmg && (self.message2 == ""))
+               self.message2 = "was squished by";
+       if(self.dmg && (!self.dmgtime))
+               self.dmgtime = 0.25;
+       self.dmgtime2 = time;
+
+       // how far to bob
+       if (self.spawnflags & 1) // X
+               self.movedir = '1 0 0' * self.height;
+       else if (self.spawnflags & 2) // Y
+               self.movedir = '0 1 0' * self.height;
+       else // Z
+               self.movedir = '0 0 1' * self.height;
+
+       if (!InitMovingBrushTrigger())
+               return;
+
+       // wait for targets to spawn
+       controller = spawn();
+       controller.classname = "func_bobbing_controller";
+       controller.owner = self;
+       controller.nextthink = time + 1;
+       controller.think = func_bobbing_controller_think;
+       self.SUB_NEXTTHINK = self.SUB_LTIME + 999999999;
+       self.SUB_THINK = SUB_NullThink;
+
+       // Savage: Reduce bandwith, critical on e.g. nexdm02
+       self.effects |= EF_LOWPRECISION;
+
+       // TODO make a reset function for this one
+}
+#endif
diff --git a/qcsrc/common/triggers/func/breakable.qc b/qcsrc/common/triggers/func/breakable.qc
new file mode 100644 (file)
index 0000000..9c371b5
--- /dev/null
@@ -0,0 +1,315 @@
+#ifdef SVQC
+#include "../../../server/weapons/common.qh"
+
+.entity sprite;
+
+.float dmg;
+.float dmg_edge;
+.float dmg_radius;
+.float dmg_force;
+.float debrismovetype;
+.float debrissolid;
+.vector debrisvelocity;
+.vector debrisvelocityjitter;
+.vector debrisavelocityjitter;
+.float debristime;
+.float debristimejitter;
+.float debrisfadetime;
+.float debrisdamageforcescale;
+.float debrisskin;
+
+.string mdl_dead; // or "" to hide when broken
+.string debris; // space separated list of debris models
+// other fields:
+//   mdl = particle effect name
+//   count = particle effect multiplier
+//   targetname = target to trigger to unbreak the model
+//   target = targets to trigger when broken
+//   health = amount of damage it can take
+//   spawnflags:
+//     1 = start disabled (needs to be triggered to activate)
+//     2 = indicate damage
+// notes:
+//   for mdl_dead to work, origin must be set (using a common/origin brush).
+//   Otherwise mdl_dead will be displayed at the map origin, and nobody would
+//   want that!
+
+void func_breakable_damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force);
+
+//
+// func_breakable
+// - basically func_assault_destructible for general gameplay use
+//
+void LaunchDebris (string debrisname, vector force)
+{
+       entity dbr = spawn();
+       setorigin(dbr, self.absmin
+                  + '1 0 0' * random() * (self.absmax.x - self.absmin.x)
+                  + '0 1 0' * random() * (self.absmax.y - self.absmin.y)
+                  + '0 0 1' * random() * (self.absmax.z - self.absmin.z));
+       setmodel (dbr, debrisname );
+       dbr.skin = self.debrisskin;
+       dbr.colormap = self.colormap; // inherit team colors
+       dbr.owner = self; // do not be affected by our own explosion
+       dbr.movetype = self.debrismovetype;
+       dbr.solid = self.debrissolid;
+       if(dbr.solid != SOLID_BSP) // SOLID_BSP has exact collision, MAYBE this works? TODO check this out
+               setsize(dbr, '0 0 0', '0 0 0'); // needed for performance, until engine can deal better with it
+       dbr.velocity_x = self.debrisvelocity.x + self.debrisvelocityjitter.x * crandom();
+       dbr.velocity_y = self.debrisvelocity.y + self.debrisvelocityjitter.y * crandom();
+       dbr.velocity_z = self.debrisvelocity.z + self.debrisvelocityjitter.z * crandom();
+       self.velocity = self.velocity + force * self.debrisdamageforcescale;
+       dbr.avelocity_x = random()*self.debrisavelocityjitter.x;
+       dbr.avelocity_y = random()*self.debrisavelocityjitter.y;
+       dbr.avelocity_z = random()*self.debrisavelocityjitter.z;
+       dbr.damageforcescale = self.debrisdamageforcescale;
+       if(dbr.damageforcescale)
+               dbr.takedamage = DAMAGE_YES;
+       SUB_SetFade(dbr, time + self.debristime + crandom() * self.debristimejitter, self.debrisfadetime);
+}
+
+void func_breakable_colormod()
+{
+       float h;
+       if (!(self.spawnflags & 2))
+               return;
+       h = self.health / self.max_health;
+       if(h < 0.25)
+               self.colormod = '1 0 0';
+       else if(h <= 0.75)
+               self.colormod = '1 0 0' + '0 1 0' * (2 * h - 0.5);
+       else
+               self.colormod = '1 1 1';
+
+       CSQCMODEL_AUTOUPDATE();
+}
+
+void func_breakable_look_destroyed()
+{
+       float floorZ;
+
+       if(self.solid == SOLID_BSP) // in case a misc_follow moved me, save the current origin first
+               self.dropped_origin = self.origin;
+
+       if(self.mdl_dead == "")
+               self.effects |= EF_NODRAW;
+       else {
+               if (self.origin == '0 0 0')     {       // probably no origin brush, so don't spawn in the middle of the map..
+                       floorZ = self.absmin.z;
+                       setorigin(self,((self.absmax+self.absmin)*.5));
+                       self.origin_z = floorZ;
+               }
+               setmodel(self, self.mdl_dead);
+               self.effects &= ~EF_NODRAW;
+       }
+
+       CSQCMODEL_AUTOUPDATE();
+
+       self.solid = SOLID_NOT;
+}
+
+void func_breakable_look_restore()
+{
+       setmodel(self, self.mdl);
+       self.effects &= ~EF_NODRAW;
+
+       if(self.mdl_dead != "") // only do this if we use mdl_dead, to behave better with misc_follow
+               setorigin(self, self.dropped_origin);
+
+       CSQCMODEL_AUTOUPDATE();
+
+       self.solid = SOLID_BSP;
+}
+
+void func_breakable_behave_destroyed()
+{
+       self.health = self.max_health;
+       self.takedamage = DAMAGE_NO;
+       self.bot_attack = false;
+       self.event_damage = func_null;
+       self.state = 1;
+       func_breakable_colormod();
+       if (self.noise1)
+               stopsound (self, CH_TRIGGER_SINGLE);
+}
+
+void func_breakable_behave_restore()
+{
+       self.health = self.max_health;
+       if(self.sprite)
+       {
+               WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health);
+               WaypointSprite_UpdateHealth(self.sprite, self.health);
+       }
+       self.takedamage = DAMAGE_AIM;
+       self.bot_attack = true;
+       self.event_damage = func_breakable_damage;
+       self.state = 0;
+       self.nextthink = 0; // cancel auto respawn
+       func_breakable_colormod();
+       if (self.noise1)
+               sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
+}
+
+void func_breakable_init_for_player(entity player)
+{
+       if (self.noise1 && self.state == 0 && clienttype(player) == CLIENTTYPE_REAL)
+       {
+               msg_entity = player;
+               soundto (MSG_ONE, self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
+       }
+}
+
+void func_breakable_destroyed()
+{
+       func_breakable_look_destroyed();
+       func_breakable_behave_destroyed();
+
+       CSQCMODEL_AUTOUPDATE();
+}
+
+void func_breakable_restore()
+{
+       func_breakable_look_restore();
+       func_breakable_behave_restore();
+
+       CSQCMODEL_AUTOUPDATE();
+}
+
+vector debrisforce; // global, set before calling this
+void func_breakable_destroy() {
+       float n, i;
+       string oldmsg;
+
+       activator = self.owner;
+       self.owner = world; // set by W_PrepareExplosionByDamage
+
+       // now throw around the debris
+       n = tokenize_console(self.debris);
+       for(i = 0; i < n; ++i)
+               LaunchDebris(argv(i), debrisforce);
+
+       func_breakable_destroyed();
+
+       if(self.noise)
+               sound (self, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
+
+       if(self.dmg)
+               RadiusDamage(self, activator, self.dmg, self.dmg_edge, self.dmg_radius, self, world, self.dmg_force, DEATH_HURTTRIGGER, world);
+
+       if(self.cnt)
+               pointparticles(self.cnt, self.absmin * 0.5 + self.absmax * 0.5, '0 0 0', self.count);
+
+       if(self.respawntime)
+       {
+               self.think = func_breakable_restore;
+               self.nextthink = time + self.respawntime + crandom() * self.respawntimejitter;
+       }
+
+       oldmsg = self.message;
+       self.message = "";
+       SUB_UseTargets();
+       self.message = oldmsg;
+}
+
+void func_breakable_damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
+{
+       if(self.state == 1)
+               return;
+       if(self.spawnflags & DOOR_NOSPLASH)
+               if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
+                       return;
+       if(self.team)
+               if(attacker.team == self.team)
+                       return;
+       self.health = self.health - damage;
+       if(self.sprite)
+       {
+               WaypointSprite_Ping(self.sprite);
+               WaypointSprite_UpdateHealth(self.sprite, self.health);
+       }
+       func_breakable_colormod();
+
+       if(self.health <= 0)
+       {
+               debrisforce = force;
+               W_PrepareExplosionByDamage(attacker, func_breakable_destroy);
+       }
+}
+
+void func_breakable_reset()
+{
+       self.team = self.team_saved;
+       func_breakable_look_restore();
+       if(self.spawnflags & 1)
+               func_breakable_behave_destroyed();
+       else
+               func_breakable_behave_restore();
+
+       CSQCMODEL_AUTOUPDATE();
+}
+
+// destructible walls that can be used to trigger target_objective_decrease
+void spawnfunc_func_breakable()
+{
+       float n, i;
+       if(!self.health)
+               self.health = 100;
+       self.max_health = self.health;
+
+       // yes, I know, MOVETYPE_NONE is not available here, not that one would want it here anyway
+       if(!self.debrismovetype) self.debrismovetype = MOVETYPE_BOUNCE;
+       if(!self.debrissolid) self.debrissolid = SOLID_NOT;
+       if(self.debrisvelocity == '0 0 0') self.debrisvelocity = '0 0 140';
+       if(self.debrisvelocityjitter == '0 0 0') self.debrisvelocityjitter = '70 70 70';
+       if(self.debrisavelocityjitter == '0 0 0') self.debrisavelocityjitter = '600 600 600';
+       if(!self.debristime) self.debristime = 3.5;
+       if(!self.debristimejitter) self.debristime = 2.5;
+
+       if(self.mdl != "")
+               self.cnt = particleeffectnum(self.mdl);
+       if(self.count == 0)
+               self.count = 1;
+
+       if(self.message == "")
+               self.message = "got too close to an explosion";
+       if(self.message2 == "")
+               self.message2 = "was pushed into an explosion by";
+       if(!self.dmg_radius)
+               self.dmg_radius = 150;
+       if(!self.dmg_force)
+               self.dmg_force = 200;
+
+       self.mdl = self.model;
+       SetBrushEntityModel();
+
+       self.use = func_breakable_restore;
+
+       // precache all the models
+       if (self.mdl_dead)
+               precache_model(self.mdl_dead);
+       n = tokenize_console(self.debris);
+       for(i = 0; i < n; ++i)
+               precache_model(argv(i));
+       if(self.noise)
+               precache_sound(self.noise);
+       if(self.noise1)
+               precache_sound(self.noise1);
+
+       self.team_saved = self.team;
+       self.dropped_origin = self.origin;
+
+       self.reset = func_breakable_reset;
+       func_breakable_reset();
+
+       self.init_for_player_needed = 1;
+       self.init_for_player = func_breakable_init_for_player;
+
+       CSQCMODEL_AUTOINIT();
+}
+
+// for use in maps with a "model" key set
+void spawnfunc_misc_breakablemodel() {
+       spawnfunc_func_breakable();
+}
+#endif
diff --git a/qcsrc/common/triggers/func/breakable.qh b/qcsrc/common/triggers/func/breakable.qh
new file mode 100644 (file)
index 0000000..b641d94
--- /dev/null
@@ -0,0 +1,8 @@
+#ifndef TRIGGERS_FUNC_BREAKABLE_H
+#define TRIGGERS_FUNC_BREAKABLE_H
+
+#ifdef SVQC
+void spawnfunc_func_breakable();
+#endif
+
+#endif
diff --git a/qcsrc/common/triggers/func/button.qc b/qcsrc/common/triggers/func/button.qc
new file mode 100644 (file)
index 0000000..0ed374a
--- /dev/null
@@ -0,0 +1,155 @@
+#ifdef SVQC
+// button and multiple button
+
+void() button_wait;
+void() button_return;
+
+void button_wait()
+{
+       self.state = STATE_TOP;
+       self.SUB_NEXTTHINK = self.SUB_LTIME + self.wait;
+       self.SUB_THINK = button_return;
+       activator = self.enemy;
+       SUB_UseTargets();
+       self.frame = 1;                 // use alternate textures
+}
+
+void button_done()
+{
+       self.state = STATE_BOTTOM;
+}
+
+void button_return()
+{
+       self.state = STATE_DOWN;
+       SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, button_done);
+       self.frame = 0;                 // use normal textures
+       if (self.health)
+               self.takedamage = DAMAGE_YES;   // can be shot again
+}
+
+
+void button_blocked()
+{
+       // do nothing, just don't come all the way back out
+}
+
+
+void button_fire()
+{
+       self.health = self.max_health;
+       self.takedamage = DAMAGE_NO;    // will be reset upon return
+
+       if (self.state == STATE_UP || self.state == STATE_TOP)
+               return;
+
+       if (self.noise != "")
+               sound (self, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
+
+       self.state = STATE_UP;
+       SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, button_wait);
+}
+
+void button_reset()
+{
+       self.health = self.max_health;
+       setorigin(self, self.pos1);
+       self.frame = 0;                 // use normal textures
+       self.state = STATE_BOTTOM;
+       if (self.health)
+               self.takedamage = DAMAGE_YES;   // can be shot again
+}
+
+void button_use()
+{
+       if(self.active != ACTIVE_ACTIVE)
+               return;
+
+       self.enemy = activator;
+       button_fire ();
+}
+
+void button_touch()
+{
+       if (!other)
+               return;
+       if (!other.iscreature)
+               return;
+       if(other.velocity * self.movedir < 0)
+               return;
+       self.enemy = other;
+       if (other.owner)
+               self.enemy = other.owner;
+       button_fire ();
+}
+
+void button_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
+{
+       if(self.spawnflags & DOOR_NOSPLASH)
+               if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
+                       return;
+       self.health = self.health - damage;
+       if (self.health <= 0)
+       {
+               self.enemy = damage_attacker;
+               button_fire ();
+       }
+}
+
+
+/*QUAKED spawnfunc_func_button (0 .5 .8) ?
+When a button is touched, it moves some distance in the direction of it's angle, triggers all of it's targets, waits some time, then returns to it's original position where it can be triggered again.
+
+"angle"                determines the opening direction
+"target"       all entities with a matching targetname will be used
+"speed"                override the default 40 speed
+"wait"         override the default 1 second wait (-1 = never return)
+"lip"          override the default 4 pixel lip remaining at end of move
+"health"       if set, the button must be killed instead of touched. If set to -1, the button will fire on ANY attack, even damageless ones like the InstaGib laser
+"sounds"
+0) steam metal
+1) wooden clunk
+2) metallic click
+3) in-out
+*/
+void spawnfunc_func_button()
+{
+       SetMovedir ();
+
+       if (!InitMovingBrushTrigger())
+               return;
+       self.effects |= EF_LOWPRECISION;
+
+       self.blocked = button_blocked;
+       self.use = button_use;
+
+//     if (self.health == 0) // all buttons are now shootable
+//             self.health = 10;
+       if (self.health)
+       {
+               self.max_health = self.health;
+               self.event_damage = button_damage;
+               self.takedamage = DAMAGE_YES;
+       }
+       else
+               self.touch = button_touch;
+
+       if (!self.speed)
+               self.speed = 40;
+       if (!self.wait)
+               self.wait = 1;
+       if (!self.lip)
+               self.lip = 4;
+
+    if(self.noise != "")
+        precache_sound(self.noise);
+
+       self.active = ACTIVE_ACTIVE;
+
+       self.pos1 = self.origin;
+       self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
+    self.flags |= FL_NOTARGET;
+
+       button_reset();
+}
+#endif
diff --git a/qcsrc/common/triggers/func/conveyor.qc b/qcsrc/common/triggers/func/conveyor.qc
new file mode 100644 (file)
index 0000000..6ed01b0
--- /dev/null
@@ -0,0 +1,197 @@
+void conveyor_think()
+{
+#ifdef CSQC
+       // TODO: check if this is what is causing the glitchiness when switching between them
+       float dt = time - self.move_time;
+       self.move_time = time;
+       if(dt <= 0) { return; }
+#endif
+       entity e;
+
+       // set myself as current conveyor where possible
+       for(e = world; (e = findentity(e, conveyor, self)); )
+               e.conveyor = world;
+
+       if(self.state)
+       {
+               for(e = findradius((self.absmin + self.absmax) * 0.5, vlen(self.absmax - self.absmin) * 0.5 + 1); e; e = e.chain)
+                       if(!e.conveyor.state)
+                               if(isPushable(e))
+                               {
+                                       vector emin = e.absmin;
+                                       vector emax = e.absmax;
+                                       if(self.solid == SOLID_BSP)
+                                       {
+                                               emin -= '1 1 1';
+                                               emax += '1 1 1';
+                                       }
+                                       if(boxesoverlap(emin, emax, self.absmin, self.absmax)) // quick
+                                               if(WarpZoneLib_BoxTouchesBrush(emin, emax, self, e)) // accurate
+                                                       e.conveyor = self;
+                               }
+
+               for(e = world; (e = findentity(e, conveyor, self)); )
+               {
+                       if(IS_CLIENT(e)) // doing it via velocity has quite some advantages
+                               continue; // done in SV_PlayerPhysics   continue;
+
+                       setorigin(e, e.origin + self.movedir * PHYS_INPUT_FRAMETIME);
+                       move_out_of_solid(e);
+#ifdef SVQC
+                       UpdateCSQCProjectile(e);
+#endif
+                       /*
+                       // stupid conveyor code
+                       tracebox(e.origin, e.mins, e.maxs, e.origin + self.movedir * sys_frametime, MOVE_NORMAL, e);
+                       if(trace_fraction > 0)
+                               setorigin(e, trace_endpos);
+                       */
+               }
+       }
+
+#ifdef SVQC
+       self.nextthink = time;
+#endif
+}
+
+#ifdef SVQC
+
+void conveyor_use()
+{
+       self.state = !self.state;
+
+       self.SendFlags |= 2;
+}
+
+void conveyor_reset()
+{
+       self.state = (self.spawnflags & 1);
+
+       self.SendFlags |= 2;
+}
+
+bool conveyor_send(entity to, int sf)
+{
+       WriteByte(MSG_ENTITY, ENT_CLIENT_CONVEYOR);
+       WriteByte(MSG_ENTITY, sf);
+
+       if(sf & 1)
+       {
+               WriteByte(MSG_ENTITY, self.warpzone_isboxy);
+               WriteCoord(MSG_ENTITY, self.origin_x);
+               WriteCoord(MSG_ENTITY, self.origin_y);
+               WriteCoord(MSG_ENTITY, self.origin_z);
+
+               WriteCoord(MSG_ENTITY, self.mins_x);
+               WriteCoord(MSG_ENTITY, self.mins_y);
+               WriteCoord(MSG_ENTITY, self.mins_z);
+               WriteCoord(MSG_ENTITY, self.maxs_x);
+               WriteCoord(MSG_ENTITY, self.maxs_y);
+               WriteCoord(MSG_ENTITY, self.maxs_z);
+
+               WriteCoord(MSG_ENTITY, self.movedir_x);
+               WriteCoord(MSG_ENTITY, self.movedir_y);
+               WriteCoord(MSG_ENTITY, self.movedir_z);
+
+               WriteByte(MSG_ENTITY, self.speed);
+               WriteByte(MSG_ENTITY, self.state);
+
+               WriteString(MSG_ENTITY, self.targetname);
+               WriteString(MSG_ENTITY, self.target);
+       }
+
+       if(sf & 2)
+               WriteByte(MSG_ENTITY, self.state);
+
+       return true;
+}
+
+void conveyor_init()
+{
+       if (!self.speed)
+               self.speed = 200;
+       self.movedir = self.movedir * self.speed;
+       self.think = conveyor_think;
+       self.nextthink = time;
+       IFTARGETED
+       {
+               self.use = conveyor_use;
+               self.reset = conveyor_reset;
+               conveyor_reset();
+       }
+       else
+               self.state = 1;
+
+       FixSize(self);
+
+       Net_LinkEntity(self, 0, false, conveyor_send);
+
+       self.SendFlags |= 1;
+}
+
+void spawnfunc_trigger_conveyor()
+{
+       SetMovedir();
+       EXACTTRIGGER_INIT;
+       conveyor_init();
+}
+
+void spawnfunc_func_conveyor()
+{
+       SetMovedir();
+       InitMovingBrushTrigger();
+       self.movetype = MOVETYPE_NONE;
+       conveyor_init();
+}
+
+#elif defined(CSQC)
+
+void conveyor_init()
+{
+       self.draw = conveyor_think;
+       self.drawmask = MASK_NORMAL;
+
+       self.movetype = MOVETYPE_NONE;
+       self.model = "";
+       self.solid = SOLID_TRIGGER;
+       self.move_origin = self.origin;
+       self.move_time = time;
+}
+
+void ent_conveyor()
+{
+       float sf = ReadByte();
+
+       if(sf & 1)
+       {
+               self.warpzone_isboxy = ReadByte();
+               self.origin_x = ReadCoord();
+               self.origin_y = ReadCoord();
+               self.origin_z = ReadCoord();
+               setorigin(self, self.origin);
+
+               self.mins_x = ReadCoord();
+               self.mins_y = ReadCoord();
+               self.mins_z = ReadCoord();
+               self.maxs_x = ReadCoord();
+               self.maxs_y = ReadCoord();
+               self.maxs_z = ReadCoord();
+               setsize(self, self.mins, self.maxs);
+
+               self.movedir_x = ReadCoord();
+               self.movedir_y = ReadCoord();
+               self.movedir_z = ReadCoord();
+
+               self.speed = ReadByte();
+               self.state = ReadByte();
+
+               self.targetname = strzone(ReadString());
+               self.target = strzone(ReadString());
+
+               conveyor_init();
+       }
+
+       if(sf & 2)
+               self.state = ReadByte();
+}
+#endif
diff --git a/qcsrc/common/triggers/func/conveyor.qh b/qcsrc/common/triggers/func/conveyor.qh
new file mode 100644 (file)
index 0000000..f272030
--- /dev/null
@@ -0,0 +1,3 @@
+#ifdef CSQC
+void ent_conveyor();
+#endif
diff --git a/qcsrc/common/triggers/func/door.qc b/qcsrc/common/triggers/func/door.qc
new file mode 100644 (file)
index 0000000..1a1c82b
--- /dev/null
@@ -0,0 +1,886 @@
+/*
+
+Doors are similar to buttons, but can spawn a fat trigger field around them
+to open without a touch, and they link together to form simultanious
+double/quad doors.
+
+Door.owner is the master door.  If there is only one door, it points to itself.
+If multiple doors, all will point to a single one.
+
+Door.enemy chains from the master door through all doors linked in the chain.
+
+*/
+
+
+/*
+=============================================================================
+
+THINK FUNCTIONS
+
+=============================================================================
+*/
+
+void() door_go_down;
+void() door_go_up;
+void() door_rotating_go_down;
+void() door_rotating_go_up;
+
+void door_blocked()
+{
+       if((self.spawnflags & 8) 
+#ifdef SVQC
+               && (other.takedamage != DAMAGE_NO)
+#elif defined(CSQC)
+               && !PHYS_DEAD(other)
+#endif
+       )
+       { // KIll Kill Kill!!
+#ifdef SVQC
+               Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
+#endif
+       }
+       else
+       {
+#ifdef SVQC
+               if((self.dmg) && (other.takedamage == DAMAGE_YES))    // Shall we bite?
+                       Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
+#endif
+
+                // don't change direction for dead or dying stuff
+               if(PHYS_DEAD(other)
+#ifdef SVQC
+                       && (other.takedamage == DAMAGE_NO)
+#endif
+               )
+               {
+                       if (self.wait >= 0)
+                       {
+                               if (self.state == STATE_DOWN)
+                       if (self.classname == "door")
+                       {
+                               door_go_up ();
+                       } else
+                       {
+                               door_rotating_go_up ();
+                       }
+                               else
+                       if (self.classname == "door")
+                       {
+                               door_go_down ();
+                       } else
+                       {
+                               door_rotating_go_down ();
+                       }
+                       }
+               }
+#ifdef SVQC
+               else
+               {
+                       //gib dying stuff just to make sure
+                       if((self.dmg) && (other.takedamage != DAMAGE_NO))    // Shall we bite?
+                               Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
+               }
+#endif
+       }
+}
+
+void door_hit_top()
+{
+       if (self.noise1 != "")
+               sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
+       self.state = STATE_TOP;
+       if (self.spawnflags & DOOR_TOGGLE)
+               return;         // don't come down automatically
+       if (self.classname == "door")
+       {
+               self.SUB_THINK = door_go_down;
+       } else
+       {
+               self.SUB_THINK = door_rotating_go_down;
+       }
+       self.SUB_NEXTTHINK = self.SUB_LTIME + self.wait;
+}
+
+void door_hit_bottom()
+{
+       if (self.noise1 != "")
+               sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
+       self.state = STATE_BOTTOM;
+}
+
+void door_go_down()
+{
+       if (self.noise2 != "")
+               sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
+       if (self.max_health)
+       {
+               self.takedamage = DAMAGE_YES;
+               self.health = self.max_health;
+       }
+
+       self.state = STATE_DOWN;
+       SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, door_hit_bottom);
+}
+
+void door_go_up()
+{
+       if (self.state == STATE_UP)
+               return;         // already going up
+
+       if (self.state == STATE_TOP)
+       {       // reset top wait time
+               self.SUB_NEXTTHINK = self.SUB_LTIME + self.wait;
+               return;
+       }
+
+       if (self.noise2 != "")
+               sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
+       self.state = STATE_UP;
+       SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, door_hit_top);
+
+       string oldmessage;
+       oldmessage = self.message;
+       self.message = "";
+       SUB_UseTargets();
+       self.message = oldmessage;
+}
+
+
+/*
+=============================================================================
+
+ACTIVATION FUNCTIONS
+
+=============================================================================
+*/
+
+float door_check_keys(void)
+{
+       local entity door;
+
+
+       if (self.owner)
+               door = self.owner;
+       else
+               door = self;
+
+       // no key needed
+       if (!door.itemkeys)
+               return true;
+
+       // this door require a key
+       // only a player can have a key
+       if (!IS_PLAYER(other))
+               return false;
+
+#ifdef SVQC
+       if (item_keys_usekey(door, other))
+       {
+               // some keys were used
+               if (other.key_door_messagetime <= time)
+               {
+
+                       play2(other, "misc/talk.wav");
+                       Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_LOCKED_ALSONEED, item_keys_keylist(door.itemkeys));
+                       other.key_door_messagetime = time + 2;
+               }
+       }
+       else
+       {
+               // no keys were used
+               if (other.key_door_messagetime <= time)
+               {
+                       play2(other, "misc/talk.wav");
+                       Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_LOCKED_NEED, item_keys_keylist(door.itemkeys));
+
+                       other.key_door_messagetime = time + 2;
+               }
+       }
+#endif
+
+       if (door.itemkeys)
+       {
+#ifdef SVQC
+               // door is now unlocked
+               play2(other, "misc/talk.wav");
+               Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_UNLOCKED);
+#endif
+               return true;
+       }
+       else
+               return false;
+}
+
+void door_fire()
+{
+       entity  oself;
+       entity  starte;
+
+       if (self.owner != self)
+               objerror ("door_fire: self.owner != self");
+
+       oself = self;
+
+       if (self.spawnflags & DOOR_TOGGLE)
+       {
+               if (self.state == STATE_UP || self.state == STATE_TOP)
+               {
+                       starte = self;
+                       do
+                       {
+                               if (self.classname == "door")
+                               {
+                                       door_go_down ();
+                               }
+                               else
+                               {
+                                       door_rotating_go_down ();
+                               }
+                               self = self.enemy;
+                       } while ( (self != starte) && (self != world) );
+                       self = oself;
+                       return;
+               }
+       }
+
+// trigger all paired doors
+       starte = self;
+       do
+       {
+               if (self.classname == "door")
+               {
+                       door_go_up ();
+               } else
+               {
+                       // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
+                       if ((self.spawnflags & 2) && other.trigger_reverse!=0 && self.lip!=666 && self.state == STATE_BOTTOM)
+                       {
+                               self.lip = 666; // self.lip is used to remember reverse opening direction for door_rotating
+                               self.pos2 = '0 0 0' - self.pos2;
+                       }
+                       // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
+                       if (!((self.spawnflags & 2) &&  (self.spawnflags & 8) && self.state == STATE_DOWN
+                               && (((self.lip==666) && (other.trigger_reverse==0)) || ((self.lip!=666) && (other.trigger_reverse!=0)))))
+                       {
+                               door_rotating_go_up ();
+                       }
+               }
+               self = self.enemy;
+       } while ( (self != starte) && (self != world) );
+       self = oself;
+}
+
+void door_use()
+{
+       entity oself;
+
+       //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
+
+       if (self.owner)
+       {
+               oself = self;
+               self = self.owner;
+               door_fire ();
+               self = oself;
+       }
+}
+
+void door_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
+{
+       entity oself;
+       if(self.spawnflags & DOOR_NOSPLASH)
+               if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
+                       return;
+       self.health = self.health - damage;
+
+       if (self.itemkeys)
+       {
+               // don't allow opening doors through damage if keys are required
+               return;
+       }
+
+       if (self.health <= 0)
+       {
+               oself = self;
+               self = self.owner;
+               self.health = self.max_health;
+               self.takedamage = DAMAGE_NO;    // wil be reset upon return
+               door_use ();
+               self = oself;
+       }
+}
+
+
+/*
+================
+door_touch
+
+Prints messages
+================
+*/
+
+void door_touch()
+{
+       if (!IS_PLAYER(other))
+               return;
+       if (self.owner.attack_finished_single > time)
+               return;
+
+       self.owner.attack_finished_single = time + 2;
+
+#ifdef SVQC
+       if (!(self.owner.dmg) && (self.owner.message != ""))
+       {
+               if (IS_CLIENT(other))
+                       centerprint(other, self.owner.message);
+               play2(other, "misc/talk.wav");
+       }
+#endif
+}
+
+void door_generic_plat_blocked()
+{
+
+       if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
+#ifdef SVQC
+               Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
+#endif
+       }
+       else
+       {
+
+#ifdef SVQC
+               if((self.dmg) && (other.takedamage == DAMAGE_YES))    // Shall we bite?
+                       Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
+#endif
+
+                //Dont chamge direction for dead or dying stuff
+               if(PHYS_DEAD(other) && (other.takedamage == DAMAGE_NO))
+               {
+                       if (self.wait >= 0)
+                       {
+                               if (self.state == STATE_DOWN)
+                                       door_rotating_go_up ();
+                               else
+                                       door_rotating_go_down ();
+                       }
+               }
+#ifdef SVQC
+               else
+               {
+                       //gib dying stuff just to make sure
+                       if((self.dmg) && (other.takedamage != DAMAGE_NO))    // Shall we bite?
+                               Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
+               }
+#endif
+       }
+}
+
+void door_rotating_hit_top()
+{
+       if (self.noise1 != "")
+               sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
+       self.state = STATE_TOP;
+       if (self.spawnflags & DOOR_TOGGLE)
+               return;         // don't come down automatically
+       self.SUB_THINK = door_rotating_go_down;
+       self.SUB_NEXTTHINK = self.SUB_LTIME + self.wait;
+}
+
+void door_rotating_hit_bottom()
+{
+       if (self.noise1 != "")
+               sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
+       if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
+       {
+               self.pos2 = '0 0 0' - self.pos2;
+               self.lip = 0;
+       }
+       self.state = STATE_BOTTOM;
+}
+
+void door_rotating_go_down()
+{
+       if (self.noise2 != "")
+               sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
+       if (self.max_health)
+       {
+               self.takedamage = DAMAGE_YES;
+               self.health = self.max_health;
+       }
+
+       self.state = STATE_DOWN;
+       SUB_CalcAngleMove (self.pos1, TSPEED_LINEAR, self.speed, door_rotating_hit_bottom);
+}
+
+void door_rotating_go_up()
+{
+       if (self.state == STATE_UP)
+               return;         // already going up
+
+       if (self.state == STATE_TOP)
+       {       // reset top wait time
+               self.SUB_NEXTTHINK = self.SUB_LTIME + self.wait;
+               return;
+       }
+       if (self.noise2 != "")
+               sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
+       self.state = STATE_UP;
+       SUB_CalcAngleMove (self.pos2, TSPEED_LINEAR, self.speed, door_rotating_hit_top);
+
+       string oldmessage;
+       oldmessage = self.message;
+       self.message = "";
+       SUB_UseTargets();
+       self.message = oldmessage;
+}
+
+
+/*
+=========================================
+door trigger
+
+Spawned if a door lacks a real activator
+=========================================
+*/
+
+void door_trigger_touch()
+{
+       if (other.health < 1)
+#ifdef SVQC
+               if (!((other.iscreature || (other.flags & FL_PROJECTILE)) && !PHYS_DEAD(other)))
+#elif defined(CSQC)
+               if(!((IS_CLIENT(other) || other.classname == "csqcprojectile") && !PHYS_DEAD(other)))
+#endif
+                       return;
+
+       if (time < self.attack_finished_single)
+               return;
+
+       // check if door is locked
+       if (!door_check_keys())
+               return;
+
+       self.attack_finished_single = time + 1;
+
+       activator = other;
+
+       self = self.owner;
+       door_use ();
+}
+
+void spawn_field(vector fmins, vector fmaxs)
+{
+       entity  trigger;
+       vector  t1 = fmins, t2 = fmaxs;
+
+       trigger = spawn();
+       trigger.classname = "doortriggerfield";
+       trigger.movetype = MOVETYPE_NONE;
+       trigger.solid = SOLID_TRIGGER;
+       trigger.owner = self;
+#ifdef SVQC
+       trigger.touch = door_trigger_touch;
+#elif defined(CSQC)
+       trigger.trigger_touch = door_trigger_touch;
+       trigger.draw = trigger_draw_generic;
+       trigger.drawmask = MASK_NORMAL;
+#endif
+
+       setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
+}
+
+
+/*
+=============
+LinkDoors
+
+
+=============
+*/
+
+entity LinkDoors_nextent(entity cur, entity near, entity pass)
+{
+       while((cur = find(cur, classname, self.classname)) && ((cur.spawnflags & 4) || cur.enemy))
+       {
+       }
+       return cur;
+}
+
+bool LinkDoors_isconnected(entity e1, entity e2, entity pass)
+{
+       float DELTA = 4;
+       if((e1.absmin_x > e2.absmax_x + DELTA)
+       || (e1.absmin_y > e2.absmax_y + DELTA)
+       || (e1.absmin_z > e2.absmax_z + DELTA)
+       || (e2.absmin_x > e1.absmax_x + DELTA)
+       || (e2.absmin_y > e1.absmax_y + DELTA)
+       || (e2.absmin_z > e1.absmax_z + DELTA)
+       ) { return false; }
+       return true;
+}
+
+#ifdef SVQC
+void door_link();
+#endif
+void LinkDoors()
+{
+       entity  t;
+       vector  cmins, cmaxs;
+
+#ifdef SVQC
+       door_link();
+#endif
+
+       if (self.enemy)
+               return;         // already linked by another door
+       if (self.spawnflags & 4)
+       {
+               self.owner = self.enemy = self;
+
+               if (self.health)
+                       return;
+               IFTARGETED
+                       return;
+               if (self.items)
+                       return;
+
+               spawn_field(self.absmin, self.absmax);
+
+               return;         // don't want to link this door
+       }
+
+       FindConnectedComponent(self, enemy, LinkDoors_nextent, LinkDoors_isconnected, world);
+
+       // set owner, and make a loop of the chain
+       dprint("LinkDoors: linking doors:");
+       for(t = self; ; t = t.enemy)
+       {
+               dprint(" ", etos(t));
+               t.owner = self;
+               if(t.enemy == world)
+               {
+                       t.enemy = self;
+                       break;
+               }
+       }
+       dprint("\n");
+
+       // collect health, targetname, message, size
+       cmins = self.absmin;
+       cmaxs = self.absmax;
+       for(t = self; ; t = t.enemy)
+       {
+               if(t.health && !self.health)
+                       self.health = t.health;
+               if((t.targetname != "") && (self.targetname == ""))
+                       self.targetname = t.targetname;
+               if((t.message != "") && (self.message == ""))
+                       self.message = t.message;
+               if (t.absmin_x < cmins_x)
+                       cmins_x = t.absmin_x;
+               if (t.absmin_y < cmins_y)
+                       cmins_y = t.absmin_y;
+               if (t.absmin_z < cmins_z)
+                       cmins_z = t.absmin_z;
+               if (t.absmax_x > cmaxs_x)
+                       cmaxs_x = t.absmax_x;
+               if (t.absmax_y > cmaxs_y)
+                       cmaxs_y = t.absmax_y;
+               if (t.absmax_z > cmaxs_z)
+                       cmaxs_z = t.absmax_z;
+               if(t.enemy == self)
+                       break;
+       }
+
+       // distribute health, targetname, message
+       for(t = self; t; t = t.enemy)
+       {
+               t.health = self.health;
+               t.targetname = self.targetname;
+               t.message = self.message;
+               if(t.enemy == self)
+                       break;
+       }
+
+       // shootable, or triggered doors just needed the owner/enemy links,
+       // they don't spawn a field
+
+       if (self.health)
+               return;
+       IFTARGETED
+               return;
+       if (self.items)
+               return;
+
+       spawn_field(cmins, cmaxs);
+}
+
+#ifdef SVQC
+/*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
+if two doors touch, they are assumed to be connected and operate as a unit.
+
+TOGGLE causes the door to wait in both the start and end states for a trigger event.
+
+START_OPEN causes the door to move to its destination when spawned, and operate in reverse.  It is used to temporarily or permanently close off an area when triggered (not useful for touch or takedamage doors).
+
+GOLD_KEY causes the door to open only if the activator holds a gold key.
+
+SILVER_KEY causes the door to open only if the activator holds a silver key.
+
+"message"      is printed when the door is touched if it is a trigger door and it hasn't been fired yet
+"angle"                determines the opening direction
+"targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
+"health"       if set, door must be shot open
+"speed"                movement speed (100 default)
+"wait"         wait before returning (3 default, -1 = never return)
+"lip"          lip remaining at end of move (8 default)
+"dmg"          damage to inflict when blocked (2 default)
+"sounds"
+0)     no sound
+1)     stone
+2)     base
+3)     stone chain
+4)     screechy metal
+FIXME: only one sound set available at the time being
+
+*/
+
+float door_send(entity to, float sf)
+{
+       WriteByte(MSG_ENTITY, ENT_CLIENT_DOOR);
+       WriteByte(MSG_ENTITY, sf);
+
+       if(sf & SF_TRIGGER_INIT)
+       {
+               WriteString(MSG_ENTITY, self.classname);
+               WriteByte(MSG_ENTITY, self.spawnflags);
+
+               WriteString(MSG_ENTITY, self.model);
+
+               trigger_common_write(true);
+
+               WriteCoord(MSG_ENTITY, self.pos1_x);
+               WriteCoord(MSG_ENTITY, self.pos1_y);
+               WriteCoord(MSG_ENTITY, self.pos1_z);
+               WriteCoord(MSG_ENTITY, self.pos2_x);
+               WriteCoord(MSG_ENTITY, self.pos2_y);
+               WriteCoord(MSG_ENTITY, self.pos2_z);
+
+               WriteCoord(MSG_ENTITY, self.size_x);
+               WriteCoord(MSG_ENTITY, self.size_y);
+               WriteCoord(MSG_ENTITY, self.size_z);
+
+               WriteShort(MSG_ENTITY, self.wait);
+               WriteShort(MSG_ENTITY, self.speed);
+               WriteByte(MSG_ENTITY, self.lip);
+               WriteByte(MSG_ENTITY, self.state);
+               WriteCoord(MSG_ENTITY, self.SUB_LTIME);
+       }
+
+       if(sf & SF_TRIGGER_RESET)
+       {
+               // client makes use of this, we do not
+       }
+
+       if(sf & SF_TRIGGER_UPDATE)
+       {
+               WriteCoord(MSG_ENTITY, self.origin_x);
+               WriteCoord(MSG_ENTITY, self.origin_y);
+               WriteCoord(MSG_ENTITY, self.origin_z);
+
+               WriteCoord(MSG_ENTITY, self.pos1_x);
+               WriteCoord(MSG_ENTITY, self.pos1_y);
+               WriteCoord(MSG_ENTITY, self.pos1_z);
+               WriteCoord(MSG_ENTITY, self.pos2_x);
+               WriteCoord(MSG_ENTITY, self.pos2_y);
+               WriteCoord(MSG_ENTITY, self.pos2_z);
+       }
+
+       return true;
+}
+
+void door_link()
+{
+       // set size now, as everything is loaded
+       FixSize(self);
+       Net_LinkEntity(self, false, 0, door_send);
+}
+#endif
+
+void door_init_startopen()
+{
+       SUB_SETORIGIN(self, self.pos2);
+       self.pos2 = self.pos1;
+       self.pos1 = self.origin;
+
+#ifdef SVQC
+       self.SendFlags |= SF_TRIGGER_UPDATE;
+#endif
+}
+
+void door_reset()
+{
+       SUB_SETORIGIN(self, self.pos1);
+       self.SUB_VELOCITY = '0 0 0';
+       self.state = STATE_BOTTOM;
+       self.SUB_THINK = func_null;
+       self.SUB_NEXTTHINK = 0;
+
+#ifdef SVQC
+       self.SendFlags |= SF_TRIGGER_RESET;
+#endif
+}
+
+#ifdef SVQC
+
+// spawnflags require key (for now only func_door)
+void spawnfunc_func_door()
+{
+       // Quake 1 keys compatibility
+       if (self.spawnflags & SPAWNFLAGS_GOLD_KEY)
+               self.itemkeys |= ITEM_KEY_BIT(0);
+       if (self.spawnflags & SPAWNFLAGS_SILVER_KEY)
+               self.itemkeys |= ITEM_KEY_BIT(1);
+
+       SetMovedir ();
+
+       self.max_health = self.health;
+       if (!InitMovingBrushTrigger())
+               return;
+       self.effects |= EF_LOWPRECISION;
+       self.classname = "door";
+
+       self.blocked = door_blocked;
+       self.use = door_use;
+
+       if(self.dmg && (self.message == ""))
+               self.message = "was squished";
+       if(self.dmg && (self.message2 == ""))
+               self.message2 = "was squished by";
+
+       if (self.sounds > 0)
+       {
+               precache_sound ("plats/medplat1.wav");
+               precache_sound ("plats/medplat2.wav");
+               self.noise2 = "plats/medplat1.wav";
+               self.noise1 = "plats/medplat2.wav";
+       }
+
+       if (!self.speed)
+               self.speed = 100;
+       if (!self.wait)
+               self.wait = 3;
+       if (!self.lip)
+               self.lip = 8;
+
+       self.pos1 = self.SUB_ORIGIN;
+       self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
+
+// DOOR_START_OPEN is to allow an entity to be lighted in the closed position
+// but spawn in the open position
+       if (self.spawnflags & DOOR_START_OPEN)
+               InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
+
+       self.state = STATE_BOTTOM;
+
+       if (self.health)
+       {
+               self.takedamage = DAMAGE_YES;
+               self.event_damage = door_damage;
+       }
+
+       if (self.items)
+               self.wait = -1;
+
+       self.touch = door_touch;
+
+// LinkDoors can't be done until all of the doors have been spawned, so
+// the sizes can be detected properly.
+       InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
+
+       self.reset = door_reset;
+}
+
+#elif defined(CSQC)
+
+void door_draw()
+{
+       Movetype_Physics_NoMatchServer();
+
+       trigger_draw_generic();
+}
+
+void ent_door()
+{
+       float sf = ReadByte();
+
+       if(sf & SF_TRIGGER_INIT)
+       {
+               self.classname = strzone(ReadString());
+               self.spawnflags = ReadByte();
+
+               self.mdl = strzone(ReadString());
+               setmodel(self, self.mdl);
+
+               trigger_common_read(true);
+
+               self.pos1_x = ReadCoord();
+               self.pos1_y = ReadCoord();
+               self.pos1_z = ReadCoord();
+               self.pos2_x = ReadCoord();
+               self.pos2_y = ReadCoord();
+               self.pos2_z = ReadCoord();
+
+               self.size_x = ReadCoord();
+               self.size_y = ReadCoord();
+               self.size_z = ReadCoord();
+
+               self.wait = ReadShort();
+               self.speed = ReadShort();
+               self.lip = ReadByte();
+               self.state = ReadByte();
+               self.SUB_LTIME = ReadCoord();
+
+               self.solid = SOLID_BSP;
+               self.movetype = MOVETYPE_PUSH;
+               self.trigger_touch = door_touch;
+               self.draw = door_draw;
+               self.drawmask = MASK_NORMAL;
+               self.use = door_use;
+
+               LinkDoors();
+
+               if(self.spawnflags & DOOR_START_OPEN)
+                       door_init_startopen();
+
+               self.move_time = time;
+               self.move_origin = self.origin;
+               self.move_movetype = MOVETYPE_PUSH;
+               self.move_angles = self.angles;
+               self.move_blocked = door_blocked;
+       }
+
+       if(sf & SF_TRIGGER_RESET)
+       {
+               door_reset();
+       }
+
+       if(sf & SF_TRIGGER_UPDATE)
+       {
+               self.origin_x = ReadCoord();
+               self.origin_y = ReadCoord();
+               self.origin_z = ReadCoord();
+               setorigin(self, self.origin);
+               self.move_origin = self.origin;
+
+               self.pos1_x = ReadCoord();
+               self.pos1_y = ReadCoord();
+               self.pos1_z = ReadCoord();
+               self.pos2_x = ReadCoord();
+               self.pos2_y = ReadCoord();
+               self.pos2_z = ReadCoord();
+       }
+}
+
+#endif
diff --git a/qcsrc/common/triggers/func/door.qh b/qcsrc/common/triggers/func/door.qh
new file mode 100644 (file)
index 0000000..b58091b
--- /dev/null
@@ -0,0 +1,17 @@
+// door constants
+const float DOOR_START_OPEN = 1;
+const float DOOR_DONT_LINK = 4;
+const float DOOR_TOGGLE = 32;
+
+const float DOOR_NOSPLASH = 256; // generic anti-splashdamage spawnflag
+
+const float SPAWNFLAGS_GOLD_KEY = 8;
+const float SPAWNFLAGS_SILVER_KEY = 16;
+
+#ifdef CSQC
+// stuff for preload
+void ent_door();
+
+// abused
+.float attack_finished_single;
+#endif
diff --git a/qcsrc/common/triggers/func/door_rotating.qc b/qcsrc/common/triggers/func/door_rotating.qc
new file mode 100644 (file)
index 0000000..bdf05a0
--- /dev/null
@@ -0,0 +1,126 @@
+#ifdef SVQC
+/*QUAKED spawnfunc_func_door_rotating (0 .5 .8) ? START_OPEN BIDIR DOOR_DONT_LINK BIDIR_IN_DOWN x TOGGLE X_AXIS Y_AXIS
+if two doors touch, they are assumed to be connected and operate as a unit.
+
+TOGGLE causes the door to wait in both the start and end states for a trigger event.
+
+BIDIR makes the door work bidirectional, so that the opening direction is always away from the requestor.
+The usage of bidirectional doors requires two manually instantiated triggers (trigger_multiple), the one to open it in the other direction
+must have set trigger_reverse to 1.
+BIDIR_IN_DOWN will the door prevent from reopening while closing if it is triggered from the other side.
+
+START_OPEN causes the door to move to its destination when spawned, and operate in reverse.  It is used to temporarily or permanently close off an area when triggered (not usefull for touch or takedamage doors).
+
+"message"      is printed when the door is touched if it is a trigger door and it hasn't been fired yet
+"angle"                determines the destination angle for opening. negative values reverse the direction.
+"targetname"    if set, no touch field will be spawned and a remote button or trigger field activates the door.
+"health"       if set, door must be shot open
+"speed"                movement speed (100 default)
+"wait"         wait before returning (3 default, -1 = never return)
+"dmg"          damage to inflict when blocked (2 default)
+"sounds"
+0)     no sound
+1)     stone
+2)     base
+3)     stone chain
+4)     screechy metal
+FIXME: only one sound set available at the time being
+*/
+
+void door_rotating_reset()
+{
+       self.angles = self.pos1;
+       self.avelocity = '0 0 0';
+       self.state = STATE_BOTTOM;
+       self.think = func_null;
+       self.nextthink = 0;
+}
+
+void door_rotating_init_startopen()
+{
+       self.angles = self.movedir;
+       self.pos2 = '0 0 0';
+       self.pos1 = self.movedir;
+}
+
+
+void spawnfunc_func_door_rotating()
+{
+
+       //if (!self.deathtype) // map makers can override this
+       //      self.deathtype = " got in the way";
+
+       // I abuse "movedir" for denoting the axis for now
+       if (self.spawnflags & 64) // X (untested)
+               self.movedir = '0 0 1';
+       else if (self.spawnflags & 128) // Y (untested)
+               self.movedir = '1 0 0';
+       else // Z
+               self.movedir = '0 1 0';
+
+       if (self.angles_y==0) self.angles_y = 90;
+
+       self.movedir = self.movedir * self.angles_y;
+       self.angles = '0 0 0';
+
+       self.max_health = self.health;
+       self.avelocity = self.movedir;
+       if (!InitMovingBrushTrigger())
+               return;
+       self.velocity = '0 0 0';
+       //self.effects |= EF_LOWPRECISION;
+       self.classname = "door_rotating";
+
+       self.blocked = door_blocked;
+       self.use = door_use;
+
+    if(self.spawnflags & 8)
+        self.dmg = 10000;
+
+    if(self.dmg && (self.message == ""))
+               self.message = "was squished";
+    if(self.dmg && (self.message2 == ""))
+               self.message2 = "was squished by";
+
+    if (self.sounds > 0)
+       {
+               precache_sound ("plats/medplat1.wav");
+               precache_sound ("plats/medplat2.wav");
+               self.noise2 = "plats/medplat1.wav";
+               self.noise1 = "plats/medplat2.wav";
+       }
+
+       if (!self.speed)
+               self.speed = 50;
+       if (!self.wait)
+               self.wait = 1;
+       self.lip = 0; // self.lip is used to remember reverse opening direction for door_rotating
+
+       self.pos1 = '0 0 0';
+       self.pos2 = self.movedir;
+
+// DOOR_START_OPEN is to allow an entity to be lighted in the closed position
+// but spawn in the open position
+       if (self.spawnflags & DOOR_START_OPEN)
+               InitializeEntity(self, door_rotating_init_startopen, INITPRIO_SETLOCATION);
+
+       self.state = STATE_BOTTOM;
+
+       if (self.health)
+       {
+               self.takedamage = DAMAGE_YES;
+               self.event_damage = door_damage;
+       }
+
+       if (self.items)
+               self.wait = -1;
+
+       self.touch = door_touch;
+
+// LinkDoors can't be done until all of the doors have been spawned, so
+// the sizes can be detected properly.
+       InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
+
+       self.reset = door_rotating_reset;
+}
+#endif
diff --git a/qcsrc/common/triggers/func/door_secret.qc b/qcsrc/common/triggers/func/door_secret.qc
new file mode 100644 (file)
index 0000000..b188f83
--- /dev/null
@@ -0,0 +1,236 @@
+#ifdef SVQC
+void() fd_secret_move1;
+void() fd_secret_move2;
+void() fd_secret_move3;
+void() fd_secret_move4;
+void() fd_secret_move5;
+void() fd_secret_move6;
+void() fd_secret_done;
+
+const float SECRET_OPEN_ONCE = 1;              // stays open
+const float SECRET_1ST_LEFT = 2;               // 1st move is left of arrow
+const float SECRET_1ST_DOWN = 4;               // 1st move is down from arrow
+const float SECRET_NO_SHOOT = 8;               // only opened by trigger
+const float SECRET_YES_SHOOT = 16;     // shootable even if targeted
+
+void fd_secret_use()
+{
+       float temp;
+       string message_save;
+
+       self.health = 10000;
+       self.bot_attack = true;
+
+       // exit if still moving around...
+       if (self.origin != self.oldorigin)
+               return;
+
+       message_save = self.message;
+       self.message = ""; // no more message
+       SUB_UseTargets();                               // fire all targets / killtargets
+       self.message = message_save;
+
+       self.velocity = '0 0 0';
+
+       // Make a sound, wait a little...
+
+       if (self.noise1 != "")
+               sound(self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
+       self.SUB_NEXTTHINK = self.SUB_LTIME + 0.1;
+
+       temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
+       makevectors(self.mangle);
+
+       if (!self.t_width)
+       {
+               if (self.spawnflags & SECRET_1ST_DOWN)
+                       self.t_width = fabs(v_up * self.size);
+               else
+                       self.t_width = fabs(v_right * self.size);
+       }
+
+       if (!self.t_length)
+               self.t_length = fabs(v_forward * self.size);
+
+       if (self.spawnflags & SECRET_1ST_DOWN)
+               self.dest1 = self.origin - v_up * self.t_width;
+       else
+               self.dest1 = self.origin + v_right * (self.t_width * temp);
+
+       self.dest2 = self.dest1 + v_forward * self.t_length;
+       SUB_CalcMove(self.dest1, TSPEED_LINEAR, self.speed, fd_secret_move1);
+       if (self.noise2 != "")
+               sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
+}
+
+void fd_secret_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
+{
+       fd_secret_use();
+}
+
+// Wait after first movement...
+void fd_secret_move1()
+{
+       self.SUB_NEXTTHINK = self.SUB_LTIME + 1.0;
+       self.think = fd_secret_move2;
+       if (self.noise3 != "")
+               sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
+}
+
+// Start moving sideways w/sound...
+void fd_secret_move2()
+{
+       if (self.noise2 != "")
+               sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
+       SUB_CalcMove(self.dest2, TSPEED_LINEAR, self.speed, fd_secret_move3);
+}
+
+// Wait here until time to go back...
+void fd_secret_move3()
+{
+       if (self.noise3 != "")
+               sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
+       if (!(self.spawnflags & SECRET_OPEN_ONCE))
+       {
+               self.SUB_NEXTTHINK = self.SUB_LTIME + self.wait;
+               self.think = fd_secret_move4;
+       }
+}
+
+// Move backward...
+void fd_secret_move4()
+{
+       if (self.noise2 != "")
+               sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
+       SUB_CalcMove(self.dest1, TSPEED_LINEAR, self.speed, fd_secret_move5);
+}
+
+// Wait 1 second...
+void fd_secret_move5()
+{
+       self.SUB_NEXTTHINK = self.SUB_LTIME + 1.0;
+       self.think = fd_secret_move6;
+       if (self.noise3 != "")
+               sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
+}
+
+void fd_secret_move6()
+{
+       if (self.noise2 != "")
+               sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
+       SUB_CalcMove(self.oldorigin, TSPEED_LINEAR, self.speed, fd_secret_done);
+}
+
+void fd_secret_done()
+{
+       if (self.spawnflags&SECRET_YES_SHOOT)
+       {
+               self.health = 10000;
+               self.takedamage = DAMAGE_YES;
+               //self.th_pain = fd_secret_use;
+       }
+       if (self.noise3 != "")
+               sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
+}
+
+void secret_blocked()
+{
+       if (time < self.attack_finished_single)
+               return;
+       self.attack_finished_single = time + 0.5;
+       //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
+}
+
+/*
+==============
+secret_touch
+
+Prints messages
+================
+*/
+void secret_touch()
+{
+       if (!other.iscreature)
+               return;
+       if (self.attack_finished_single > time)
+               return;
+
+       self.attack_finished_single = time + 2;
+
+       if (self.message)
+       {
+               if (IS_CLIENT(other))
+                       centerprint(other, self.message);
+               play2(other, "misc/talk.wav");
+       }
+}
+
+void secret_reset()
+{
+       if (self.spawnflags&SECRET_YES_SHOOT)
+       {
+               self.health = 10000;
+               self.takedamage = DAMAGE_YES;
+       }
+       setorigin(self, self.oldorigin);
+       self.think = func_null;
+       self.SUB_NEXTTHINK = 0;
+}
+
+/*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
+Basic secret door. Slides back, then to the side. Angle determines direction.
+wait  = # of seconds before coming back
+1st_left = 1st move is left of arrow
+1st_down = 1st move is down from arrow
+always_shoot = even if targeted, keep shootable
+t_width = override WIDTH to move back (or height if going down)
+t_length = override LENGTH to move sideways
+"dmg"          damage to inflict when blocked (2 default)
+
+If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
+"sounds"
+1) medieval
+2) metal
+3) base
+*/
+
+void spawnfunc_func_door_secret()
+{
+       /*if (!self.deathtype) // map makers can override this
+               self.deathtype = " got in the way";*/
+
+       if (!self.dmg)
+               self.dmg = 2;
+
+       // Magic formula...
+       self.mangle = self.angles;
+       self.angles = '0 0 0';
+       self.classname = "door";
+       if (!InitMovingBrushTrigger())
+               return;
+       self.effects |= EF_LOWPRECISION;
+
+       self.touch = secret_touch;
+       self.blocked = secret_blocked;
+       self.speed = 50;
+       self.use = fd_secret_use;
+       IFTARGETED
+       {
+       }
+       else
+               self.spawnflags |= SECRET_YES_SHOOT;
+
+       if(self.spawnflags&SECRET_YES_SHOOT)
+       {
+               self.health = 10000;
+               self.takedamage = DAMAGE_YES;
+               self.event_damage = fd_secret_damage;
+       }
+       self.oldorigin = self.origin;
+       if (!self.wait)
+               self.wait = 5;          // 5 seconds before closing
+
+       self.reset = secret_reset;
+       secret_reset();
+}
+#endif
diff --git a/qcsrc/common/triggers/func/fourier.qc b/qcsrc/common/triggers/func/fourier.qc
new file mode 100644 (file)
index 0000000..19dceef
--- /dev/null
@@ -0,0 +1,89 @@
+#ifdef SVQC
+/*QUAKED spawnfunc_func_fourier (0 .5 .8) ?
+Brush model that moves in a pattern of added up sine waves, can be used e.g. for circular motions.
+netname: list of <frequencymultiplier> <phase> <x> <y> <z> quadruples, separated by spaces; note that phase 0 represents a sine wave, and phase 0.25 a cosine wave (by default, it uses 1 0 0 0 1, to match func_bobbing's defaults
+speed: how long one cycle of frequency multiplier 1 in seconds (default 4)
+height: amplitude modifier (default 32)
+phase: cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
+noise: path/name of looping .wav file to play.
+dmg: Do this mutch dmg every .dmgtime intervall when blocked
+dmgtime: See above.
+*/
+
+void func_fourier_controller_think()
+{
+       vector v;
+       float n, i, t;
+
+       self.nextthink = time + 0.1;
+       if(self.owner.active != ACTIVE_ACTIVE)
+       {
+               self.owner.velocity = '0 0 0';
+               return;
+       }
+
+
+       n = floor((tokenize_console(self.owner.netname)) / 5);
+       t = self.nextthink * self.owner.cnt + self.owner.phase * 360;
+
+       v = self.owner.destvec;
+
+       for(i = 0; i < n; ++i)
+       {
+               makevectors((t * stof(argv(i*5)) + stof(argv(i*5+1)) * 360) * '0 1 0');
+               v = v + ('1 0 0' * stof(argv(i*5+2)) + '0 1 0' * stof(argv(i*5+3)) + '0 0 1' * stof(argv(i*5+4))) * self.owner.height * v_forward_y;
+       }
+
+       if(self.owner.classname == "func_fourier") // don't brake stuff if the func_fourier was killtarget'ed
+               // * 10 so it will arrive in 0.1 sec
+               self.owner.velocity = (v - self.owner.origin) * 10;
+}
+
+void spawnfunc_func_fourier()
+{
+       entity controller;
+       if (self.noise != "")
+       {
+               precache_sound(self.noise);
+               soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
+       }
+
+       if (!self.speed)
+               self.speed = 4;
+       if (!self.height)
+               self.height = 32;
+       self.destvec = self.origin;
+       self.cnt = 360 / self.speed;
+
+       self.blocked = generic_plat_blocked;
+       if(self.dmg && (self.message == ""))
+               self.message = " was squished";
+    if(self.dmg && (self.message2 == ""))
+               self.message2 = "was squished by";
+       if(self.dmg && (!self.dmgtime))
+               self.dmgtime = 0.25;
+       self.dmgtime2 = time;
+
+       if(self.netname == "")
+               self.netname = "1 0 0 0 1";
+
+       if (!InitMovingBrushTrigger())
+               return;
+
+       self.active = ACTIVE_ACTIVE;
+
+       // wait for targets to spawn
+       controller = spawn();
+       controller.classname = "func_fourier_controller";
+       controller.owner = self;
+       controller.nextthink = time + 1;
+       controller.think = func_fourier_controller_think;
+       self.SUB_NEXTTHINK = self.SUB_LTIME + 999999999;
+       self.SUB_THINK = SUB_NullThink; // for PushMove
+
+       // Savage: Reduce bandwith, critical on e.g. nexdm02
+       self.effects |= EF_LOWPRECISION;
+
+       // TODO make a reset function for this one
+}
+#endif
diff --git a/qcsrc/common/triggers/func/include.qc b/qcsrc/common/triggers/func/include.qc
new file mode 100644 (file)
index 0000000..290c2e9
--- /dev/null
@@ -0,0 +1,19 @@
+#include "include.qh"
+
+#include "bobbing.qc"
+#include "breakable.qc"
+#include "button.qc"
+#include "conveyor.qc"
+#include "door.qc"
+#include "door_rotating.qc"
+#include "door_secret.qc"
+#include "fourier.qc"
+#include "ladder.qc"
+#include "pendulum.qc"
+#include "plat.qc"
+#include "pointparticles.qc"
+#include "rainsnow.qc"
+#include "rotating.qc"
+#include "stardust.qc"
+#include "train.qc"
+#include "vectormamamam.qc"
diff --git a/qcsrc/common/triggers/func/include.qh b/qcsrc/common/triggers/func/include.qh
new file mode 100644 (file)
index 0000000..624c0b5
--- /dev/null
@@ -0,0 +1,12 @@
+#ifndef TRIGGERS_FUNC_INCLUDE_H
+#define TRIGGERS_FUNC_INCLUDE_H
+
+#include "conveyor.qh"
+#include "door.qh"
+#include "ladder.qh"
+#include "plat.qh"
+#include "rainsnow.qh"
+#include "pointparticles.qh"
+#include "train.qh"
+
+#endif
diff --git a/qcsrc/common/triggers/func/ladder.qc b/qcsrc/common/triggers/func/ladder.qc
new file mode 100644 (file)
index 0000000..8b093f7
--- /dev/null
@@ -0,0 +1,73 @@
+void func_ladder_touch()
+{
+#ifdef SVQC
+       if (!other.iscreature)
+               return;
+       if (other.vehicle_flags & VHF_ISVEHICLE)
+               return;
+#endif
+#ifdef CSQC
+       if(other.classname != "csqcmodel")
+               return;
+#endif
+
+       EXACTTRIGGER_TOUCH;
+
+       other.ladder_time = time + 0.1;
+       other.ladder_entity = self;
+}
+
+#ifdef SVQC
+float func_ladder_send(entity to, float sf)
+{
+       WriteByte(MSG_ENTITY, ENT_CLIENT_LADDER);
+
+       WriteString(MSG_ENTITY, self.classname);
+       WriteByte(MSG_ENTITY, self.skin);
+       WriteByte(MSG_ENTITY, self.speed);
+
+       trigger_common_write(false);
+
+       return true;
+}
+
+void func_ladder_link()
+{
+       Net_LinkEntity(self, false, 0, func_ladder_send);
+}
+
+void spawnfunc_func_ladder()
+{
+       EXACTTRIGGER_INIT;
+       self.touch = func_ladder_touch;
+
+       func_ladder_link();
+}
+
+void spawnfunc_func_water()
+{
+       EXACTTRIGGER_INIT;
+       self.touch = func_ladder_touch;
+
+       func_ladder_link();
+}
+
+#elif defined(CSQC)
+.float speed;
+
+void ent_func_ladder()
+{
+       self.classname = strzone(ReadString());
+       self.skin = ReadByte();
+       self.speed = ReadByte();
+
+       trigger_common_read(false);
+
+       self.solid = SOLID_TRIGGER;
+       self.draw = trigger_draw_generic;
+       self.trigger_touch = func_ladder_touch;
+       self.drawmask = MASK_NORMAL;
+       self.move_time = time;
+       self.entremove = trigger_remove_generic;
+}
+#endif
diff --git a/qcsrc/common/triggers/func/ladder.qh b/qcsrc/common/triggers/func/ladder.qh
new file mode 100644 (file)
index 0000000..11eeeda
--- /dev/null
@@ -0,0 +1,6 @@
+.float ladder_time;
+.entity ladder_entity;
+
+#ifdef CSQC
+void ent_func_ladder();
+#endif
diff --git a/qcsrc/common/triggers/func/pendulum.qc b/qcsrc/common/triggers/func/pendulum.qc
new file mode 100644 (file)
index 0000000..7d3eff7
--- /dev/null
@@ -0,0 +1,77 @@
+#ifdef SVQC
+.float freq;
+void func_pendulum_controller_think()
+{
+       float v;
+       self.nextthink = time + 0.1;
+
+       if (!(self.owner.active == ACTIVE_ACTIVE))
+       {
+               self.owner.avelocity_x = 0;
+               return;
+       }
+
+       // calculate sinewave using makevectors
+       makevectors((self.nextthink * self.owner.freq + self.owner.phase) * '0 360 0');
+       v = self.owner.speed * v_forward_y + self.cnt;
+       if(self.owner.classname == "func_pendulum") // don't brake stuff if the func_bobbing was killtarget'ed
+       {
+               // * 10 so it will arrive in 0.1 sec
+               self.owner.avelocity_z = (remainder(v - self.owner.angles_z, 360)) * 10;
+       }
+}
+
+void spawnfunc_func_pendulum()
+{
+       entity controller;
+       if (self.noise != "")
+       {
+               precache_sound(self.noise);
+               soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
+       }
+
+       self.active = ACTIVE_ACTIVE;
+
+       // keys: angle, speed, phase, noise, freq
+
+       if(!self.speed)
+               self.speed = 30;
+       // not initializing self.dmg to 2, to allow damageless pendulum
+
+       if(self.dmg && (self.message == ""))
+               self.message = " was squished";
+       if(self.dmg && (self.message2 == ""))
+               self.message2 = "was squished by";
+       if(self.dmg && (!self.dmgtime))
+               self.dmgtime = 0.25;
+       self.dmgtime2 = time;
+
+       self.blocked = generic_plat_blocked;
+
+       self.avelocity_z = 0.0000001;
+       if (!InitMovingBrushTrigger())
+               return;
+
+       if(!self.freq)
+       {
+               // find pendulum length (same formula as Q3A)
+               self.freq = 1 / (M_PI * 2) * sqrt(autocvar_sv_gravity / (3 * max(8, fabs(self.mins_z))));
+       }
+
+       // copy initial angle
+       self.cnt = self.angles_z;
+
+       // wait for targets to spawn
+       controller = spawn();
+       controller.classname = "func_pendulum_controller";
+       controller.owner = self;
+       controller.nextthink = time + 1;
+       controller.think = func_pendulum_controller_think;
+       self.nextthink = self.SUB_LTIME + 999999999;
+       self.SUB_THINK = SUB_NullThink; // for PushMove
+
+       //self.effects |= EF_LOWPRECISION;
+
+       // TODO make a reset function for this one
+}
+#endif
diff --git a/qcsrc/common/triggers/func/plat.qc b/qcsrc/common/triggers/func/plat.qc
new file mode 100644 (file)
index 0000000..198083f
--- /dev/null
@@ -0,0 +1,201 @@
+#ifdef SVQC
+void plat_link();
+
+void plat_delayedinit()
+{
+       plat_link();
+       plat_spawn_inside_trigger(); // the "start moving" trigger
+}
+
+float plat_send(entity to, float sf)
+{
+       WriteByte(MSG_ENTITY, ENT_CLIENT_PLAT);
+       WriteByte(MSG_ENTITY, sf);
+
+       if(sf & SF_TRIGGER_INIT)
+       {
+               WriteByte(MSG_ENTITY, self.platmovetype_start);
+               WriteByte(MSG_ENTITY, self.platmovetype_turn);
+               WriteByte(MSG_ENTITY, self.platmovetype_end);
+               WriteByte(MSG_ENTITY, self.spawnflags);
+
+               WriteString(MSG_ENTITY, self.model);
+
+               trigger_common_write(true);
+
+               WriteCoord(MSG_ENTITY, self.pos1_x);
+               WriteCoord(MSG_ENTITY, self.pos1_y);
+               WriteCoord(MSG_ENTITY, self.pos1_z);
+               WriteCoord(MSG_ENTITY, self.pos2_x);
+               WriteCoord(MSG_ENTITY, self.pos2_y);
+               WriteCoord(MSG_ENTITY, self.pos2_z);
+
+               WriteCoord(MSG_ENTITY, self.size_x);
+               WriteCoord(MSG_ENTITY, self.size_y);
+               WriteCoord(MSG_ENTITY, self.size_z);
+
+               WriteAngle(MSG_ENTITY, self.mangle_x);
+               WriteAngle(MSG_ENTITY, self.mangle_y);
+               WriteAngle(MSG_ENTITY, self.mangle_z);
+
+               WriteShort(MSG_ENTITY, self.speed);
+               WriteShort(MSG_ENTITY, self.height);
+               WriteByte(MSG_ENTITY, self.lip);
+               WriteByte(MSG_ENTITY, self.state);
+
+               WriteShort(MSG_ENTITY, self.dmg);
+       }
+
+       if(sf & SF_TRIGGER_RESET)
+       {
+               // used on client
+       }
+
+       return true;
+}
+
+void plat_link()
+{
+       Net_LinkEntity(self, 0, false, plat_send);
+}
+
+void spawnfunc_func_plat()
+{
+       if (self.sounds == 0)
+               self.sounds = 2;
+
+    if(self.spawnflags & 4)
+        self.dmg = 10000;
+
+    if(self.dmg && (self.message == ""))
+               self.message = "was squished";
+    if(self.dmg && (self.message2 == ""))
+               self.message2 = "was squished by";
+
+       if (self.sounds == 1)
+       {
+               precache_sound ("plats/plat1.wav");
+               precache_sound ("plats/plat2.wav");
+               self.noise = "plats/plat1.wav";
+               self.noise1 = "plats/plat2.wav";
+       }
+
+       if (self.sounds == 2)
+       {
+               precache_sound ("plats/medplat1.wav");
+               precache_sound ("plats/medplat2.wav");
+               self.noise = "plats/medplat1.wav";
+               self.noise1 = "plats/medplat2.wav";
+       }
+
+       if (self.sound1)
+       {
+               precache_sound (self.sound1);
+               self.noise = self.sound1;
+       }
+       if (self.sound2)
+       {
+               precache_sound (self.sound2);
+               self.noise1 = self.sound2;
+       }
+
+       self.mangle = self.angles;
+       self.angles = '0 0 0';
+
+       self.classname = "plat";
+       if (!InitMovingBrushTrigger())
+               return;
+       self.effects |= EF_LOWPRECISION;
+       setsize (self, self.mins , self.maxs);
+
+       self.blocked = plat_crush;
+
+       if (!self.speed)
+               self.speed = 150;
+       if (!self.lip)
+               self.lip = 16;
+       if (!self.height)
+               self.height = self.size_z - self.lip;
+
+       self.pos1 = self.origin;
+       self.pos2 = self.origin;
+       self.pos2_z = self.origin_z - self.height;
+
+       self.reset = plat_reset;
+       plat_reset();
+
+       InitializeEntity(self, plat_delayedinit, INITPRIO_FINDTARGET);
+}
+#elif defined(CSQC)
+void plat_draw()
+{
+       Movetype_Physics_NoMatchServer();
+       //Movetype_Physics_MatchServer(autocvar_cl_projectiles_sloppy);
+}
+
+void ent_plat()
+{
+       float sf = ReadByte();
+
+       if(sf & SF_TRIGGER_INIT)
+       {
+               self.platmovetype_start = ReadByte();
+               self.platmovetype_turn = ReadByte();
+               self.platmovetype_end = ReadByte();
+               self.spawnflags = ReadByte();
+
+               self.model = strzone(ReadString());
+               setmodel(self, self.model);
+
+               trigger_common_read(true);
+
+               self.pos1_x = ReadCoord();
+               self.pos1_y = ReadCoord();
+               self.pos1_z = ReadCoord();
+               self.pos2_x = ReadCoord();
+               self.pos2_y = ReadCoord();
+               self.pos2_z = ReadCoord();
+
+               self.size_x = ReadCoord();
+               self.size_y = ReadCoord();
+               self.size_z = ReadCoord();
+
+               self.mangle_x = ReadAngle();
+               self.mangle_y = ReadAngle();
+               self.mangle_z = ReadAngle();
+
+               self.speed = ReadShort();
+               self.height = ReadShort();
+               self.lip = ReadByte();
+               self.state = ReadByte();
+
+               self.dmg = ReadShort();
+
+               self.classname = "plat";
+               self.solid = SOLID_BSP;
+               self.movetype = MOVETYPE_PUSH;
+               self.drawmask = MASK_NORMAL;
+               self.draw = plat_draw;
+               self.use = plat_use;
+               self.entremove = trigger_remove_generic;
+
+               plat_reset(); // also called here
+
+               self.move_movetype = MOVETYPE_PUSH;
+               self.move_origin = self.origin;
+               self.move_angles = self.angles;
+               self.move_time = time;
+
+               plat_spawn_inside_trigger();
+       }
+
+       if(sf & SF_TRIGGER_RESET)
+       {
+               plat_reset();
+
+               self.move_origin = self.origin;
+               self.move_angles = self.angles;
+               self.move_time = time;
+       }
+}
+#endif
diff --git a/qcsrc/common/triggers/func/plat.qh b/qcsrc/common/triggers/func/plat.qh
new file mode 100644 (file)
index 0000000..e3562f4
--- /dev/null
@@ -0,0 +1,3 @@
+#ifdef CSQC
+void ent_plat();
+#endif
diff --git a/qcsrc/common/triggers/func/pointparticles.qc b/qcsrc/common/triggers/func/pointparticles.qc
new file mode 100644 (file)
index 0000000..773ce6a
--- /dev/null
@@ -0,0 +1,362 @@
+#ifdef CSQC
+       #include "../../../client/particles.qh"
+#endif
+
+#ifdef SVQC
+// NOTE: also contains func_sparks
+
+float pointparticles_SendEntity(entity to, float fl)
+{
+       WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
+
+       // optional features to save space
+       fl = fl & 0x0F;
+       if(self.spawnflags & 2)
+               fl |= 0x10; // absolute count on toggle-on
+       if(self.movedir != '0 0 0' || self.velocity != '0 0 0')
+               fl |= 0x20; // 4 bytes - saves CPU
+       if(self.waterlevel || self.count != 1)
+               fl |= 0x40; // 4 bytes - obscure features almost never used
+       if(self.mins != '0 0 0' || self.maxs != '0 0 0')
+               fl |= 0x80; // 14 bytes - saves lots of space
+
+       WriteByte(MSG_ENTITY, fl);
+       if(fl & 2)
+       {
+               if(self.state)
+                       WriteCoord(MSG_ENTITY, self.impulse);
+               else
+                       WriteCoord(MSG_ENTITY, 0); // off
+       }
+       if(fl & 4)
+       {
+               WriteCoord(MSG_ENTITY, self.origin_x);
+               WriteCoord(MSG_ENTITY, self.origin_y);
+               WriteCoord(MSG_ENTITY, self.origin_z);
+       }
+       if(fl & 1)
+       {
+               if(self.model != "null")
+               {
+                       WriteShort(MSG_ENTITY, self.modelindex);
+                       if(fl & 0x80)
+                       {
+                               WriteCoord(MSG_ENTITY, self.mins_x);
+                               WriteCoord(MSG_ENTITY, self.mins_y);
+                               WriteCoord(MSG_ENTITY, self.mins_z);
+                               WriteCoord(MSG_ENTITY, self.maxs_x);
+                               WriteCoord(MSG_ENTITY, self.maxs_y);
+                               WriteCoord(MSG_ENTITY, self.maxs_z);
+                       }
+               }
+               else
+               {
+                       WriteShort(MSG_ENTITY, 0);
+                       if(fl & 0x80)
+                       {
+                               WriteCoord(MSG_ENTITY, self.maxs_x);
+                               WriteCoord(MSG_ENTITY, self.maxs_y);
+                               WriteCoord(MSG_ENTITY, self.maxs_z);
+                       }
+               }
+               WriteShort(MSG_ENTITY, self.cnt);
+               if(fl & 0x20)
+               {
+                       WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
+                       WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
+               }
+               if(fl & 0x40)
+               {
+                       WriteShort(MSG_ENTITY, self.waterlevel * 16.0);
+                       WriteByte(MSG_ENTITY, self.count * 16.0);
+               }
+               WriteString(MSG_ENTITY, self.noise);
+               if(self.noise != "")
+               {
+                       WriteByte(MSG_ENTITY, floor(self.atten * 64));
+                       WriteByte(MSG_ENTITY, floor(self.volume * 255));
+               }
+               WriteString(MSG_ENTITY, self.bgmscript);
+               if(self.bgmscript != "")
+               {
+                       WriteByte(MSG_ENTITY, floor(self.bgmscriptattack * 64));
+                       WriteByte(MSG_ENTITY, floor(self.bgmscriptdecay * 64));
+                       WriteByte(MSG_ENTITY, floor(self.bgmscriptsustain * 255));
+                       WriteByte(MSG_ENTITY, floor(self.bgmscriptrelease * 64));
+               }
+       }
+       return 1;
+}
+
+void pointparticles_use()
+{
+       self.state = !self.state;
+       self.SendFlags |= 2;
+}
+
+void pointparticles_think()
+{
+       if(self.origin != self.oldorigin)
+       {
+               self.SendFlags |= 4;
+               self.oldorigin = self.origin;
+       }
+       self.nextthink = time;
+}
+
+void pointparticles_reset()
+{
+       if(self.spawnflags & 1)
+               self.state = 1;
+       else
+               self.state = 0;
+}
+
+void spawnfunc_func_pointparticles()
+{
+       if(self.model != "")
+               setmodel(self, self.model);
+       if(self.noise != "")
+               precache_sound (self.noise);
+
+       if(!self.bgmscriptsustain)
+               self.bgmscriptsustain = 1;
+       else if(self.bgmscriptsustain < 0)
+               self.bgmscriptsustain = 0;
+
+       if(!self.atten)
+               self.atten = ATTEN_NORM;
+       else if(self.atten < 0)
+               self.atten = 0;
+       if(!self.volume)
+               self.volume = 1;
+       if(!self.count)
+               self.count = 1;
+       if(!self.impulse)
+               self.impulse = 1;
+
+       if(!self.modelindex)
+       {
+               setorigin(self, self.origin + self.mins);
+               setsize(self, '0 0 0', self.maxs - self.mins);
+       }
+       if(!self.cnt)
+               self.cnt = particleeffectnum(self.mdl);
+
+       Net_LinkEntity(self, (self.spawnflags & 4), 0, pointparticles_SendEntity);
+
+       IFTARGETED
+       {
+               self.use = pointparticles_use;
+               self.reset = pointparticles_reset;
+               self.reset();
+       }
+       else
+               self.state = 1;
+       self.think = pointparticles_think;
+       self.nextthink = time;
+}
+
+void spawnfunc_func_sparks()
+{
+       // self.cnt is the amount of sparks that one burst will spawn
+       if(self.cnt < 1) {
+               self.cnt = 25.0; // nice default value
+       }
+
+       // self.wait is the probability that a sparkthink will spawn a spark shower
+       // range: 0 - 1, but 0 makes little sense, so...
+       if(self.wait < 0.05) {
+               self.wait = 0.25; // nice default value
+       }
+
+       self.count = self.cnt;
+       self.mins = '0 0 0';
+       self.maxs = '0 0 0';
+       self.velocity = '0 0 -1';
+       self.mdl = "TE_SPARK";
+       self.impulse = 10 * self.wait; // by default 2.5/sec
+       self.wait = 0;
+       self.cnt = 0; // use mdl
+
+       spawnfunc_func_pointparticles();
+}
+#elif defined(CSQC)
+
+void Draw_PointParticles()
+{
+       float n, i, fail;
+       vector p;
+       vector sz;
+       vector o;
+       o = self.origin;
+       sz = self.maxs - self.mins;
+       n = BGMScript(self);
+       if(self.absolute == 2)
+       {
+               if(n >= 0)
+                       n = self.just_toggled ? self.impulse : 0;
+               else
+                       n = self.impulse * drawframetime;
+       }
+       else
+       {
+               n *= self.impulse * drawframetime;
+               if(self.just_toggled)
+                       if(n < 1)
+                               n = 1;
+       }
+       if(n == 0)
+               return;
+       fail = 0;
+       for(i = random(); i <= n && fail <= 64*n; ++i)
+       {
+               p = o + self.mins;
+               p.x += random() * sz.x;
+               p.y += random() * sz.y;
+               p.z += random() * sz.z;
+               if(WarpZoneLib_BoxTouchesBrush(p, p, self, world))
+               {
+                       if(self.movedir != '0 0 0')
+                       {
+                               traceline(p, p + normalize(self.movedir) * 4096, 0, world);
+                               p = trace_endpos;
+                               pointparticles(self.cnt, p, trace_plane_normal * vlen(self.movedir) + self.velocity + randomvec() * self.waterlevel, self.count);
+                       }
+                       else
+                       {
+                               pointparticles(self.cnt, p, self.velocity + randomvec() * self.waterlevel, self.count);
+                       }
+                       if(self.noise != "")
+                       {
+                               setorigin(self, p);
+                               sound(self, CH_AMBIENT, self.noise, VOL_BASE * self.volume, self.atten);
+                       }
+                       self.just_toggled = 0;
+               }
+               else if(self.absolute)
+               {
+                       ++fail;
+                       --i;
+               }
+       }
+       setorigin(self, o);
+}
+
+void Ent_PointParticles_Remove()
+{
+       if(self.noise)
+               strunzone(self.noise);
+       self.noise = string_null;
+       if(self.bgmscript)
+               strunzone(self.bgmscript);
+       self.bgmscript = string_null;
+}
+
+void Ent_PointParticles()
+{
+       float i;
+       vector v;
+       int f = ReadByte();
+       if(f & 2)
+       {
+               i = ReadCoord(); // density (<0: point, >0: volume)
+               if(i && !self.impulse && self.cnt) // self.cnt check is so it only happens if the ent already existed
+                       self.just_toggled = 1;
+               self.impulse = i;
+       }
+       if(f & 4)
+       {
+               self.origin_x = ReadCoord();
+               self.origin_y = ReadCoord();
+               self.origin_z = ReadCoord();
+       }
+       if(f & 1)
+       {
+               self.modelindex = ReadShort();
+               if(f & 0x80)
+               {
+                       if(self.modelindex)
+                       {
+                               self.mins_x = ReadCoord();
+                               self.mins_y = ReadCoord();
+                               self.mins_z = ReadCoord();
+                               self.maxs_x = ReadCoord();
+                               self.maxs_y = ReadCoord();
+                               self.maxs_z = ReadCoord();
+                       }
+                       else
+                       {
+                               self.mins    = '0 0 0';
+                               self.maxs_x = ReadCoord();
+                               self.maxs_y = ReadCoord();
+                               self.maxs_z = ReadCoord();
+                       }
+               }
+               else
+               {
+                       self.mins = self.maxs = '0 0 0';
+               }
+
+               self.cnt = ReadShort(); // effect number
+
+               if(f & 0x20)
+               {
+                       self.velocity = decompressShortVector(ReadShort());
+                       self.movedir = decompressShortVector(ReadShort());
+               }
+               else
+               {
+                       self.velocity = self.movedir = '0 0 0';
+               }
+               if(f & 0x40)
+               {
+                       self.waterlevel = ReadShort() / 16.0;
+                       self.count = ReadByte() / 16.0;
+               }
+               else
+               {
+                       self.waterlevel = 0;
+                       self.count = 1;
+               }
+               if(self.noise)
+                       strunzone(self.noise);
+               if(self.bgmscript)
+                       strunzone(self.bgmscript);
+               self.noise = strzone(ReadString());
+               if(self.noise != "")
+               {
+                       self.atten = ReadByte() / 64.0;
+                       self.volume = ReadByte() / 255.0;
+               }
+               self.bgmscript = strzone(ReadString());
+               if(self.bgmscript != "")
+               {
+                       self.bgmscriptattack = ReadByte() / 64.0;
+                       self.bgmscriptdecay = ReadByte() / 64.0;
+                       self.bgmscriptsustain = ReadByte() / 255.0;
+                       self.bgmscriptrelease = ReadByte() / 64.0;
+               }
+               BGMScript_InitEntity(self);
+       }
+
+       if(f & 2)
+       {
+               self.absolute = (self.impulse >= 0);
+               if(!self.absolute)
+               {
+                       v = self.maxs - self.mins;
+                       self.impulse *= -v.x * v.y * v.z / 262144; // relative: particles per 64^3 cube
+               }
+       }
+
+       if(f & 0x10)
+               self.absolute = 2;
+
+       setorigin(self, self.origin);
+       setsize(self, self.mins, self.maxs);
+       self.solid = SOLID_NOT;
+       self.draw = Draw_PointParticles;
+       self.entremove = Ent_PointParticles_Remove;
+}
+#endif
diff --git a/qcsrc/common/triggers/func/pointparticles.qh b/qcsrc/common/triggers/func/pointparticles.qh
new file mode 100644 (file)
index 0000000..d446e72
--- /dev/null
@@ -0,0 +1,5 @@
+#ifdef CSQC
+
+void Ent_PointParticles();
+
+#endif
\ No newline at end of file
diff --git a/qcsrc/common/triggers/func/rainsnow.qc b/qcsrc/common/triggers/func/rainsnow.qc
new file mode 100644 (file)
index 0000000..deb5ee5
--- /dev/null
@@ -0,0 +1,128 @@
+#ifdef SVQC
+float rainsnow_SendEntity(entity to, float sf)
+{
+       WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
+       WriteByte(MSG_ENTITY, self.state);
+       WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
+       WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
+       WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
+       WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
+       WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
+       WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
+       WriteShort(MSG_ENTITY, compressShortVector(self.dest));
+       WriteShort(MSG_ENTITY, self.count);
+       WriteByte(MSG_ENTITY, self.cnt);
+       return 1;
+}
+
+/*QUAKED spawnfunc_func_rain (0 .5 .8) ?
+This is an invisible area like a trigger, which rain falls inside of.
+
+Keys:
+"velocity"
+ falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
+"cnt"
+ sets color of rain (default 12 - white)
+"count"
+ adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
+*/
+void spawnfunc_func_rain()
+{
+       self.dest = self.velocity;
+       self.velocity = '0 0 0';
+       if (!self.dest)
+               self.dest = '0 0 -700';
+       self.angles = '0 0 0';
+       self.movetype = MOVETYPE_NONE;
+       self.solid = SOLID_NOT;
+       SetBrushEntityModel();
+       if (!self.cnt)
+               self.cnt = 12;
+       if (!self.count)
+               self.count = 2000;
+       self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
+       if (self.count < 1)
+               self.count = 1;
+       if(self.count > 65535)
+               self.count = 65535;
+
+       self.state = 1; // 1 is rain, 0 is snow
+       self.Version = 1;
+
+       Net_LinkEntity(self, false, 0, rainsnow_SendEntity);
+}
+
+
+/*QUAKED spawnfunc_func_snow (0 .5 .8) ?
+This is an invisible area like a trigger, which snow falls inside of.
+
+Keys:
+"velocity"
+ falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
+"cnt"
+ sets color of rain (default 12 - white)
+"count"
+ adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
+*/
+void spawnfunc_func_snow()
+{
+       self.dest = self.velocity;
+       self.velocity = '0 0 0';
+       if (!self.dest)
+               self.dest = '0 0 -300';
+       self.angles = '0 0 0';
+       self.movetype = MOVETYPE_NONE;
+       self.solid = SOLID_NOT;
+       SetBrushEntityModel();
+       if (!self.cnt)
+               self.cnt = 12;
+       if (!self.count)
+               self.count = 2000;
+       self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
+       if (self.count < 1)
+               self.count = 1;
+       if(self.count > 65535)
+               self.count = 65535;
+
+       self.state = 0; // 1 is rain, 0 is snow
+       self.Version = 1;
+
+       Net_LinkEntity(self, false, 0, rainsnow_SendEntity);
+}
+#elif defined(CSQC)
+void Draw_Rain()
+{
+    te_particlerain(self.origin + self.mins, self.origin + self.maxs, self.velocity, floor(self.count * drawframetime + random()), self.glow_color);
+}
+
+void Draw_Snow()
+{
+    te_particlesnow(self.origin + self.mins, self.origin + self.maxs, self.velocity, floor(self.count * drawframetime + random()), self.glow_color);
+}
+
+void Ent_RainOrSnow()
+{
+       self.impulse = ReadByte(); // Rain, Snow, or Whatever
+       self.origin_x = ReadCoord();
+       self.origin_y = ReadCoord();
+       self.origin_z = ReadCoord();
+       self.maxs_x = ReadCoord();
+       self.maxs_y = ReadCoord();
+       self.maxs_z = ReadCoord();
+       self.velocity = decompressShortVector(ReadShort());
+       self.count = ReadShort() * 10;
+       self.glow_color = ReadByte(); // color
+
+       self.mins    = -0.5 * self.maxs;
+       self.maxs    =  0.5 * self.maxs;
+       self.origin  = self.origin - self.mins;
+
+       setorigin(self, self.origin);
+       setsize(self, self.mins, self.maxs);
+       self.solid = SOLID_NOT;
+       if(self.impulse)
+               self.draw = Draw_Rain;
+       else
+               self.draw = Draw_Snow;
+}
+#endif
diff --git a/qcsrc/common/triggers/func/rainsnow.qh b/qcsrc/common/triggers/func/rainsnow.qh
new file mode 100644 (file)
index 0000000..5d8d923
--- /dev/null
@@ -0,0 +1,3 @@
+#ifdef CSQC
+void Ent_RainOrSnow();
+#endif
diff --git a/qcsrc/common/triggers/func/rotating.qc b/qcsrc/common/triggers/func/rotating.qc
new file mode 100644 (file)
index 0000000..93d4a30
--- /dev/null
@@ -0,0 +1,77 @@
+#ifdef SVQC
+void func_rotating_setactive(float astate)
+{
+
+       if (astate == ACTIVE_TOGGLE)
+       {
+               if(self.active == ACTIVE_ACTIVE)
+                       self.active = ACTIVE_NOT;
+               else
+                       self.active = ACTIVE_ACTIVE;
+       }
+       else
+               self.active = astate;
+
+       if(self.active  == ACTIVE_NOT)
+               self.avelocity = '0 0 0';
+       else
+               self.avelocity = self.pos1;
+}
+
+/*QUAKED spawnfunc_func_rotating (0 .5 .8) ? - - X_AXIS Y_AXIS
+Brush model that spins in place on one axis (default Z).
+speed   : speed to rotate (in degrees per second)
+noise   : path/name of looping .wav file to play.
+dmg     : Do this mutch dmg every .dmgtime intervall when blocked
+dmgtime : See above.
+*/
+
+void spawnfunc_func_rotating()
+{
+       if (self.noise != "")
+       {
+               precache_sound(self.noise);
+               ambientsound(self.origin, self.noise, VOL_BASE, ATTEN_IDLE);
+       }
+
+       self.active = ACTIVE_ACTIVE;
+       self.setactive = func_rotating_setactive;
+
+       if (!self.speed)
+               self.speed = 100;
+       // FIXME: test if this turns the right way, then remove this comment (negate as needed)
+       if (self.spawnflags & 4) // X (untested)
+               self.avelocity = '0 0 1' * self.speed;
+       // FIXME: test if this turns the right way, then remove this comment (negate as needed)
+       else if (self.spawnflags & 8) // Y (untested)
+               self.avelocity = '1 0 0' * self.speed;
+       // FIXME: test if this turns the right way, then remove this comment (negate as needed)
+       else // Z
+               self.avelocity = '0 1 0' * self.speed;
+
+       self.pos1 = self.avelocity;
+
+    if(self.dmg && (self.message == ""))
+        self.message = " was squished";
+    if(self.dmg && (self.message2 == ""))
+               self.message2 = "was squished by";
+
+
+    if(self.dmg && (!self.dmgtime))
+        self.dmgtime = 0.25;
+
+    self.dmgtime2 = time;
+
+       if (!InitMovingBrushTrigger())
+               return;
+       // no EF_LOWPRECISION here, as rounding angles is bad
+
+    self.blocked = generic_plat_blocked;
+
+       // wait for targets to spawn
+       self.SUB_NEXTTHINK = self.SUB_LTIME + 999999999;
+       self.SUB_THINK = SUB_NullThink; // for PushMove
+
+       // TODO make a reset function for this one
+}
+#endif
diff --git a/qcsrc/common/triggers/func/stardust.qc b/qcsrc/common/triggers/func/stardust.qc
new file mode 100644 (file)
index 0000000..30c406f
--- /dev/null
@@ -0,0 +1,8 @@
+#ifdef SVQC
+void spawnfunc_func_stardust()
+{
+       self.effects = EF_STARDUST;
+
+       CSQCMODEL_AUTOINIT();
+}
+#endif
diff --git a/qcsrc/common/triggers/func/train.qc b/qcsrc/common/triggers/func/train.qc
new file mode 100644 (file)
index 0000000..2a5d2fa
--- /dev/null
@@ -0,0 +1,316 @@
+.float train_wait_turning;
+void() train_next;
+void train_wait()
+{
+       entity oldself;
+       oldself = self;
+       self = self.enemy;
+       SUB_UseTargets();
+       self = oldself;
+       self.enemy = world;
+
+       // if turning is enabled, the train will turn toward the next point while waiting
+       if(self.platmovetype_turn && !self.train_wait_turning)
+       {
+               entity targ, cp;
+               vector ang;
+               targ = find(world, targetname, self.target);
+               if((self.spawnflags & 1) && targ.curvetarget)
+                       cp = find(world, targetname, targ.curvetarget);
+               else
+                       cp = world;
+
+               if(cp) // bezier curves movement
+                       ang = cp.origin - (self.origin - self.view_ofs); // use the origin of the control point of the next path_corner
+               else // linear movement
+                       ang = targ.origin - (self.origin - self.view_ofs); // use the origin of the next path_corner
+               ang = vectoangles(ang);
+               ang_x = -ang_x; // flip up / down orientation
+
+               if(self.wait > 0) // slow turning
+                       SUB_CalcAngleMove(ang, TSPEED_TIME, self.SUB_LTIME - time + self.wait, train_wait);
+               else // instant turning
+                       SUB_CalcAngleMove(ang, TSPEED_TIME, 0.0000001, train_wait);
+               self.train_wait_turning = true;
+               return;
+       }
+
+#ifdef SVQC
+       if(self.noise != "")
+               stopsoundto(MSG_BROADCAST, self, CH_TRIGGER_SINGLE); // send this as unreliable only, as the train will resume operation shortly anyway
+#endif
+
+       if(self.wait < 0 || self.train_wait_turning) // no waiting or we already waited while turning
+       {
+               self.train_wait_turning = false;
+               train_next();
+       }
+       else
+       {
+               self.SUB_THINK = train_next;
+               self.SUB_NEXTTHINK = self.SUB_LTIME + self.wait;
+       }
+}
+
+void train_next()
+{
+       entity targ, cp = world;
+       vector cp_org = '0 0 0';
+
+       targ = find(world, targetname, self.target);
+       self.target = targ.target;
+       if (self.spawnflags & 1)
+       {
+               if(targ.curvetarget)
+               {
+                       cp = find(world, targetname, targ.curvetarget); // get its second target (the control point)
+                       cp_org = cp.origin - self.view_ofs; // no control point found, assume a straight line to the destination
+               }
+       }
+       if (self.target == "")
+               objerror("train_next: no next target");
+       self.wait = targ.wait;
+       if (!self.wait)
+               self.wait = 0.1;
+
+       if(targ.platmovetype)
+       {
+               // this path_corner contains a movetype overrider, apply it
+               self.platmovetype_start = targ.platmovetype_start;
+               self.platmovetype_end = targ.platmovetype_end;
+       }
+       else
+       {
+               // this path_corner doesn't contain a movetype overrider, use the train's defaults
+               self.platmovetype_start = self.platmovetype_start_default;
+               self.platmovetype_end = self.platmovetype_end_default;
+       }
+
+       if (targ.speed)
+       {
+               if (cp)
+                       SUB_CalcMove_Bezier(cp_org, targ.origin - self.view_ofs, TSPEED_LINEAR, targ.speed, train_wait);
+               else
+                       SUB_CalcMove(targ.origin - self.view_ofs, TSPEED_LINEAR, targ.speed, train_wait);
+       }
+       else
+       {
+               if (cp)
+                       SUB_CalcMove_Bezier(cp_org, targ.origin - self.view_ofs, TSPEED_LINEAR, self.speed, train_wait);
+               else
+                       SUB_CalcMove(targ.origin - self.view_ofs, TSPEED_LINEAR, self.speed, train_wait);
+       }
+
+       if(self.noise != "")
+               sound(self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
+}
+
+#ifdef SVQC
+float train_send(entity to, float sf)
+{
+       WriteByte(MSG_ENTITY, ENT_CLIENT_TRAIN);
+       WriteByte(MSG_ENTITY, sf);
+
+       if(sf & SF_TRIGGER_INIT)
+       {
+               WriteString(MSG_ENTITY, self.platmovetype);
+               WriteByte(MSG_ENTITY, self.platmovetype_turn);
+               WriteByte(MSG_ENTITY, self.spawnflags);
+
+               WriteString(MSG_ENTITY, self.model);
+
+               trigger_common_write(true);
+
+               WriteString(MSG_ENTITY, self.curvetarget);
+
+               WriteCoord(MSG_ENTITY, self.pos1_x);
+               WriteCoord(MSG_ENTITY, self.pos1_y);
+               WriteCoord(MSG_ENTITY, self.pos1_z);
+               WriteCoord(MSG_ENTITY, self.pos2_x);
+               WriteCoord(MSG_ENTITY, self.pos2_y);
+               WriteCoord(MSG_ENTITY, self.pos2_z);
+
+               WriteCoord(MSG_ENTITY, self.size_x);
+               WriteCoord(MSG_ENTITY, self.size_y);
+               WriteCoord(MSG_ENTITY, self.size_z);
+
+               WriteCoord(MSG_ENTITY, self.view_ofs_x);
+               WriteCoord(MSG_ENTITY, self.view_ofs_y);
+               WriteCoord(MSG_ENTITY, self.view_ofs_z);
+
+               WriteAngle(MSG_ENTITY, self.mangle_x);
+               WriteAngle(MSG_ENTITY, self.mangle_y);
+               WriteAngle(MSG_ENTITY, self.mangle_z);
+
+               WriteShort(MSG_ENTITY, self.speed);
+               WriteShort(MSG_ENTITY, self.height);
+               WriteByte(MSG_ENTITY, self.lip);
+               WriteByte(MSG_ENTITY, self.state);
+               WriteByte(MSG_ENTITY, self.wait);
+
+               WriteShort(MSG_ENTITY, self.dmg);
+               WriteByte(MSG_ENTITY, self.dmgtime);
+       }
+
+       if(sf & SF_TRIGGER_RESET)
+       {
+               // used on client
+       }
+
+       return true;
+}
+
+void train_link()
+{
+       Net_LinkEntity(self, 0, false, train_send);
+}
+
+void func_train_find()
+{
+       entity targ;
+       targ = find(world, targetname, self.target);
+       self.target = targ.target;
+       if (self.target == "")
+               objerror("func_train_find: no next target");
+       SUB_SETORIGIN(self, targ.origin - self.view_ofs);
+       self.SUB_NEXTTHINK = self.SUB_LTIME + 1;
+       self.SUB_THINK = train_next;
+
+       train_link();
+}
+
+#endif
+
+/*QUAKED spawnfunc_func_train (0 .5 .8) ?
+Ridable platform, targets spawnfunc_path_corner path to follow.
+speed : speed the train moves (can be overridden by each spawnfunc_path_corner)
+target : targetname of first spawnfunc_path_corner (starts here)
+*/
+#ifdef SVQC
+void spawnfunc_func_train()
+{
+       if (self.noise != "")
+               precache_sound(self.noise);
+
+       if (self.target == "")
+               objerror("func_train without a target");
+       if (!self.speed)
+               self.speed = 100;
+
+       if (!InitMovingBrushTrigger())
+               return;
+       self.effects |= EF_LOWPRECISION;
+       
+       if (self.spawnflags & 2)
+       {
+               self.platmovetype_turn = true;
+               self.view_ofs = '0 0 0'; // don't offset a rotating train, origin works differently now
+       }
+       else
+               self.view_ofs = self.mins;
+
+       // wait for targets to spawn
+       InitializeEntity(self, func_train_find, INITPRIO_FINDTARGET);
+
+       self.blocked = generic_plat_blocked;
+       if(self.dmg && (self.message == ""))
+               self.message = " was squished";
+    if(self.dmg && (self.message2 == ""))
+               self.message2 = "was squished by";
+       if(self.dmg && (!self.dmgtime))
+               self.dmgtime = 0.25;
+       self.dmgtime2 = time;
+
+       if(!set_platmovetype(self, self.platmovetype))
+               return;
+       self.platmovetype_start_default = self.platmovetype_start;
+       self.platmovetype_end_default = self.platmovetype_end;
+
+       // TODO make a reset function for this one
+}
+#elif defined(CSQC)
+void train_draw()
+{
+       //Movetype_Physics_NoMatchServer();
+       Movetype_Physics_MatchServer(autocvar_cl_projectiles_sloppy);
+}
+
+void ent_train()
+{
+       float sf = ReadByte();
+
+       if(sf & SF_TRIGGER_INIT)
+       {
+               self.platmovetype = strzone(ReadString());
+               self.platmovetype_turn = ReadByte();
+               self.spawnflags = ReadByte();
+
+               self.model = strzone(ReadString());
+               setmodel(self, self.model);
+
+               trigger_common_read(true);
+
+               self.curvetarget = strzone(ReadString());
+
+               self.pos1_x = ReadCoord();
+               self.pos1_y = ReadCoord();
+               self.pos1_z = ReadCoord();
+               self.pos2_x = ReadCoord();
+               self.pos2_y = ReadCoord();
+               self.pos2_z = ReadCoord();
+
+               self.size_x = ReadCoord();
+               self.size_y = ReadCoord();
+               self.size_z = ReadCoord();
+
+               self.view_ofs_x = ReadCoord();
+               self.view_ofs_y = ReadCoord();
+               self.view_ofs_z = ReadCoord();
+
+               self.mangle_x = ReadAngle();
+               self.mangle_y = ReadAngle();
+               self.mangle_z = ReadAngle();
+
+               self.speed = ReadShort();
+               self.height = ReadShort();
+               self.lip = ReadByte();
+               self.state = ReadByte();
+               self.wait = ReadByte();
+
+               self.dmg = ReadShort();
+               self.dmgtime = ReadByte();
+
+               self.classname = "func_train";
+               self.solid = SOLID_BSP;
+               self.movetype = MOVETYPE_PUSH;
+               self.drawmask = MASK_NORMAL;
+               self.draw = train_draw;
+               self.entremove = trigger_remove_generic;
+
+               if(set_platmovetype(self, self.platmovetype))
+               {
+                       self.platmovetype_start_default = self.platmovetype_start;
+                       self.platmovetype_end_default = self.platmovetype_end;
+               }
+
+               // everything is set up by the time the train is linked, we shouldn't need this
+               //func_train_find();
+
+               // but we will need these
+               //self.move_nextthink = self.move_ltime + 0.1;
+               //self.move_think = train_next;
+               train_next();
+
+               self.move_movetype = MOVETYPE_PUSH;
+               self.move_origin = self.origin;
+               self.move_angles = self.angles;
+               self.move_time = time;
+       }
+
+       if(sf & SF_TRIGGER_RESET)
+       {
+               // TODO: make a reset function for trains
+       }
+}
+
+#endif
diff --git a/qcsrc/common/triggers/func/train.qh b/qcsrc/common/triggers/func/train.qh
new file mode 100644 (file)
index 0000000..f69515f
--- /dev/null
@@ -0,0 +1,4 @@
+#ifdef CSQC
+.float dmgtime;
+void ent_train();
+#endif
diff --git a/qcsrc/common/triggers/func/vectormamamam.qc b/qcsrc/common/triggers/func/vectormamamam.qc
new file mode 100644 (file)
index 0000000..521ac87
--- /dev/null
@@ -0,0 +1,159 @@
+#ifdef SVQC
+// reusing some fields havocbots declared
+.entity wp00, wp01, wp02, wp03;
+
+.float targetfactor, target2factor, target3factor, target4factor;
+.vector targetnormal, target2normal, target3normal, target4normal;
+
+vector func_vectormamamam_origin(entity o, float t)
+{
+       vector v, p;
+       float f;
+       entity e;
+
+       f = o.spawnflags;
+       v = '0 0 0';
+
+       e = o.wp00;
+       if(e)
+       {
+               p = e.origin + t * e.velocity;
+               if(f & 1)
+                       v = v + (p * o.targetnormal) * o.targetnormal * o.targetfactor;
+               else
+                       v = v + (p - (p * o.targetnormal) * o.targetnormal) * o.targetfactor;
+       }
+
+       e = o.wp01;
+       if(e)
+       {
+               p = e.origin + t * e.velocity;
+               if(f & 2)
+                       v = v + (p * o.target2normal) * o.target2normal * o.target2factor;
+               else
+                       v = v + (p - (p * o.target2normal) * o.target2normal) * o.target2factor;
+       }
+
+       e = o.wp02;
+       if(e)
+       {
+               p = e.origin + t * e.velocity;
+               if(f & 4)
+                       v = v + (p * o.target3normal) * o.target3normal * o.target3factor;
+               else
+                       v = v + (p - (p * o.target3normal) * o.target3normal) * o.target3factor;
+       }
+
+       e = o.wp03;
+       if(e)
+       {
+               p = e.origin + t * e.velocity;
+               if(f & 8)
+                       v = v + (p * o.target4normal) * o.target4normal * o.target4factor;
+               else
+                       v = v + (p - (p * o.target4normal) * o.target4normal) * o.target4factor;
+       }
+
+       return v;
+}
+
+void func_vectormamamam_controller_think()
+{
+       self.nextthink = time + 0.1;
+
+       if(self.owner.active != ACTIVE_ACTIVE)
+       {
+               self.owner.velocity = '0 0 0';
+               return;
+       }
+
+       if(self.owner.classname == "func_vectormamamam") // don't brake stuff if the func_vectormamamam was killtarget'ed
+               self.owner.velocity = (self.owner.destvec + func_vectormamamam_origin(self.owner, 0.1) - self.owner.origin) * 10;
+}
+
+void func_vectormamamam_findtarget()
+{
+       if(self.target != "")
+               self.wp00 = find(world, targetname, self.target);
+
+       if(self.target2 != "")
+               self.wp01 = find(world, targetname, self.target2);
+
+       if(self.target3 != "")
+               self.wp02 = find(world, targetname, self.target3);
+
+       if(self.target4 != "")
+               self.wp03 = find(world, targetname, self.target4);
+
+       if(!self.wp00 && !self.wp01 && !self.wp02 && !self.wp03)
+               objerror("No reference entity found, so there is nothing to move. Aborting.");
+
+       self.destvec = self.origin - func_vectormamamam_origin(self, 0);
+
+       entity controller;
+       controller = spawn();
+       controller.classname = "func_vectormamamam_controller";
+       controller.owner = self;
+       controller.nextthink = time + 1;
+       controller.think = func_vectormamamam_controller_think;
+}
+
+void spawnfunc_func_vectormamamam()
+{
+       if (self.noise != "")
+       {
+               precache_sound(self.noise);
+               soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
+       }
+
+       if(!self.targetfactor)
+               self.targetfactor = 1;
+
+       if(!self.target2factor)
+               self.target2factor = 1;
+
+       if(!self.target3factor)
+               self.target3factor = 1;
+
+       if(!self.target4factor)
+               self.target4factor = 1;
+
+       if(vlen(self.targetnormal))
+               self.targetnormal = normalize(self.targetnormal);
+
+       if(vlen(self.target2normal))
+               self.target2normal = normalize(self.target2normal);
+
+       if(vlen(self.target3normal))
+               self.target3normal = normalize(self.target3normal);
+
+       if(vlen(self.target4normal))
+               self.target4normal = normalize(self.target4normal);
+
+       self.blocked = generic_plat_blocked;
+       if(self.dmg && (self.message == ""))
+               self.message = " was squished";
+    if(self.dmg && (self.message == ""))
+               self.message2 = "was squished by";
+       if(self.dmg && (!self.dmgtime))
+               self.dmgtime = 0.25;
+       self.dmgtime2 = time;
+
+       if(self.netname == "")
+               self.netname = "1 0 0 0 1";
+
+       if (!InitMovingBrushTrigger())
+               return;
+
+       // wait for targets to spawn
+       self.SUB_NEXTTHINK = self.SUB_LTIME + 999999999;
+       self.SUB_THINK = SUB_NullThink; // for PushMove
+
+       // Savage: Reduce bandwith, critical on e.g. nexdm02
+       self.effects |= EF_LOWPRECISION;
+
+       self.active = ACTIVE_ACTIVE;
+
+       InitializeEntity(self, func_vectormamamam_findtarget, INITPRIO_FINDTARGET);
+}
+#endif
diff --git a/qcsrc/common/triggers/include.qc b/qcsrc/common/triggers/include.qc
new file mode 100644 (file)
index 0000000..59da1f2
--- /dev/null
@@ -0,0 +1,20 @@
+#include "include.qh"
+
+// some required common stuff
+#include "subs.qc"
+#include "triggers.qc"
+#include "platforms.qc"
+#include "teleporters.qc"
+
+// func
+#include "func/include.qc"
+
+// misc
+#include "misc/include.qc"
+
+// target
+#include "target/include.qc"
+
+// trigger
+#include "trigger/include.qc"
+
diff --git a/qcsrc/common/triggers/include.qh b/qcsrc/common/triggers/include.qh
new file mode 100644 (file)
index 0000000..138d267
--- /dev/null
@@ -0,0 +1,25 @@
+#ifndef TRIGGERS_INCLUDE_H
+#define TRIGGERS_INCLUDE_H
+
+// some required common stuff
+#ifdef CSQC
+       #include "../../server/item_key.qh"
+#endif
+#include "triggers.qh"
+#include "subs.qh"
+#include "platforms.qh"
+#include "teleporters.qh"
+
+// func
+#include "func/include.qh"
+
+// misc
+#include "misc/include.qh"
+
+// target
+#include "target/include.qh"
+
+// trigger
+#include "trigger/include.qh"
+
+#endif
diff --git a/qcsrc/common/triggers/misc/corner.qc b/qcsrc/common/triggers/misc/corner.qc
new file mode 100644 (file)
index 0000000..35ad2ce
--- /dev/null
@@ -0,0 +1,85 @@
+#ifdef SVQC
+bool corner_send(entity to, int sf)
+{
+       WriteByte(MSG_ENTITY, ENT_CLIENT_CORNER);
+
+       WriteString(MSG_ENTITY, self.platmovetype);
+
+       WriteCoord(MSG_ENTITY, self.origin_x);
+       WriteCoord(MSG_ENTITY, self.origin_y);
+       WriteCoord(MSG_ENTITY, self.origin_z);
+
+       WriteString(MSG_ENTITY, self.target);
+       WriteString(MSG_ENTITY, self.target2);
+       WriteString(MSG_ENTITY, self.target3);
+       WriteString(MSG_ENTITY, self.target4);
+       WriteString(MSG_ENTITY, self.targetname);
+       WriteByte(MSG_ENTITY, self.target_random);
+
+       WriteByte(MSG_ENTITY, self.wait);
+
+       return true;
+}
+
+void corner_link()
+{
+       Net_LinkEntity(self, false, 0, corner_send);
+}
+
+void spawnfunc_path_corner()
+{
+       // setup values for overriding train movement
+       // if a second value does not exist, both start and end speeds are the single value specified
+       set_platmovetype(self, self.platmovetype);
+
+       corner_link();
+}
+#elif defined(CSQC)
+
+void corner_remove()
+{
+       if(self.target) { strunzone(self.target); }
+       self.target = string_null;
+
+       if(self.target2) { strunzone(self.target2); }
+       self.target2 = string_null;
+
+       if(self.target3) { strunzone(self.target3); }
+       self.target3 = string_null;
+
+       if(self.target4) { strunzone(self.target4); }
+       self.target4 = string_null;
+
+       if(self.targetname) { strunzone(self.targetname); }
+       self.targetname = string_null;
+
+       if(self.platmovetype) { strunzone(self.platmovetype); }
+       self.platmovetype = string_null;
+}
+
+void ent_corner()
+{
+       self.platmovetype = strzone(ReadString());
+
+       self.origin_x = ReadCoord();
+       self.origin_y = ReadCoord();
+       self.origin_z = ReadCoord();
+       setorigin(self, self.origin);
+
+       self.target = strzone(ReadString());
+       self.target2 = strzone(ReadString());
+       self.target3 = strzone(ReadString());
+       self.target4 = strzone(ReadString());
+       self.targetname = strzone(ReadString());
+       self.target_random = ReadByte();
+
+       self.wait = ReadByte();
+
+       self.classname = "path_corner";
+       self.drawmask = MASK_NORMAL;
+       self.entremove = corner_remove;
+
+       set_platmovetype(self, self.platmovetype);
+}
+
+#endif
diff --git a/qcsrc/common/triggers/misc/corner.qh b/qcsrc/common/triggers/misc/corner.qh
new file mode 100644 (file)
index 0000000..62b3aae
--- /dev/null
@@ -0,0 +1,3 @@
+#ifdef CSQC
+void ent_corner();
+#endif
diff --git a/qcsrc/common/triggers/misc/follow.qc b/qcsrc/common/triggers/misc/follow.qc
new file mode 100644 (file)
index 0000000..bfeb865
--- /dev/null
@@ -0,0 +1,69 @@
+// the way this entity works makes it no use to CSQC, as it removes itself instantly
+
+#ifdef SVQC
+void follow_init()
+{
+       entity src, dst;
+       src = world;
+       dst = world;
+       if(self.killtarget != "")
+               src = find(world, targetname, self.killtarget);
+       if(self.target != "")
+               dst = find(world, targetname, self.target);
+
+       if(!src && !dst)
+       {
+               objerror("follow: could not find target/killtarget");
+               return;
+       }
+
+       if(self.jointtype)
+       {
+               // already done :P entity must stay
+               self.aiment = src;
+               self.enemy = dst;
+       }
+       else if(!src || !dst)
+       {
+               objerror("follow: could not find target/killtarget");
+               return;
+       }
+       else if(self.spawnflags & 1)
+       {
+               // attach
+               if(self.spawnflags & 2)
+               {
+                       setattachment(dst, src, self.message);
+               }
+               else
+               {
+                       attach_sameorigin(dst, src, self.message);
+               }
+
+               dst.solid = SOLID_NOT; // solid doesn't work with attachment
+               remove(self);
+       }
+       else
+       {
+               if(self.spawnflags & 2)
+               {
+                       dst.movetype = MOVETYPE_FOLLOW;
+                       dst.aiment = src;
+                       // dst.punchangle = '0 0 0'; // keep unchanged
+                       dst.view_ofs = dst.origin;
+                       dst.v_angle = dst.angles;
+               }
+               else
+               {
+                       follow_sameorigin(dst, src);
+               }
+
+               remove(self);
+       }
+}
+
+void spawnfunc_misc_follow()
+{
+       InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
+}
+#endif
diff --git a/qcsrc/common/triggers/misc/include.qc b/qcsrc/common/triggers/misc/include.qc
new file mode 100644 (file)
index 0000000..c05f8c3
--- /dev/null
@@ -0,0 +1,6 @@
+#include "include.qh"
+
+#include "corner.qc"
+#include "follow.qc"
+#include "laser.qc"
+#include "teleport_dest.qc"
diff --git a/qcsrc/common/triggers/misc/include.qh b/qcsrc/common/triggers/misc/include.qh
new file mode 100644 (file)
index 0000000..71b2205
--- /dev/null
@@ -0,0 +1,7 @@
+#ifndef TRIGGERS_MISC_INCLUDE_H
+#define TRIGGERS_MISC_INCLUDE_H
+
+#include "corner.qh"
+#include "laser.qh"
+
+#endif
diff --git a/qcsrc/common/triggers/misc/laser.qc b/qcsrc/common/triggers/misc/laser.qc
new file mode 100644 (file)
index 0000000..c67eb32
--- /dev/null
@@ -0,0 +1,392 @@
+#if defined(CSQC)
+       #include "../../../dpdefs/csprogsdefs.qh"
+       #include "../../buffs.qh"
+       #include "../../../csqcmodellib/interpolate.qh"
+       #include "../../../client/main.qh"
+       #include "../../../csqcmodellib/cl_model.qh"
+#elif defined(MENUQC)
+#elif defined(SVQC)
+#endif
+
+#ifdef SVQC
+.float modelscale;
+void misc_laser_aim()
+{
+       vector a;
+       if(self.enemy)
+       {
+               if(self.spawnflags & 2)
+               {
+                       if(self.enemy.origin != self.mangle)
+                       {
+                               self.mangle = self.enemy.origin;
+                               self.SendFlags |= 2;
+                       }
+               }
+               else
+               {
+                       a = vectoangles(self.enemy.origin - self.origin);
+                       a_x = -a_x;
+                       if(a != self.mangle)
+                       {
+                               self.mangle = a;
+                               self.SendFlags |= 2;
+                       }
+               }
+       }
+       else
+       {
+               if(self.angles != self.mangle)
+               {
+                       self.mangle = self.angles;
+                       self.SendFlags |= 2;
+               }
+       }
+       if(self.origin != self.oldorigin)
+       {
+               self.SendFlags |= 1;
+               self.oldorigin = self.origin;
+       }
+}
+
+void misc_laser_init()
+{
+       if(self.target != "")
+               self.enemy = find(world, targetname, self.target);
+}
+
+.entity pusher;
+void misc_laser_think()
+{
+       vector o;
+       entity oldself;
+       entity hitent;
+       vector hitloc;
+
+       self.nextthink = time;
+
+       if(!self.state)
+               return;
+
+       misc_laser_aim();
+
+       if(self.enemy)
+       {
+               o = self.enemy.origin;
+               if (!(self.spawnflags & 2))
+                       o = self.origin + normalize(o - self.origin) * 32768;
+       }
+       else
+       {
+               makevectors(self.mangle);
+               o = self.origin + v_forward * 32768;
+       }
+
+       if(self.dmg || self.enemy.target != "")
+       {
+               traceline(self.origin, o, MOVE_NORMAL, self);
+       }
+       hitent = trace_ent;
+       hitloc = trace_endpos;
+
+       if(self.enemy.target != "") // DETECTOR laser
+       {
+               if(trace_ent.iscreature)
+               {
+                       self.pusher = hitent;
+                       if(!self.count)
+                       {
+                               self.count = 1;
+
+                               oldself = self;
+                               self = self.enemy;
+                               activator = self.pusher;
+                               SUB_UseTargets();
+                               self = oldself;
+                       }
+               }
+               else
+               {
+                       if(self.count)
+                       {
+                               self.count = 0;
+
+                               oldself = self;
+                               self = self.enemy;
+                               activator = self.pusher;
+                               SUB_UseTargets();
+                               self = oldself;
+                       }
+               }
+       }
+
+       if(self.dmg)
+       {
+               if(self.team)
+                       if(((self.spawnflags & 8) == 0) == (self.team != hitent.team))
+                               return;
+               if(hitent.takedamage)
+                       Damage(hitent, self, self, ((self.dmg < 0) ? 100000 : (self.dmg * frametime)), DEATH_HURTTRIGGER, hitloc, '0 0 0');
+       }
+}
+
+float laser_SendEntity(entity to, float fl)
+{
+       WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
+       fl = fl - (fl & 0xF0); // use that bit to indicate finite length laser
+       if(self.spawnflags & 2)
+               fl |= 0x80;
+       if(self.alpha)
+               fl |= 0x40;
+       if(self.scale != 1 || self.modelscale != 1)
+               fl |= 0x20;
+       if(self.spawnflags & 4)
+               fl |= 0x10;
+       WriteByte(MSG_ENTITY, fl);
+       if(fl & 1)
+       {
+               WriteCoord(MSG_ENTITY, self.origin_x);
+               WriteCoord(MSG_ENTITY, self.origin_y);
+               WriteCoord(MSG_ENTITY, self.origin_z);
+       }
+       if(fl & 8)
+       {
+               WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
+               WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
+               WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
+               if(fl & 0x40)
+                       WriteByte(MSG_ENTITY, self.alpha * 255.0);
+               if(fl & 0x20)
+               {
+                       WriteByte(MSG_ENTITY, bound(0, self.scale * 16.0, 255));
+                       WriteByte(MSG_ENTITY, bound(0, self.modelscale * 16.0, 255));
+               }
+               if((fl & 0x80) || !(fl & 0x10)) // effect doesn't need sending if the laser is infinite and has collision testing turned off
+                       WriteShort(MSG_ENTITY, self.cnt + 1);
+       }
+       if(fl & 2)
+       {
+               if(fl & 0x80)
+               {
+                       WriteCoord(MSG_ENTITY, self.enemy.origin_x);
+                       WriteCoord(MSG_ENTITY, self.enemy.origin_y);
+                       WriteCoord(MSG_ENTITY, self.enemy.origin_z);
+               }
+               else
+               {
+                       WriteAngle(MSG_ENTITY, self.mangle_x);
+                       WriteAngle(MSG_ENTITY, self.mangle_y);
+               }
+       }
+       if(fl & 4)
+               WriteByte(MSG_ENTITY, self.state);
+       return 1;
+}
+
+/*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
+Any object touching the beam will be hurt
+Keys:
+"target"
+ spawnfunc_target_position where the laser ends
+"mdl"
+ name of beam end effect to use
+"colormod"
+ color of the beam (default: red)
+"dmg"
+ damage per second (-1 for a laser that kills immediately)
+*/
+void laser_use()
+{
+       self.state = !self.state;
+       self.SendFlags |= 4;
+       misc_laser_aim();
+}
+
+void laser_reset()
+{
+       if(self.spawnflags & 1)
+               self.state = 1;
+       else
+               self.state = 0;
+}
+
+void spawnfunc_misc_laser()
+{
+       if(self.mdl)
+       {
+               if(self.mdl == "none")
+                       self.cnt = -1;
+               else
+               {
+                       self.cnt = particleeffectnum(self.mdl);
+                       if(self.cnt < 0)
+                               if(self.dmg)
+                                       self.cnt = particleeffectnum("laser_deadly");
+               }
+       }
+       else if(!self.cnt)
+       {
+               if(self.dmg)
+                       self.cnt = particleeffectnum("laser_deadly");
+               else
+                       self.cnt = -1;
+       }
+       if(self.cnt < 0)
+               self.cnt = -1;
+
+       if(self.colormod == '0 0 0')
+               if(!self.alpha)
+                       self.colormod = '1 0 0';
+       if(self.message == "")
+               self.message = "saw the light";
+       if (self.message2 == "")
+               self.message2 = "was pushed into a laser by";
+       if(!self.scale)
+               self.scale = 1;
+       if(!self.modelscale)
+               self.modelscale = 1;
+       else if(self.modelscale < 0)
+               self.modelscale = 0;
+       self.think = misc_laser_think;
+       self.nextthink = time;
+       InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
+
+       self.mangle = self.angles;
+
+       Net_LinkEntity(self, false, 0, laser_SendEntity);
+
+       IFTARGETED
+       {
+               self.reset = laser_reset;
+               laser_reset();
+               self.use = laser_use;
+       }
+       else
+               self.state = 1;
+}
+#elif defined(CSQC)
+
+// a laser goes from origin in direction angles
+// it has color 'colormod'
+// and stops when something is in the way
+.int cnt; // end effect
+.vector colormod;
+.int state; // on-off
+.int count; // flags for the laser
+.vector velocity;
+.float alpha;
+.float scale; // scaling factor of the thickness
+.float modelscale; // scaling factor of the dlight
+
+void Draw_Laser()
+{
+       if(!self.state)
+               return;
+       InterpolateOrigin_Do();
+       if(self.count & 0x80)
+       {
+               if(self.count & 0x10)
+               {
+                       trace_endpos = self.velocity;
+                       trace_dphitq3surfaceflags = 0;
+               }
+               else
+                       traceline(self.origin, self.velocity, 0, self);
+       }
+       else
+       {
+               if(self.count & 0x10)
+               {
+                       makevectors(self.angles);
+                       trace_endpos = self.origin + v_forward * 1048576;
+                       trace_dphitq3surfaceflags = Q3SURFACEFLAG_SKY;
+               }
+               else
+               {
+                       makevectors(self.angles);
+                       traceline(self.origin, self.origin + v_forward * 32768, 0, self);
+                       if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY)
+                               trace_endpos = self.origin + v_forward * 1048576;
+               }
+       }
+       if(self.scale != 0)
+       {
+               if(self.alpha)
+               {
+                       Draw_CylindricLine(self.origin, trace_endpos, self.scale, "particles/laserbeam", 0, time * 3, self.colormod, self.alpha, DRAWFLAG_NORMAL, view_origin);
+               }
+               else
+               {
+                       Draw_CylindricLine(self.origin, trace_endpos, self.scale, "particles/laserbeam", 0, time * 3, self.colormod, 0.5, DRAWFLAG_ADDITIVE, view_origin);
+               }
+       }
+       if (!(trace_dphitq3surfaceflags & (Q3SURFACEFLAG_SKY | Q3SURFACEFLAG_NOIMPACT)))
+       {
+               if(self.cnt >= 0)
+                       pointparticles(self.cnt, trace_endpos, trace_plane_normal, drawframetime * 1000);
+               if(self.colormod != '0 0 0' && self.modelscale != 0)
+                       adddynamiclight(trace_endpos + trace_plane_normal * 1, self.modelscale, self.colormod * 5);
+       }
+}
+
+void Ent_Laser()
+{
+       InterpolateOrigin_Undo();
+
+       // 30 bytes, or 13 bytes for just moving
+       int f = ReadByte();
+       self.count = (f & 0xF0);
+
+       if(self.count & 0x80)
+               self.iflags = IFLAG_VELOCITY | IFLAG_ORIGIN;
+       else
+               self.iflags = IFLAG_ANGLES | IFLAG_ORIGIN;
+
+       if(f & 1)
+       {
+               self.origin_x = ReadCoord();
+               self.origin_y = ReadCoord();
+               self.origin_z = ReadCoord();
+               setorigin(self, self.origin);
+       }
+       if(f & 8)
+       {
+               self.colormod_x = ReadByte() / 255.0;
+               self.colormod_y = ReadByte() / 255.0;
+               self.colormod_z = ReadByte() / 255.0;
+               if(f & 0x40)
+                       self.alpha = ReadByte() / 255.0;
+               else
+                       self.alpha = 0;
+               self.scale = 2;
+               self.modelscale = 50;
+               if(f & 0x20)
+               {
+                       self.scale *= ReadByte() / 16.0; // beam radius
+                       self.modelscale *= ReadByte() / 16.0; // dlight radius
+               }
+               if((f & 0x80) || !(f & 0x10))
+                       self.cnt = ReadShort() - 1; // effect number
+               else
+                       self.cnt = 0;
+       }
+       if(f & 2)
+       {
+               if(f & 0x80)
+               {
+                       self.velocity_x = ReadCoord();
+                       self.velocity_y = ReadCoord();
+                       self.velocity_z = ReadCoord();
+               }
+               else
+               {
+                       self.angles_x = ReadAngle();
+                       self.angles_y = ReadAngle();
+               }
+       }
+       if(f & 4)
+               self.state = ReadByte();
+       InterpolateOrigin_Note();
+       self.draw = Draw_Laser;
+}
+#endif
diff --git a/qcsrc/common/triggers/misc/laser.qh b/qcsrc/common/triggers/misc/laser.qh
new file mode 100644 (file)
index 0000000..a77c4c5
--- /dev/null
@@ -0,0 +1,3 @@
+#ifdef CSQC
+void Ent_Laser();
+#endif
diff --git a/qcsrc/common/triggers/misc/teleport_dest.qc b/qcsrc/common/triggers/misc/teleport_dest.qc
new file mode 100644 (file)
index 0000000..b80ce82
--- /dev/null
@@ -0,0 +1,30 @@
+#ifdef SVQC
+
+void spawnfunc_info_teleport_destination (void)
+{
+       self.classname = "info_teleport_destination";
+
+       self.mangle = self.angles;
+       self.angles = '0 0 0';
+
+       //setorigin (self, self.origin + '0 0 27');     // To fix a mappers' habit as old as Quake
+       setorigin (self, self.origin);
+
+       IFTARGETED
+       {
+       }
+       else
+               objerror ("^3Teleport destination without a targetname");
+}
+
+void spawnfunc_misc_teleporter_dest (void)
+{
+       spawnfunc_info_teleport_destination();
+}
+
+void spawnfunc_target_teleporter (void)
+{
+       spawnfunc_info_teleport_destination();
+}
+
+#endif
diff --git a/qcsrc/common/triggers/platforms.qc b/qcsrc/common/triggers/platforms.qc
new file mode 100644 (file)
index 0000000..b837d7c
--- /dev/null
@@ -0,0 +1,235 @@
+void generic_plat_blocked()
+{
+#ifdef SVQC
+       if(self.dmg && other.takedamage != DAMAGE_NO)
+       {
+               if(self.dmgtime2 < time)
+               {
+                       Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
+                       self.dmgtime2 = time + self.dmgtime;
+               }
+
+               // Gib dead/dying stuff
+               if(other.deadflag != DEAD_NO)
+                       Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
+       }
+#endif
+}
+
+void plat_spawn_inside_trigger()
+{
+       entity trigger;
+       vector tmin, tmax;
+
+       trigger = spawn();
+       trigger.touch = plat_center_touch;
+       trigger.movetype = MOVETYPE_NONE;
+       trigger.solid = SOLID_TRIGGER;
+       trigger.enemy = self;
+
+#ifdef CSQC
+       trigger.drawmask = MASK_NORMAL;
+       trigger.trigger_touch = plat_center_touch;
+       trigger.draw = trigger_draw_generic;
+#endif
+
+       tmin = self.absmin + '25 25 0';
+       tmax = self.absmax - '25 25 -8';
+       tmin_z = tmax_z - (self.pos1_z - self.pos2_z + 8);
+       if (self.spawnflags & PLAT_LOW_TRIGGER)
+               tmax_z = tmin_z + 8;
+
+       if (self.size_x <= 50)
+       {
+               tmin_x = (self.mins_x + self.maxs_x) / 2;
+               tmax_x = tmin_x + 1;
+       }
+       if (self.size_y <= 50)
+       {
+               tmin_y = (self.mins_y + self.maxs_y) / 2;
+               tmax_y = tmin_y + 1;
+       }
+
+       if(tmin_x < tmax_x)
+               if(tmin_y < tmax_y)
+                       if(tmin_z < tmax_z)
+                       {
+                               setsize (trigger, tmin, tmax);
+                               return;
+                       }
+
+       // otherwise, something is fishy...
+       remove(trigger);
+       objerror("plat_spawn_inside_trigger: platform has odd size or lip, can't spawn");
+}
+
+void plat_hit_top()
+{
+       sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
+       self.state = 1;
+
+       self.SUB_THINK = plat_go_down;
+       self.SUB_NEXTTHINK = self.SUB_LTIME + 3;
+}
+
+void plat_hit_bottom()
+{
+       sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
+       self.state = 2;
+}
+
+void plat_go_down()
+{
+       sound (self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_NORM);
+       self.state = 3;
+       SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, plat_hit_bottom);
+}
+
+void plat_go_up()
+{
+       sound (self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_NORM);
+       self.state = 4;
+       SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, plat_hit_top);
+}
+
+void plat_center_touch()
+{
+#ifdef SVQC
+       if (!other.iscreature)
+               return;
+
+       if (other.health <= 0)
+               return;
+#elif defined(CSQC)
+       if (!IS_PLAYER(other))
+               return;
+       if(PHYS_DEAD(other))
+               return;
+#endif
+
+       self = self.enemy;
+       if (self.state == 2)
+               plat_go_up ();
+       else if (self.state == 1)
+               self.SUB_NEXTTHINK = self.SUB_LTIME + 1;
+}
+
+void plat_outside_touch()
+{
+#ifdef SVQC
+       if (!other.iscreature)
+               return;
+
+       if (other.health <= 0)
+               return;
+#elif defined(CSQC)
+       if (!IS_PLAYER(other))
+               return;
+#endif
+
+       self = self.enemy;
+       if (self.state == 1)
+               plat_go_down ();
+}
+
+void plat_trigger_use()
+{
+#ifdef SVQC
+       if (self.think)
+               return;         // already activated
+#elif defined(CSQC)
+       if(self.move_think)
+               return;
+#endif
+       plat_go_down();
+}
+
+
+void plat_crush()
+{
+       if((self.spawnflags & 4) && (other.takedamage != DAMAGE_NO))
+       { // KIll Kill Kill!!
+#ifdef SVQC
+               Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
+#endif
+       }
+       else
+       {
+#ifdef SVQC
+               if((self.dmg) && (other.takedamage != DAMAGE_NO))
+               {   // Shall we bite?
+                       Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
+                       // Gib dead/dying stuff
+                       if(other.deadflag != DEAD_NO)
+                               Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
+               }
+#endif
+
+               if (self.state == 4)
+                       plat_go_down ();
+               else if (self.state == 3)
+                       plat_go_up ();
+       // when in other states, then the plat_crush event came delayed after
+       // plat state already had changed
+       // this isn't a bug per se!
+       }
+}
+
+void plat_use()
+{
+       self.use = func_null;
+       if (self.state != 4)
+               objerror ("plat_use: not in up state");
+       plat_go_down();
+}
+
+.string sound1, sound2;
+
+void plat_reset()
+{
+       IFTARGETED
+       {
+               setorigin (self, self.pos1);
+               self.state = 4;
+               self.use = plat_use;
+       }
+       else
+       {
+               setorigin (self, self.pos2);
+               self.state = 2;
+               self.use = plat_trigger_use;
+       }
+
+#ifdef SVQC
+       self.SendFlags |= SF_TRIGGER_RESET;
+#endif
+}
+
+.float platmovetype_start_default, platmovetype_end_default;
+bool set_platmovetype(entity e, string s)
+{
+       // sets platmovetype_start and platmovetype_end based on a string consisting of two values
+
+       int n = tokenize_console(s);
+       if(n > 0)
+               e.platmovetype_start = stof(argv(0));
+       else
+               e.platmovetype_start = 0;
+
+       if(n > 1)
+               e.platmovetype_end = stof(argv(1));
+       else
+               e.platmovetype_end = e.platmovetype_start;
+
+       if(n > 2)
+               if(argv(2) == "force")
+                       return true; // no checking, return immediately
+
+       if(!cubic_speedfunc_is_sane(e.platmovetype_start, e.platmovetype_end))
+       {
+               objerror("Invalid platform move type; platform would go in reverse, which is not allowed.");
+               return false;
+       }
+
+       return true;
+}
diff --git a/qcsrc/common/triggers/platforms.qh b/qcsrc/common/triggers/platforms.qh
new file mode 100644 (file)
index 0000000..e0a581b
--- /dev/null
@@ -0,0 +1,16 @@
+#ifndef PLATFORMS_H
+#define PLATFORMS_H
+
+.float dmgtime2;
+
+void() plat_center_touch;
+void() plat_outside_touch;
+void() plat_trigger_use;
+void() plat_go_up;
+void() plat_go_down;
+void() plat_crush;
+const float PLAT_LOW_TRIGGER = 1;
+
+.float dmg;
+
+#endif
diff --git a/qcsrc/common/triggers/subs.qc b/qcsrc/common/triggers/subs.qc
new file mode 100644 (file)
index 0000000..f21455d
--- /dev/null
@@ -0,0 +1,392 @@
+void SUB_NullThink(void) { }
+
+void()  SUB_CalcMoveDone;
+void() SUB_CalcAngleMoveDone;
+//void() SUB_UseTargets;
+
+/*
+==================
+SUB_Remove
+
+Remove self
+==================
+*/
+void SUB_Remove()
+{
+       remove (self);
+}
+
+/*
+==================
+SUB_Friction
+
+Applies some friction to self
+==================
+*/
+.float friction;
+void SUB_Friction (void)
+{
+       self.SUB_NEXTTHINK = time;
+       if(self.SUB_FLAGS & FL_ONGROUND)
+               self.SUB_VELOCITY = self.SUB_VELOCITY * (1 - frametime * self.friction);
+}
+
+/*
+==================
+SUB_VanishOrRemove
+
+Makes client invisible or removes non-client
+==================
+*/
+void SUB_VanishOrRemove (entity ent)
+{
+       if (IS_CLIENT(ent))
+       {
+               // vanish
+               ent.alpha = -1;
+               ent.effects = 0;
+#ifdef SVQC
+               ent.glow_size = 0;
+               ent.pflags = 0;
+#endif
+       }
+       else
+       {
+               // remove
+               remove (ent);
+       }
+}
+
+void SUB_SetFade_Think (void)
+{
+       if(self.alpha == 0)
+               self.alpha = 1;
+       self.SUB_THINK = SUB_SetFade_Think;
+       self.SUB_NEXTTHINK = time;
+       self.alpha -= frametime * self.fade_rate;
+       if (self.alpha < 0.01)
+               SUB_VanishOrRemove(self);
+       else
+               self.SUB_NEXTTHINK = time;
+}
+
+/*
+==================
+SUB_SetFade
+
+Fade 'ent' out when time >= 'when'
+==================
+*/
+void SUB_SetFade (entity ent, float when, float fading_time)
+{
+       ent.fade_rate = 1/fading_time;
+       ent.SUB_THINK = SUB_SetFade_Think;
+       ent.SUB_NEXTTHINK = when;
+}
+
+/*
+=============
+SUB_CalcMove
+
+calculate self.SUB_VELOCITY and self.SUB_NEXTTHINK to reach dest from
+self.SUB_ORIGIN traveling at speed
+===============
+*/
+void SUB_CalcMoveDone (void)
+{
+       // After moving, set origin to exact final destination
+
+       SUB_SETORIGIN (self, self.finaldest);
+       self.SUB_VELOCITY = '0 0 0';
+       self.SUB_NEXTTHINK = -1;
+       if (self.think1)
+               self.think1 ();
+}
+
+.float platmovetype_turn;
+void SUB_CalcMove_controller_think (void)
+{
+       entity oldself;
+       float traveltime;
+       float phasepos;
+       float nexttick;
+       vector delta;
+       vector delta2;
+       vector veloc;
+       vector angloc;
+       vector nextpos;
+       delta = self.destvec;
+       delta2 = self.destvec2;
+       if(time < self.animstate_endtime)
+       {
+               nexttick = time + PHYS_INPUT_FRAMETIME;
+
+               traveltime = self.animstate_endtime - self.animstate_starttime;
+               phasepos = (nexttick - self.animstate_starttime) / traveltime; // range: [0, 1]
+               phasepos = cubic_speedfunc(self.platmovetype_start, self.platmovetype_end, phasepos);
+               nextpos = self.origin + (delta * phasepos) + (delta2 * phasepos * phasepos);
+               // derivative: delta + 2 * delta2 * phasepos (e.g. for angle positioning)
+
+               if(self.owner.platmovetype_turn)
+               {
+                       vector destangle;
+                       destangle = delta + 2 * delta2 * phasepos;
+                       destangle = vectoangles(destangle);
+                       destangle_x = -destangle_x; // flip up / down orientation
+
+                       // take the shortest distance for the angles
+                       SUB_ANGLES(self.owner)_x -= 360 * floor((SUB_ANGLES(self.owner)_x - destangle_x) / 360 + 0.5);
+                       SUB_ANGLES(self.owner)_y -= 360 * floor((SUB_ANGLES(self.owner)_y - destangle_y) / 360 + 0.5);
+                       SUB_ANGLES(self.owner)_z -= 360 * floor((SUB_ANGLES(self.owner)_z - destangle_z) / 360 + 0.5);
+                       angloc = destangle - SUB_ANGLES(self.owner);
+                       angloc = angloc * (1 / PHYS_INPUT_FRAMETIME); // so it arrives for the next frame
+                       self.owner.SUB_AVELOCITY = angloc;
+               }
+               if(nexttick < self.animstate_endtime)
+                       veloc = nextpos - self.owner.SUB_ORIGIN;
+               else
+                       veloc = self.finaldest - self.owner.SUB_ORIGIN;
+               veloc = veloc * (1 / PHYS_INPUT_FRAMETIME); // so it arrives for the next frame
+
+               self.owner.SUB_VELOCITY = veloc;
+               self.nextthink = nexttick;
+       }
+       else
+       {
+               // derivative: delta + 2 * delta2 (e.g. for angle positioning)
+               oldself = self;
+               self.owner.SUB_THINK = self.think1;
+               self = self.owner;
+               remove(oldself);
+               self.SUB_THINK();
+       }
+}
+
+void SUB_CalcMove_controller_setbezier (entity controller, vector org, vector control, vector destin)
+{
+       // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + destin * t * t
+       // 2 * control * t - 2 * control * t * t + destin * t * t
+       // 2 * control * t + (destin - 2 * control) * t * t
+
+       setorigin(controller, org);
+       control -= org;
+       destin -= org;
+
+       controller.destvec = 2 * control; // control point
+       controller.destvec2 = destin - 2 * control; // quadratic part required to reach end point
+       // also: initial d/dphasepos origin = 2 * control, final speed = 2 * (destin - control)
+}
+
+void SUB_CalcMove_controller_setlinear (entity controller, vector org, vector destin)
+{
+       // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + destin * t * t
+       // 2 * control * t - 2 * control * t * t + destin * t * t
+       // 2 * control * t + (destin - 2 * control) * t * t
+
+       setorigin(controller, org);
+       destin -= org;
+
+       controller.destvec = destin; // end point
+       controller.destvec2 = '0 0 0';
+}
+
+float TSPEED_TIME = -1;
+float TSPEED_LINEAR = 0;
+float TSPEED_START = 1;
+float TSPEED_END = 2;
+// TODO average too?
+
+void SUB_CalcMove_Bezier (vector tcontrol, vector tdest, float tspeedtype, float tspeed, void() func)
+{
+       float   traveltime;
+       entity controller;
+
+       if (!tspeed)
+               objerror ("No speed is defined!");
+
+       self.think1 = func;
+       self.finaldest = tdest;
+       self.SUB_THINK = SUB_CalcMoveDone;
+
+       switch(tspeedtype)
+       {
+               default:
+               case TSPEED_START:
+                       traveltime = 2 * vlen(tcontrol - self.SUB_ORIGIN) / tspeed;
+                       break;
+               case TSPEED_END:
+                       traveltime = 2 * vlen(tcontrol - tdest)       / tspeed;
+                       break;
+               case TSPEED_LINEAR:
+                       traveltime = vlen(tdest - self.SUB_ORIGIN)        / tspeed;
+                       break;
+               case TSPEED_TIME:
+                       traveltime = tspeed;
+                       break;
+       }
+
+       if (traveltime < 0.1) // useless anim
+       {
+               self.SUB_VELOCITY = '0 0 0';
+               self.SUB_NEXTTHINK = self.SUB_LTIME + 0.1;
+               return;
+       }
+
+       controller = spawn();
+       controller.classname = "SUB_CalcMove_controller";
+       controller.owner = self;
+       controller.platmovetype = self.platmovetype;
+       controller.platmovetype_start = self.platmovetype_start;
+       controller.platmovetype_end = self.platmovetype_end;
+       SUB_CalcMove_controller_setbezier(controller, self.SUB_ORIGIN, tcontrol, tdest);
+       controller.finaldest = (tdest + '0 0 0.125'); // where do we want to end? Offset to overshoot a bit.
+       controller.animstate_starttime = time;
+       controller.animstate_endtime = time + traveltime;
+       controller.think = SUB_CalcMove_controller_think;
+       controller.think1 = self.SUB_THINK;
+
+       // the thinking is now done by the controller
+       self.SUB_THINK = SUB_NullThink; // for PushMove
+       self.SUB_NEXTTHINK = self.SUB_LTIME + traveltime;
+
+       // invoke controller
+       self = controller;
+       self.think();
+       self = self.owner;
+}
+
+void SUB_CalcMove (vector tdest, float tspeedtype, float tspeed, void() func)
+{
+       vector  delta;
+       float   traveltime;
+
+       if (!tspeed)
+               objerror ("No speed is defined!");
+
+       self.think1 = func;
+       self.finaldest = tdest;
+       self.SUB_THINK = SUB_CalcMoveDone;
+
+       if (tdest == self.SUB_ORIGIN)
+       {
+               self.SUB_VELOCITY = '0 0 0';
+               self.SUB_NEXTTHINK = self.SUB_LTIME + 0.1;
+               return;
+       }
+
+       delta = tdest - self.SUB_ORIGIN;
+
+       switch(tspeedtype)
+       {
+               default:
+               case TSPEED_START:
+               case TSPEED_END:
+               case TSPEED_LINEAR:
+                       traveltime = vlen (delta) / tspeed;
+                       break;
+               case TSPEED_TIME:
+                       traveltime = tspeed;
+                       break;
+       }
+
+       // Very short animations don't really show off the effect
+       // of controlled animation, so let's just use linear movement.
+       // Alternatively entities can choose to specify non-controlled movement.
+        // The only currently implemented alternative movement is linear (value 1)
+       if (traveltime < 0.15 || (self.platmovetype_start == 1 && self.platmovetype_end == 1)) // is this correct?
+       {
+               self.SUB_VELOCITY = delta * (1/traveltime);     // QuakeC doesn't allow vector/float division
+               self.SUB_NEXTTHINK = self.SUB_LTIME + traveltime;
+               return;
+       }
+
+       // now just run like a bezier curve...
+       SUB_CalcMove_Bezier((self.SUB_ORIGIN + tdest) * 0.5, tdest, tspeedtype, tspeed, func);
+}
+
+void SUB_CalcMoveEnt (entity ent, vector tdest, float tspeedtype, float tspeed, void() func)
+{
+       entity  oldself;
+
+       oldself = self;
+       self = ent;
+
+       SUB_CalcMove (tdest, tspeedtype, tspeed, func);
+
+       self = oldself;
+}
+
+/*
+=============
+SUB_CalcAngleMove
+
+calculate self.SUB_AVELOCITY and self.SUB_NEXTTHINK to reach destangle from
+self.angles rotating
+
+The calling function should make sure self.SUB_THINK is valid
+===============
+*/
+void SUB_CalcAngleMoveDone (void)
+{
+       // After rotating, set angle to exact final angle
+       self.angles = self.finalangle;
+       self.SUB_AVELOCITY = '0 0 0';
+       self.SUB_NEXTTHINK = -1;
+       if (self.think1)
+               self.think1 ();
+}
+
+// FIXME: I fixed this function only for rotation around the main axes
+void SUB_CalcAngleMove (vector destangle, float tspeedtype, float tspeed, void() func)
+{
+       vector  delta;
+       float   traveltime;
+
+       if (!tspeed)
+               objerror ("No speed is defined!");
+
+       // take the shortest distance for the angles
+       self.angles_x -= 360 * floor((self.angles_x - destangle_x) / 360 + 0.5);
+       self.angles_y -= 360 * floor((self.angles_y - destangle_y) / 360 + 0.5);
+       self.angles_z -= 360 * floor((self.angles_z - destangle_z) / 360 + 0.5);
+       delta = destangle - self.angles;
+
+       switch(tspeedtype)
+       {
+               default:
+               case TSPEED_START:
+               case TSPEED_END:
+               case TSPEED_LINEAR:
+                       traveltime = vlen (delta) / tspeed;
+                       break;
+               case TSPEED_TIME:
+                       traveltime = tspeed;
+                       break;
+       }
+
+       self.think1 = func;
+       self.finalangle = destangle;
+       self.SUB_THINK = SUB_CalcAngleMoveDone;
+
+       if (traveltime < 0.1)
+       {
+               self.SUB_AVELOCITY = '0 0 0';
+               self.SUB_NEXTTHINK = self.SUB_LTIME + 0.1;
+               return;
+       }
+
+       self.SUB_AVELOCITY = delta * (1 / traveltime);
+       self.SUB_NEXTTHINK = self.SUB_LTIME + traveltime;
+}
+
+void SUB_CalcAngleMoveEnt (entity ent, vector destangle, float tspeedtype, float tspeed, void() func)
+{
+       entity  oldself;
+
+       oldself = self;
+       self = ent;
+
+       SUB_CalcAngleMove (destangle, tspeedtype, tspeed, func);
+
+       self = oldself;
+}
diff --git a/qcsrc/common/triggers/subs.qh b/qcsrc/common/triggers/subs.qh
new file mode 100644 (file)
index 0000000..ea8ef30
--- /dev/null
@@ -0,0 +1,108 @@
+#ifndef SUBS_H
+#define SUBS_H
+
+#ifdef SVQC
+
+       #define SUB_ANGLES(s)           (s).angles
+       #define SUB_VELOCITY            velocity
+       #define SUB_AVELOCITY           avelocity
+       #define SUB_ORIGIN                      origin
+       #define SUB_SETORIGIN(s,v)      setorigin((s), (v))
+       #define SUB_NEXTTHINK           nextthink
+       #define SUB_THINK                       think
+       #define SUB_LTIME                       ltime
+       #define SUB_FLAGS                       flags
+
+#elif defined(CSQC)
+
+       void _Movetype_LinkEdict(float touch_triggers);
+
+       #define SUB_ANGLES(s)   (s).move_angles
+       #define SUB_VELOCITY    move_velocity
+       #define SUB_AVELOCITY   move_avelocity
+       #define SUB_ORIGIN              move_origin
+       #define SUB_NEXTTHINK   move_nextthink
+       #define SUB_THINK               move_think
+       #define SUB_LTIME               move_ltime
+       #define SUB_FLAGS               move_flags
+
+       void SUB_SETORIGIN(entity s, vector v)
+       {
+               s.move_origin = v;
+               entity oldself = self;
+               self = s;
+               _Movetype_LinkEdict(true);
+               self = oldself;
+       }
+
+#endif
+
+void SUB_Remove();
+void SUB_SetFade (entity ent, float when, float fading_time);
+void SUB_VanishOrRemove (entity ent);
+
+.vector                finaldest, finalangle;          //plat.qc stuff
+.void()                think1;
+.float state;
+.float         t_length, t_width;
+
+.vector destvec;
+.vector destvec2;
+
+// player animation state
+.float animstate_startframe;
+.float animstate_numframes;
+.float animstate_framerate;
+.float animstate_starttime;
+.float animstate_endtime;
+.float animstate_override;
+.float animstate_looping;
+
+.float delay;
+.float wait;
+.float lip;
+.float speed;
+.float sounds;
+.string  platmovetype;
+.float platmovetype_start, platmovetype_end;
+
+entity activator;
+
+.string killtarget;
+
+.vector        pos1, pos2;
+.vector        mangle;
+
+.string target2;
+.string target3;
+.string target4;
+.string curvetarget;
+.float target_random;
+.float trigger_reverse;
+
+// Keys player is holding
+.float itemkeys;
+// message delay for func_door locked by keys and key locks
+// this field is used on player entities
+.float key_door_messagetime;
+
+.vector dest1, dest2;
+
+#ifdef CSQC
+// this stuff is defined in the server side engine VM, so we must define it separately here
+.float takedamage;
+const float DAMAGE_NO  = 0;
+const float DAMAGE_YES = 1;
+const float DAMAGE_AIM = 2;
+
+float  STATE_TOP               = 0;
+float  STATE_BOTTOM    = 1;
+float  STATE_UP                = 2;
+float  STATE_DOWN              = 3;
+
+.string                noise, noise1, noise2, noise3;  // contains names of wavs to play
+
+.float         max_health;             // players maximum health is stored here
+#endif
+
+#endif
diff --git a/qcsrc/common/triggers/target/changelevel.qc b/qcsrc/common/triggers/target/changelevel.qc
new file mode 100644 (file)
index 0000000..1ec8cc9
--- /dev/null
@@ -0,0 +1,18 @@
+#ifdef SVQC
+.string chmap, gametype;
+void spawnfunc_target_changelevel_use()
+{
+       if(self.gametype != "")
+               MapInfo_SwitchGameType(MapInfo_Type_FromString(self.gametype));
+
+       if (self.chmap == "")
+               localcmd("endmatch\n");
+       else
+               localcmd(strcat("changelevel ", self.chmap, "\n"));
+}
+
+void spawnfunc_target_changelevel()
+{
+       self.use = spawnfunc_target_changelevel_use;
+}
+#endif
diff --git a/qcsrc/common/triggers/target/include.qc b/qcsrc/common/triggers/target/include.qc
new file mode 100644 (file)
index 0000000..c53ea6d
--- /dev/null
@@ -0,0 +1,8 @@
+#include "include.qh"
+
+#include "changelevel.qc"
+#include "location.qc"
+#include "music.qc"
+#include "spawn.qc"
+#include "speaker.qc"
+#include "voicescript.qc"
diff --git a/qcsrc/common/triggers/target/include.qh b/qcsrc/common/triggers/target/include.qh
new file mode 100644 (file)
index 0000000..4e44b97
--- /dev/null
@@ -0,0 +1,6 @@
+#ifndef TRIGGERS_TARGET_INCLUDE_H
+#define TRIGGERS_TARGET_INCLUDE_H
+
+#include "music.qh"
+
+#endif
diff --git a/qcsrc/common/triggers/target/location.qc b/qcsrc/common/triggers/target/location.qc
new file mode 100644 (file)
index 0000000..1430cab
--- /dev/null
@@ -0,0 +1,14 @@
+#ifdef SVQC
+void spawnfunc_target_location()
+{
+    self.classname = "target_location";
+    // location name in netname
+    // eventually support: count, teamgame selectors, line of sight?
+}
+
+void spawnfunc_info_location()
+{
+    self.classname = "target_location";
+    self.message = self.netname;
+}
+#endif
diff --git a/qcsrc/common/triggers/target/music.qc b/qcsrc/common/triggers/target/music.qc
new file mode 100644 (file)
index 0000000..136288b
--- /dev/null
@@ -0,0 +1,330 @@
+#if defined(CSQC)
+#elif defined(MENUQC)
+#elif defined(SVQC)
+       #include "../../../dpdefs/progsdefs.qh"
+    #include "../../../dpdefs/dpextensions.qh"
+    #include "../../constants.qh"
+    #include "../../../server/constants.qh"
+    #include "../../../server/defs.qh"
+#endif
+
+#ifdef SVQC
+
+// values:
+//   volume
+//   noise
+//   targetname
+//   lifetime
+//   fade_time
+//   fade_rate
+// when triggered, the music is overridden for activator until lifetime (or forever, if lifetime is 0)
+// when targetname is not set, THIS ONE is default
+void target_music_sendto(float to, float is)
+{
+       WriteByte(to, SVC_TEMPENTITY);
+       WriteByte(to, TE_CSQC_TARGET_MUSIC);
+       WriteShort(to, num_for_edict(self));
+       WriteByte(to, self.volume * 255.0 * is);
+       WriteByte(to, self.fade_time * 16.0);
+       WriteByte(to, self.fade_rate * 16.0);
+       WriteByte(to, self.lifetime);
+       WriteString(to, self.noise);
+}
+void target_music_reset()
+{
+       if(self.targetname == "")
+               target_music_sendto(MSG_ALL, 1);
+}
+void target_music_use()
+{
+       if(!activator)
+               return;
+       if(IS_REAL_CLIENT(activator))
+       {
+               msg_entity = activator;
+               target_music_sendto(MSG_ONE, 1);
+       }
+       entity head;
+       FOR_EACH_SPEC(head) if(head.enemy == activator) { msg_entity = head; target_music_sendto(MSG_ONE, 1); }
+}
+void spawnfunc_target_music()
+{
+       self.use = target_music_use;
+       self.reset = target_music_reset;
+       if(!self.volume)
+               self.volume = 1;
+       if(self.targetname == "")
+               target_music_sendto(MSG_INIT, 1);
+       else
+               target_music_sendto(MSG_INIT, 0);
+}
+void TargetMusic_RestoreGame()
+{
+       for(self = world; (self = find(self, classname, "target_music")); )
+       {
+               if(self.targetname == "")
+                       target_music_sendto(MSG_INIT, 1);
+               else
+                       target_music_sendto(MSG_INIT, 0);
+       }
+}
+// values:
+//   volume
+//   noise
+//   targetname
+//   fade_time
+// spawnflags:
+//   1 = START_OFF
+// when triggered, it is disabled/enabled for everyone
+float trigger_music_SendEntity(entity to, float sf)
+{
+       WriteByte(MSG_ENTITY, ENT_CLIENT_TRIGGER_MUSIC);
+       sf &= ~0x80;
+       if(self.cnt)
+               sf |= 0x80;
+       WriteByte(MSG_ENTITY, sf);
+       if(sf & 4)
+       {
+               WriteCoord(MSG_ENTITY, self.origin.x);
+               WriteCoord(MSG_ENTITY, self.origin.y);
+               WriteCoord(MSG_ENTITY, self.origin.z);
+       }
+       if(sf & 1)
+       {
+               if(self.model != "null")
+               {
+                       WriteShort(MSG_ENTITY, self.modelindex);
+                       WriteCoord(MSG_ENTITY, self.mins.x);
+                       WriteCoord(MSG_ENTITY, self.mins.y);
+                       WriteCoord(MSG_ENTITY, self.mins.z);
+                       WriteCoord(MSG_ENTITY, self.maxs.x);
+                       WriteCoord(MSG_ENTITY, self.maxs.y);
+                       WriteCoord(MSG_ENTITY, self.maxs.z);
+               }
+               else
+               {
+                       WriteShort(MSG_ENTITY, 0);
+                       WriteCoord(MSG_ENTITY, self.maxs.x);
+                       WriteCoord(MSG_ENTITY, self.maxs.y);
+                       WriteCoord(MSG_ENTITY, self.maxs.z);
+               }
+               WriteByte(MSG_ENTITY, self.volume * 255.0);
+               WriteByte(MSG_ENTITY, self.fade_time * 16.0);
+               WriteByte(MSG_ENTITY, self.fade_rate * 16.0);
+               WriteString(MSG_ENTITY, self.noise);
+       }
+       return 1;
+}
+void trigger_music_reset()
+{
+       self.cnt = !(self.spawnflags & 1);
+       self.SendFlags |= 0x80;
+}
+void trigger_music_use()
+{
+       self.cnt = !self.cnt;
+       self.SendFlags |= 0x80;
+}
+void spawnfunc_trigger_music()
+{
+       if(self.model != "")
+               setmodel(self, self.model);
+       if(!self.volume)
+               self.volume = 1;
+       if(!self.modelindex)
+       {
+               setorigin(self, self.origin + self.mins);
+               setsize(self, '0 0 0', self.maxs - self.mins);
+       }
+       trigger_music_reset();
+
+       self.use = trigger_music_use;
+       self.reset = trigger_music_reset;
+
+       Net_LinkEntity(self, false, 0, trigger_music_SendEntity);
+}
+#elif defined(CSQC)
+
+void TargetMusic_Advance()
+{
+       // run AFTER all the thinks!
+       entity best, e;
+       float vol, vol0;
+       best = music_default;
+       if(music_target && time < music_target.lifetime)
+               best = music_target;
+       if(music_trigger)
+               best = music_trigger;
+       for(e = world; (e = findfloat(e, enttype, ENT_CLIENT_TRIGGER_MUSIC)); ) if(e.noise)
+       {
+               vol0 = e.lastvol;
+               if(getsoundtime(e, CH_BGM_SINGLE) < 0)
+               {
+                       vol0 = -1;
+               }
+               if(e == best)
+               {
+                       // increase volume
+                       if(e.fade_time > 0)
+                               e.state = bound(0, e.state + frametime / e.fade_time, 1);
+                       else
+                               e.state = 1;
+               }
+               else
+               {
+                       // decrease volume
+                       if(e.fade_rate > 0)
+                               e.state = bound(0, e.state - frametime / e.fade_rate, 1);
+                       else
+                               e.state = 0;
+               }
+               vol = e.state * e.volume * autocvar_bgmvolume;
+               if(vol != vol0)
+               {
+                       if(vol0 < 0)
+                               sound(e, CH_BGM_SINGLE, e.noise, vol, ATTEN_NONE); // restart
+                       else
+                               sound(e, CH_BGM_SINGLE, "", vol, ATTEN_NONE);
+                       e.lastvol = vol;
+               }
+       }
+       music_trigger = world;
+
+       if(best)
+               bgmtime = getsoundtime(best, CH_BGM_SINGLE);
+       else
+               bgmtime = gettime(GETTIME_CDTRACK);
+}
+
+void Net_TargetMusic()
+{
+       int id = ReadShort();
+       float vol = ReadByte() / 255.0;
+       float fai = ReadByte() / 16.0;
+       float fao = ReadByte() / 16.0;
+       float tim = ReadByte();
+       string noi = ReadString();
+
+       entity e;
+       for(e = world; (e = findfloat(e, enttype, ENT_CLIENT_TRIGGER_MUSIC)); )
+       {
+               if(e.count == id)
+                       break;
+       }
+       if(!e)
+       {
+               e = spawn();
+               e.enttype = ENT_CLIENT_TRIGGER_MUSIC;
+               e.count = id;
+       }
+       if(e.noise != noi)
+       {
+               if(e.noise)
+                       strunzone(e.noise);
+               e.noise = strzone(noi);
+               precache_sound(e.noise);
+               sound(e, CH_BGM_SINGLE, e.noise, 0, ATTEN_NONE);
+               if(getsoundtime(e, CH_BGM_SINGLE) < 0)
+               {
+                       dprintf("Cannot initialize sound %s\n", e.noise);
+                       strunzone(e.noise);
+                       e.noise = string_null;
+               }
+       }
+       e.volume = vol;
+       e.fade_time = fai;
+       e.fade_rate = fao;
+       if(vol > 0)
+       {
+               if(tim == 0)
+               {
+                       music_default = e;
+                       if(!music_disabled)
+                       {
+                               e.state = 2;
+                               cvar_settemp("music_playlist_index", "-1"); // don't use playlists
+                               localcmd("cd stop\n"); // just in case
+                               music_disabled = 1;
+                       }
+               }
+               else
+               {
+                       music_target = e;
+                       e.lifetime = time + tim;
+               }
+       }
+}
+
+void Ent_TriggerMusic_Think()
+{
+       if(WarpZoneLib_BoxTouchesBrush(view_origin, view_origin, self, world))
+       {
+               music_trigger = self;
+       }
+       self.nextthink = time;
+}
+
+void Ent_TriggerMusic_Remove()
+{
+       if(self.noise)
+               strunzone(self.noise);
+       self.noise = string_null;
+}
+
+void Ent_ReadTriggerMusic()
+{
+       int f = ReadByte();
+       if(f & 4)
+       {
+               self.origin_x = ReadCoord();
+               self.origin_y = ReadCoord();
+               self.origin_z = ReadCoord();
+       }
+       if(f & 1)
+       {
+               self.modelindex = ReadShort();
+               if(self.modelindex)
+               {
+                       self.mins_x = ReadCoord();
+                       self.mins_y = ReadCoord();
+                       self.mins_z = ReadCoord();
+                       self.maxs_x = ReadCoord();
+                       self.maxs_y = ReadCoord();
+                       self.maxs_z = ReadCoord();
+               }
+               else
+               {
+                       self.mins    = '0 0 0';
+                       self.maxs_x = ReadCoord();
+                       self.maxs_y = ReadCoord();
+                       self.maxs_z = ReadCoord();
+               }
+
+               self.volume = ReadByte() / 255.0;
+               self.fade_time = ReadByte() / 16.0;
+               self.fade_rate = ReadByte() / 16.0;
+               string s = self.noise;
+               if(self.noise)
+                       strunzone(self.noise);
+               self.noise = strzone(ReadString());
+               if(self.noise != s)
+               {
+                       precache_sound(self.noise);
+                       sound(self, CH_BGM_SINGLE, self.noise, 0, ATTEN_NONE);
+                       if(getsoundtime(self, CH_BGM_SINGLE) < 0)
+                       {
+                               dprintf("Cannot initialize sound %s\n", self.noise);
+                               strunzone(self.noise);
+                               self.noise = string_null;
+                       }
+               }
+       }
+
+       setorigin(self, self.origin);
+       setsize(self, self.mins, self.maxs);
+       self.cnt = 1;
+       self.think = Ent_TriggerMusic_Think;
+       self.nextthink = time;
+}
+
+#endif
diff --git a/qcsrc/common/triggers/target/music.qh b/qcsrc/common/triggers/target/music.qh
new file mode 100644 (file)
index 0000000..8d014c8
--- /dev/null
@@ -0,0 +1,27 @@
+#ifndef TARGET_MUSIC_H
+#define TARGET_MUSIC_H
+
+.float lifetime;
+
+#ifdef CSQC
+float music_disabled;
+entity music_default;
+entity music_target;
+entity music_trigger;
+// FIXME also control bgmvolume here, to not require a target_music for the default track.
+
+.int state;
+.float lastvol;
+
+void TargetMusic_Advance();
+
+void Net_TargetMusic();
+
+void Ent_TriggerMusic_Think();
+
+void Ent_TriggerMusic_Remove();
+
+void Ent_ReadTriggerMusic();
+#endif
+
+#endif
diff --git a/qcsrc/common/triggers/target/spawn.qc b/qcsrc/common/triggers/target/spawn.qc
new file mode 100644 (file)
index 0000000..7b18355
--- /dev/null
@@ -0,0 +1,350 @@
+#if defined(CSQC)
+#elif defined(MENUQC)
+#elif defined(SVQC)
+       #include "../../../dpdefs/progsdefs.qh"
+    #include "../../../dpdefs/dpextensions.qh"
+    #include "../../util.qh"
+    #include "../../../server/defs.qh"
+#endif
+
+#ifdef SVQC
+
+// spawner entity
+// "classname" "target_spawn"
+// "message" "fieldname value fieldname value ..."
+// "spawnflags"
+//   1 = call the spawn function
+//   2 = trigger on map load
+
+float target_spawn_initialized;
+.void() target_spawn_spawnfunc;
+float target_spawn_spawnfunc_field;
+.entity target_spawn_activator;
+.float target_spawn_id;
+float target_spawn_count;
+
+void target_spawn_helper_setmodel()
+{
+       setmodel(self, self.model);
+}
+
+void target_spawn_helper_setsize()
+{
+       setsize(self, self.mins, self.maxs);
+}
+
+void target_spawn_edit_entity(entity e, string msg, entity kt, entity t2, entity t3, entity t4, entity act)
+{
+       float i, n, valuefieldpos;
+       string key, value, valuefield, valueoffset, valueoffsetrandom;
+       entity valueent;
+       vector data, data2;
+       entity oldself;
+       entity oldactivator;
+
+       n = tokenize_console(msg);
+
+       for(i = 0; i < n-1; i += 2)
+       {
+               key = argv(i);
+               value = argv(i+1);
+               if(key == "$")
+               {
+                       data.x = -1;
+                       data.y = FIELD_STRING;
+               }
+               else
+               {
+                       data = stov(db_get(TemporaryDB, strcat("/target_spawn/field/", key)));
+                       if(data.y == 0) // undefined field, i.e., invalid type
+                       {
+                               print("target_spawn: invalid/unknown entity key ", key, " specified, ignored!\n");
+                               continue;
+                       }
+               }
+               if(substring(value, 0, 1) == "$")
+               {
+                       value = substring(value, 1, strlen(value) - 1);
+                       if(substring(value, 0, 1) == "$")
+                       {
+                               // deferred replacement
+                               // do nothing
+                               // useful for creating target_spawns with this!
+                       }
+                       else
+                       {
+                               // replace me!
+                               valuefieldpos = strstrofs(value, "+", 0);
+                               valueoffset = "";
+                               if(valuefieldpos != -1)
+                               {
+                                       valueoffset = substring(value, valuefieldpos + 1, strlen(value) - valuefieldpos - 1);
+                                       value = substring(value, 0, valuefieldpos);
+                               }
+
+                               valuefieldpos = strstrofs(valueoffset, "+", 0);
+                               valueoffsetrandom = "";
+                               if(valuefieldpos != -1)
+                               {
+                                       valueoffsetrandom = substring(valueoffset, valuefieldpos + 1, strlen(valueoffset) - valuefieldpos - 1);
+                                       valueoffset = substring(valueoffset, 0, valuefieldpos);
+                               }
+
+                               valuefieldpos = strstrofs(value, ".", 0);
+                               valuefield = "";
+                               if(valuefieldpos != -1)
+                               {
+                                       valuefield = substring(value, valuefieldpos + 1, strlen(value) - valuefieldpos - 1);
+                                       value = substring(value, 0, valuefieldpos);
+                               }
+
+                               if(value == "self")
+                               {
+                                       valueent = self;
+                                       value = "";
+                               }
+                               else if(value == "activator")
+                               {
+                                       valueent = act;
+                                       value = "";
+                               }
+                               else if(value == "other")
+                               {
+                                       valueent = other;
+                                       value = "";
+                               }
+                               else if(value == "pusher")
+                               {
+                                       if(time < act.pushltime)
+                                               valueent = act.pusher;
+                                       else
+                                               valueent = world;
+                                       value = "";
+                               }
+                               else if(value == "target")
+                               {
+                                       valueent = e;
+                                       value = "";
+                               }
+                               else if(value == "killtarget")
+                               {
+                                       valueent = kt;
+                                       value = "";
+                               }
+                               else if(value == "target2")
+                               {
+                                       valueent = t2;
+                                       value = "";
+                               }
+                               else if(value == "target3")
+                               {
+                                       valueent = t3;
+                                       value = "";
+                               }
+                               else if(value == "target4")
+                               {
+                                       valueent = t4;
+                                       value = "";
+                               }
+                               else if(value == "time")
+                               {
+                                       valueent = world;
+                                       value = ftos(time);
+                               }
+                               else
+                               {
+                                       print("target_spawn: invalid/unknown variable replacement ", value, " specified, ignored!\n");
+                                       continue;
+                               }
+
+                               if(valuefield == "")
+                               {
+                                       if(value == "")
+                                               value = ftos(num_for_edict(valueent));
+                               }
+                               else
+                               {
+                                       if(value != "")
+                                       {
+                                               print("target_spawn: try to get a field of a non-entity, ignored!\n");
+                                               continue;
+                                       }
+                                       data2 = stov(db_get(TemporaryDB, strcat("/target_spawn/field/", valuefield)));
+                                       if(data2_y == 0) // undefined field, i.e., invalid type
+                                       {
+                                               print("target_spawn: invalid/unknown entity key replacement ", valuefield, " specified, ignored!\n");
+                                               continue;
+                                       }
+                                       value = getentityfieldstring(data2_x, valueent);
+                               }
+
+                               if(valueoffset != "")
+                               {
+                                       switch(data.y)
+                                       {
+                                               case FIELD_STRING:
+                                                       value = strcat(value, valueoffset);
+                                                       break;
+                                               case FIELD_FLOAT:
+                                                       value = ftos(stof(value) + stof(valueoffset));
+                                                       break;
+                                               case FIELD_VECTOR:
+                                                       value = vtos(stov(value) + stov(valueoffset));
+                                                       break;
+                                               default:
+                                                       print("target_spawn: only string, float and vector fields can do calculations, calculation ignored!\n");
+                                                       break;
+                                       }
+                               }
+
+                               if(valueoffsetrandom != "")
+                               {
+                                       switch(data.y)
+                                       {
+                                               case FIELD_FLOAT:
+                                                       value = ftos(stof(value) + random() * stof(valueoffsetrandom));
+                                                       break;
+                                               case FIELD_VECTOR:
+                                                       data2 = stov(valueoffsetrandom);
+                                                       value = vtos(stov(value) + random() * data2_x * '1 0 0' + random() * data2_y * '0 1 0' + random() * data2_z * '0 0 1');
+                                                       break;
+                                               default:
+                                                       print("target_spawn: only float and vector fields can do random calculations, calculation ignored!\n");
+                                                       break;
+                                       }
+                               }
+                       }
+               }
+               if(key == "$")
+               {
+                       if(substring(value, 0, 1) == "_")
+                               value = strcat("target_spawn_helper", value);
+                       putentityfieldstring(target_spawn_spawnfunc_field, e, value);
+
+                       oldself = self;
+                       oldactivator = activator;
+
+                       self = e;
+                       activator = act;
+
+                       self.target_spawn_spawnfunc();
+
+                       self = oldself;
+                       activator = oldactivator;
+
+                       // We called an external function, so we have to re-tokenize msg.
+                       n = tokenize_console(msg);
+               }
+               else
+               {
+                       if(data.y == FIELD_VECTOR)
+                               value = strreplace("'", "", value); // why?!?
+                       putentityfieldstring(data.x, e, value);
+               }
+       }
+}
+
+void target_spawn_useon(entity e)
+{
+       self.target_spawn_activator = activator;
+       target_spawn_edit_entity(
+               e,
+               self.message,
+               find(world, targetname, self.killtarget),
+               find(world, targetname, self.target2),
+               find(world, targetname, self.target3),
+               find(world, targetname, self.target4),
+               activator
+       );
+}
+
+float target_spawn_cancreate()
+{
+       float c;
+       entity e;
+
+       c = self.count;
+       if(c == 0) // no limit?
+               return 1;
+
+       ++c; // increase count to not include MYSELF
+       for(e = world; (e = findfloat(e, target_spawn_id, self.target_spawn_id)); --c)
+               ;
+
+       // if c now is 0, we have AT LEAST the given count (maybe more), so don't spawn any more
+       if(c == 0)
+               return 0;
+       return 1;
+}
+
+void target_spawn_use()
+{
+       entity e;
+
+       if(self.target == "")
+       {
+               // spawn new entity
+               if(!target_spawn_cancreate())
+                       return;
+               e = spawn();
+               target_spawn_useon(e);
+               e.target_spawn_id = self.target_spawn_id;
+       }
+       else if(self.target == "*activator")
+       {
+               // edit entity
+               if(activator)
+                       target_spawn_useon(activator);
+       }
+       else
+       {
+               // edit entity
+               for(e = world; (e = find(e, targetname, self.target)); )
+                       target_spawn_useon(e);
+       }
+}
+
+void target_spawn_spawnfirst()
+{
+       activator = self.target_spawn_activator;
+       if(self.spawnflags & 2)
+               target_spawn_use();
+}
+
+void initialize_field_db()
+{
+       if(!target_spawn_initialized)
+       {
+               float n, i;
+               string fn;
+               vector prev, new;
+               float ft;
+
+               n = numentityfields();
+               for(i = 0; i < n; ++i)
+               {
+                       fn = entityfieldname(i);
+                       ft = entityfieldtype(i);
+                       new = i * '1 0 0' + ft * '0 1 0' + '0 0 1';
+                       prev = stov(db_get(TemporaryDB, strcat("/target_spawn/field/", fn)));
+                       if(prev.y == 0)
+                       {
+                               db_put(TemporaryDB, strcat("/target_spawn/field/", fn), vtos(new));
+                               if(fn == "target_spawn_spawnfunc")
+                                       target_spawn_spawnfunc_field = i;
+                       }
+               }
+
+               target_spawn_initialized = 1;
+       }
+}
+
+void spawnfunc_target_spawn()
+{
+       initialize_field_db();
+       self.use = target_spawn_use;
+       self.message = strzone(strreplace("'", "\"", self.message));
+       self.target_spawn_id = ++target_spawn_count;
+       InitializeEntity(self, target_spawn_spawnfirst, INITPRIO_LAST);
+}
+#endif
diff --git a/qcsrc/common/triggers/target/speaker.qc b/qcsrc/common/triggers/target/speaker.qc
new file mode 100644 (file)
index 0000000..7be8b91
--- /dev/null
@@ -0,0 +1,133 @@
+#ifdef SVQC
+// TODO add a way to do looped sounds with sound(); then complete this entity
+void target_speaker_use_off();
+void target_speaker_use_activator()
+{
+       if (!IS_REAL_CLIENT(activator))
+               return;
+       string snd;
+       if(substring(self.noise, 0, 1) == "*")
+       {
+               var .string sample;
+               sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
+               if(GetPlayerSoundSampleField_notFound)
+                       snd = "misc/null.wav";
+               else if(activator.sample == "")
+                       snd = "misc/null.wav";
+               else
+               {
+                       tokenize_console(activator.sample);
+                       float n;
+                       n = stof(argv(1));
+                       if(n > 0)
+                               snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
+                       else
+                               snd = strcat(argv(0), ".wav"); // randomization
+               }
+       }
+       else
+               snd = self.noise;
+       msg_entity = activator;
+       soundto(MSG_ONE, self, CH_TRIGGER, snd, VOL_BASE * self.volume, self.atten);
+}
+void target_speaker_use_on()
+{
+       string snd;
+       if(substring(self.noise, 0, 1) == "*")
+       {
+               var .string sample;
+               sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
+               if(GetPlayerSoundSampleField_notFound)
+                       snd = "misc/null.wav";
+               else if(activator.sample == "")
+                       snd = "misc/null.wav";
+               else
+               {
+                       tokenize_console(activator.sample);
+                       float n;
+                       n = stof(argv(1));
+                       if(n > 0)
+                               snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
+                       else
+                               snd = strcat(argv(0), ".wav"); // randomization
+               }
+       }
+       else
+               snd = self.noise;
+       sound(self, CH_TRIGGER_SINGLE, snd, VOL_BASE * self.volume, self.atten);
+       if(self.spawnflags & 3)
+               self.use = target_speaker_use_off;
+}
+void target_speaker_use_off()
+{
+       sound(self, CH_TRIGGER_SINGLE, "misc/null.wav", VOL_BASE * self.volume, self.atten);
+       self.use = target_speaker_use_on;
+}
+void target_speaker_reset()
+{
+       if(self.spawnflags & 1) // LOOPED_ON
+       {
+               if(self.use == target_speaker_use_on)
+                       target_speaker_use_on();
+       }
+       else if(self.spawnflags & 2)
+       {
+               if(self.use == target_speaker_use_off)
+                       target_speaker_use_off();
+       }
+}
+
+void spawnfunc_target_speaker()
+{
+       // TODO: "*" prefix to sound file name
+       // TODO: wait and random (just, HOW? random is not a field)
+       if(self.noise)
+               precache_sound (self.noise);
+
+       if(!self.atten && !(self.spawnflags & 4))
+       {
+               IFTARGETED
+                       self.atten = ATTEN_NORM;
+               else
+                       self.atten = ATTEN_STATIC;
+       }
+       else if(self.atten < 0)
+               self.atten = 0;
+
+       if(!self.volume)
+               self.volume = 1;
+
+       IFTARGETED
+       {
+               if(self.spawnflags & 8) // ACTIVATOR
+                       self.use = target_speaker_use_activator;
+               else if(self.spawnflags & 1) // LOOPED_ON
+               {
+                       target_speaker_use_on();
+                       self.reset = target_speaker_reset;
+               }
+               else if(self.spawnflags & 2) // LOOPED_OFF
+               {
+                       self.use = target_speaker_use_on;
+                       self.reset = target_speaker_reset;
+               }
+               else
+                       self.use = target_speaker_use_on;
+       }
+       else if(self.spawnflags & 1) // LOOPED_ON
+       {
+               ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
+               remove(self);
+       }
+       else if(self.spawnflags & 2) // LOOPED_OFF
+       {
+               objerror("This sound entity can never be activated");
+       }
+       else
+       {
+               // Quake/Nexuiz fallback
+               ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
+               remove(self);
+       }
+}
+#endif
diff --git a/qcsrc/common/triggers/target/voicescript.qc b/qcsrc/common/triggers/target/voicescript.qc
new file mode 100644 (file)
index 0000000..c173d80
--- /dev/null
@@ -0,0 +1,101 @@
+#ifdef SVQC
+.entity voicescript; // attached voice script
+.float voicescript_index; // index of next voice, or -1 to use the randomized ones
+.float voicescript_nextthink; // time to play next voice
+.float voicescript_voiceend; // time when this voice ends
+
+void target_voicescript_clear(entity pl)
+{
+       pl.voicescript = world;
+}
+
+void target_voicescript_use()
+{
+       if(activator.voicescript != self)
+       {
+               activator.voicescript = self;
+               activator.voicescript_index = 0;
+               activator.voicescript_nextthink = time + self.delay;
+       }
+}
+
+void target_voicescript_next(entity pl)
+{
+       entity vs;
+       float i, n, dt;
+
+       vs = pl.voicescript;
+       if(!vs)
+               return;
+       if(vs.message == "")
+               return;
+       if (!IS_PLAYER(pl))
+               return;
+       if(gameover)
+               return;
+
+       if(time >= pl.voicescript_voiceend)
+       {
+               if(time >= pl.voicescript_nextthink)
+               {
+                       // get the next voice...
+                       n = tokenize_console(vs.message);
+
+                       if(pl.voicescript_index < vs.cnt)
+                               i = pl.voicescript_index * 2;
+                       else if(n > vs.cnt * 2)
+                               i = ((pl.voicescript_index - vs.cnt) % ((n - vs.cnt * 2 - 1) / 2)) * 2 + vs.cnt * 2 + 1;
+                       else
+                               i = -1;
+
+                       if(i >= 0)
+                       {
+                               play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
+                               dt = stof(argv(i + 1));
+                               if(dt >= 0)
+                               {
+                                       pl.voicescript_voiceend = time + dt;
+                                       pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
+                               }
+                               else
+                               {
+                                       pl.voicescript_voiceend = time - dt;
+                                       pl.voicescript_nextthink = pl.voicescript_voiceend;
+                               }
+
+                               pl.voicescript_index += 1;
+                       }
+                       else
+                       {
+                               pl.voicescript = world; // stop trying then
+                       }
+               }
+       }
+}
+
+void spawnfunc_target_voicescript()
+{
+       // netname: directory of the sound files
+       // message: list of "sound file" duration "sound file" duration, a *, and again a list
+       //          foo1 4.1 foo2 4.0 foo3 -3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
+       //          Here, a - in front of the duration means that no delay is to be
+       //          added after this message
+       // wait: average time between messages
+       // delay: initial delay before the first message
+
+       float i, n;
+       self.use = target_voicescript_use;
+
+       n = tokenize_console(self.message);
+       self.cnt = n / 2;
+       for(i = 0; i+1 < n; i += 2)
+       {
+               if(argv(i) == "*")
+               {
+                       self.cnt = i / 2;
+                       ++i;
+               }
+               precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
+       }
+}
+#endif
diff --git a/qcsrc/common/triggers/teleporters.qc b/qcsrc/common/triggers/teleporters.qc
new file mode 100644 (file)
index 0000000..5e91d76
--- /dev/null
@@ -0,0 +1,252 @@
+#include "teleporters.qh"
+
+#if defined(CSQC)
+#elif defined(MENUQC)
+#elif defined(SVQC)
+    #include "../../warpzonelib/common.qh"
+    #include "../../warpzonelib/util_server.qh"
+    #include "../../warpzonelib/server.qh"
+    #include "../constants.qh"
+       #include "../triggers/subs.qh"
+    #include "../util.qh"
+    #include "../../server/weapons/csqcprojectile.qh"
+    #include "../../server/autocvars.qh"
+    #include "../../server/constants.qh"
+    #include "../../server/defs.qh"
+    #include "../deathtypes.qh"
+    #include "../../server/tturrets/include/turrets_early.qh"
+    #include "../../server/vehicles/vehicles_def.qh"
+    #include "../mapinfo.qh"
+    #include "../../server/anticheat.qh"
+#endif
+
+#ifdef SVQC
+
+float check_tdeath(entity player, vector org, vector telefragmin, vector telefragmax)
+{
+       if (IS_PLAYER(player) && player.health >= 1)
+       {
+               TDEATHLOOP(org)
+               {
+                       if (!(teamplay && autocvar_g_telefrags_teamplay && head.team == player.team))
+                               if(IS_PLAYER(head))
+                                       if(head.health >= 1)
+                                               return 1;
+               }
+       }
+       return 0;
+}
+
+void tdeath(entity player, entity teleporter, entity telefragger, vector telefragmin, vector telefragmax)
+{
+       TDEATHLOOP(player.origin)
+       {
+               if (IS_PLAYER(player) && player.health >= 1)
+               {
+                       if (!(teamplay && autocvar_g_telefrags_teamplay && head.team == player.team))
+                       {
+                               if(IS_PLAYER(head))
+                                       if(head.health >= 1)
+                                               ++tdeath_hit;
+                               Damage (head, teleporter, telefragger, 10000, DEATH_TELEFRAG, head.origin, '0 0 0');
+                       }
+               }
+               else // dead bodies and monsters gib themselves instead of telefragging
+                       Damage (telefragger, teleporter, telefragger, 10000, DEATH_TELEFRAG, telefragger.origin, '0 0 0');
+       }
+}
+
+void spawn_tdeath(vector v0, entity e, vector v)
+{
+       tdeath(e, e, e, '0 0 0', '0 0 0');
+}
+
+void TeleportPlayer(entity teleporter, entity player, vector to, vector to_angles, vector to_velocity, vector telefragmin, vector telefragmax, float tflags)
+{
+       entity telefragger;
+       vector from;
+
+       if(teleporter.owner)
+               telefragger = teleporter.owner;
+       else
+               telefragger = player;
+
+       makevectors (to_angles);
+
+       if(player.teleportable == TELEPORT_NORMAL) // don't play sounds or show particles for anything that isn't a player, maybe change later to block only observers
+       {
+               if(self.pushltime < time) // only show one teleport effect per teleporter per 0.2 seconds, for better fps
+               {
+                       if(tflags & TELEPORT_FLAG_SOUND)
+                               sound (player, CH_TRIGGER, "misc/teleport.wav", VOL_BASE, ATTEN_NORM);
+                       if(tflags & TELEPORT_FLAG_PARTICLES)
+                       {
+                               pointparticles(particleeffectnum("teleport"), player.origin, '0 0 0', 1);
+                               pointparticles(particleeffectnum("teleport"), to + v_forward * 32, '0 0 0', 1);
+                       }
+                       self.pushltime = time + 0.2;
+               }
+       }
+
+       // Relocate the player
+       // assuming to allows PL_MIN to PL_MAX box and some more
+       from = player.origin;
+       setorigin (player, to);
+       player.oldorigin = to; // don't undo the teleport by unsticking
+       player.angles = to_angles;
+       player.fixangle = true;
+       player.velocity = to_velocity;
+       BITXOR_ASSIGN(player.effects, EF_TELEPORT_BIT);
+
+       makevectors(player.angles);
+       Reset_ArcBeam(player, v_forward);
+       UpdateCSQCProjectileAfterTeleport(player);
+
+       if(IS_PLAYER(player))
+       {
+               if(tflags & TELEPORT_FLAG_TDEATH)
+                       if(player.takedamage && player.deadflag == DEAD_NO && !g_race && !g_cts && (autocvar_g_telefrags || (tflags & TELEPORT_FLAG_FORCE_TDEATH)))
+                               tdeath(player, teleporter, telefragger, telefragmin, telefragmax);
+
+               // player no longer is on ground
+               player.flags &= ~FL_ONGROUND;
+
+               // reset tracking of oldvelocity for impact damage (sudden velocity changes)
+               player.oldvelocity = player.velocity;
+
+               // reset tracking of who pushed you into a hazard (for kill credit)
+               if(teleporter.owner)
+               {
+                       player.pusher = teleporter.owner;
+                       player.pushltime = time + autocvar_g_maxpushtime;
+                       player.istypefrag = player.BUTTON_CHAT;
+               }
+               else
+               {
+                       player.pushltime = 0;
+                       player.istypefrag = 0;
+               }
+
+               player.lastteleporttime = time;
+       }
+}
+
+entity Simple_TeleportPlayer(entity teleporter, entity player)
+{
+       vector locout;
+       entity e;
+       float p;
+
+       // Find the output teleporter
+       if(teleporter.enemy)
+       {
+               e = teleporter.enemy;
+       }
+       else
+       {
+               RandomSelection_Init();
+               for(e = world; (e = find(e, targetname, teleporter.target)); )
+               {
+                       p = 1;
+                       if(autocvar_g_telefrags_avoid)
+                       {
+                               locout = e.origin + '0 0 1' * (1 - player.mins.z - 24);
+                               if(check_tdeath(player, locout, '0 0 0', '0 0 0'))
+                                       p = 0;
+                       }
+                       RandomSelection_Add(e, 0, string_null, (e.cnt ? e.cnt : 1), p);
+               }
+               e = RandomSelection_chosen_ent;
+       }
+
+       if(!e) { sprint(player, "Teleport destination vanished. Sorry... please complain to the mapper.\n"); }
+
+       makevectors(e.mangle);
+
+       if(e.speed)
+               if(vlen(player.velocity) > e.speed)
+                       player.velocity = normalize(player.velocity) * max(0, e.speed);
+
+       if(autocvar_g_teleport_maxspeed)
+               if(vlen(player.velocity) > autocvar_g_teleport_maxspeed)
+                       player.velocity = normalize(player.velocity) * max(0, autocvar_g_teleport_maxspeed);
+
+       locout = e.origin + '0 0 1' * (1 - player.mins.z - 24);
+       TeleportPlayer(teleporter, player, locout, e.mangle, v_forward * vlen(player.velocity), '0 0 0', '0 0 0', TELEPORT_FLAGS_TELEPORTER);
+
+       return e;
+}
+
+void teleport_findtarget (void)
+{
+       entity e;
+       float n;
+
+       n = 0;
+       for(e = world; (e = find(e, targetname, self.target)); )
+       {
+               ++n;
+               if(e.movetype == MOVETYPE_NONE)
+                       waypoint_spawnforteleporter(self, e.origin, 0);
+               if(e.classname != "info_teleport_destination")
+                       print("^3MAPPER ERROR: teleporter does target an invalid teleport destination entity. Angles will not work.\n");
+       }
+
+       if(n == 0)
+       {
+               // no dest!
+               objerror ("Teleporter with nonexistant target");
+               return;
+       }
+       else if(n == 1)
+       {
+               // exactly one dest - bots love that
+               self.enemy = find(e, targetname, self.target);
+       }
+       else
+       {
+               // have to use random selection every single time
+               self.enemy = world;
+       }
+
+       // now enable touch
+       self.touch = Teleport_Touch;
+}
+
+entity Teleport_Find(vector mi, vector ma)
+{
+       entity e;
+       for(e = world; (e = find(e, classname, "trigger_teleport")); )
+               if(WarpZoneLib_BoxTouchesBrush(mi, ma, e, world))
+                       return e;
+       return world;
+}
+
+void WarpZone_PostTeleportPlayer_Callback(entity pl)
+{
+       makevectors(pl.angles);
+       Reset_ArcBeam(pl, v_forward);
+       UpdateCSQCProjectileAfterTeleport(pl);
+       {
+               entity oldself = self;
+               self = pl;
+               anticheat_fixangle();
+               self = oldself;
+       }
+       // "disown" projectiles after teleport
+       if(pl.owner)
+       if(pl.owner == pl.realowner)
+       {
+               if(!(pl.flags & FL_PROJECTILE))
+                       print("A non-projectile got through a warpzone and its owner cleared. It's a ", pl.classname, ".\n");
+               pl.owner = world;
+       }
+       if(IS_PLAYER(pl))
+       {
+               // reset tracking of oldvelocity for impact damage (sudden velocity changes)
+               pl.oldvelocity = pl.velocity;
+               // reset teleport time tracking too (or multijump can cause insane speeds)
+               pl.lastteleporttime = time;
+       }
+}
+#endif
diff --git a/qcsrc/common/triggers/teleporters.qh b/qcsrc/common/triggers/teleporters.qh
new file mode 100644 (file)
index 0000000..bdd24dd
--- /dev/null
@@ -0,0 +1,68 @@
+#ifndef T_TELEPORTERS_H
+#define T_TELEPORTERS_H
+
+#ifdef SVQC
+
+void trigger_teleport_use();
+
+#define TDEATHLOOP(o) \
+       entity head; \
+       vector deathmin; \
+       vector deathmax; \
+       float deathradius; \
+       deathmin = (o) + player.mins; \
+       deathmax = (o) + player.maxs; \
+       if(telefragmin != telefragmax) \
+       { \
+               if(deathmin.x > telefragmin.x) deathmin.x = telefragmin.x; \
+               if(deathmin.y > telefragmin.y) deathmin.y = telefragmin.y; \
+               if(deathmin.z > telefragmin.z) deathmin.z = telefragmin.z; \
+               if(deathmax.x < telefragmax.x) deathmax.x = telefragmax.x; \
+               if(deathmax.y < telefragmax.y) deathmax.y = telefragmax.y; \
+               if(deathmax.z < telefragmax.z) deathmax.z = telefragmax.z; \
+       } \
+       deathradius = max(vlen(deathmin), vlen(deathmax)); \
+       for(head = findradius(o, deathradius); head; head = head.chain) \
+               if(head != player) \
+                       if(head.takedamage) \
+                               if(boxesoverlap(deathmin, deathmax, head.absmin, head.absmax))
+
+
+float check_tdeath(entity player, vector org, vector telefragmin, vector telefragmax);
+float tdeath_hit;
+void tdeath(entity player, entity teleporter, entity telefragger, vector telefragmin, vector telefragmax);
+
+void spawn_tdeath(vector v0, entity e, vector v);
+
+.entity pusher;
+const float TELEPORT_FLAG_SOUND = 1;
+const float TELEPORT_FLAG_PARTICLES = 2;
+const float TELEPORT_FLAG_TDEATH = 4;
+const float TELEPORT_FLAG_FORCE_TDEATH = 8;
+
+#define TELEPORT_FLAGS_WARPZONE   0
+#define TELEPORT_FLAGS_PORTAL     (TELEPORT_FLAG_SOUND | TELEPORT_FLAG_PARTICLES | TELEPORT_FLAG_TDEATH | TELEPORT_FLAG_FORCE_TDEATH)
+#define TELEPORT_FLAGS_TELEPORTER (TELEPORT_FLAG_SOUND | TELEPORT_FLAG_PARTICLES | TELEPORT_FLAG_TDEATH)
+
+// types for .teleportable entity setting
+const float TELEPORT_NORMAL = 1; // play sounds/effects etc
+const float TELEPORT_SIMPLE = 2; // only do teleport, nothing special
+
+void Reset_ArcBeam(entity player, vector forward);
+void TeleportPlayer(entity teleporter, entity player, vector to, vector to_angles, vector to_velocity, vector telefragmin, vector telefragmax, float tflags);
+
+entity Simple_TeleportPlayer(entity teleporter, entity player);
+
+void Teleport_Touch (void);
+
+void teleport_findtarget (void);
+
+entity Teleport_Find(vector mi, vector ma);
+
+entity teleport_first;
+.entity teleport_next;
+
+void WarpZone_PostTeleportPlayer_Callback(entity pl);
+#endif
+
+#endif
diff --git a/qcsrc/common/triggers/trigger/counter.qc b/qcsrc/common/triggers/trigger/counter.qc
new file mode 100644 (file)
index 0000000..bf1d9b2
--- /dev/null
@@ -0,0 +1,49 @@
+#ifdef SVQC
+void counter_use()
+{
+       self.count -= 1;
+       if (self.count < 0)
+               return;
+
+       if (self.count == 0)
+       {
+               if(IS_PLAYER(activator) && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
+                       Send_Notification(NOTIF_ONE, activator, MSG_CENTER, CENTER_SEQUENCE_COMPLETED);
+
+               self.enemy = activator;
+               multi_trigger ();
+       }
+       else
+       {
+               if(IS_PLAYER(activator) && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
+               if(self.count >= 4)
+                       Send_Notification(NOTIF_ONE, activator, MSG_CENTER, CENTER_SEQUENCE_COUNTER);
+               else
+                       Send_Notification(NOTIF_ONE, activator, MSG_CENTER, CENTER_SEQUENCE_COUNTER_FEWMORE, self.count);
+       }
+}
+
+void counter_reset()
+{
+       self.count = self.cnt;
+       multi_reset();
+}
+
+/*QUAKED spawnfunc_trigger_counter (.5 .5 .5) ? nomessage
+Acts as an intermediary for an action that takes multiple inputs.
+
+If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
+
+After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
+*/
+void spawnfunc_trigger_counter()
+{
+       self.wait = -1;
+       if (!self.count)
+               self.count = 2;
+       self.cnt = self.count;
+
+       self.use = counter_use;
+       self.reset = counter_reset;
+}
+#endif
diff --git a/qcsrc/common/triggers/trigger/delay.qc b/qcsrc/common/triggers/trigger/delay.qc
new file mode 100644 (file)
index 0000000..b01efe3
--- /dev/null
@@ -0,0 +1,22 @@
+#ifdef SVQC
+void delay_use()
+{
+    self.think = SUB_UseTargets;
+   self.nextthink = self.wait;
+}
+
+void delay_reset()
+{
+       self.think = func_null;
+       self.nextthink = 0;
+}
+
+void spawnfunc_trigger_delay()
+{
+    if(!self.wait)
+        self.wait = 1;
+
+    self.use = delay_use;
+    self.reset = delay_reset;
+}
+#endif
diff --git a/qcsrc/common/triggers/trigger/disablerelay.qc b/qcsrc/common/triggers/trigger/disablerelay.qc
new file mode 100644 (file)
index 0000000..cd5fdff
--- /dev/null
@@ -0,0 +1,31 @@
+#ifdef SVQC
+void trigger_disablerelay_use()
+{
+       entity e;
+
+       float a, b;
+       a = b = 0;
+
+       for(e = world; (e = find(e, targetname, self.target)); )
+       {
+               if(e.use == SUB_UseTargets)
+               {
+                       e.use = SUB_DontUseTargets;
+                       ++a;
+               }
+               else if(e.use == SUB_DontUseTargets)
+               {
+                       e.use = SUB_UseTargets;
+                       ++b;
+               }
+       }
+
+       if((!a) == (!b))
+               print("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!\n");
+}
+
+void spawnfunc_trigger_disablerelay()
+{
+       self.use = trigger_disablerelay_use;
+}
+#endif
diff --git a/qcsrc/common/triggers/trigger/flipflop.qc b/qcsrc/common/triggers/trigger/flipflop.qc
new file mode 100644 (file)
index 0000000..12d8a59
--- /dev/null
@@ -0,0 +1,19 @@
+#ifdef SVQC
+/*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
+"Flip-flop" trigger gate... lets only every second trigger event through
+*/
+void flipflop_use()
+{
+    self.state = !self.state;
+    if(self.state)
+        SUB_UseTargets();
+}
+
+void spawnfunc_trigger_flipflop()
+{
+    if(self.spawnflags & 1)
+        self.state = 1;
+    self.use = flipflop_use;
+    self.reset = spawnfunc_trigger_flipflop; // perfect resetter
+}
+#endif
diff --git a/qcsrc/common/triggers/trigger/gamestart.qc b/qcsrc/common/triggers/trigger/gamestart.qc
new file mode 100644 (file)
index 0000000..3ad419d
--- /dev/null
@@ -0,0 +1,22 @@
+#ifdef SVQC
+void gamestart_use()
+{
+       activator = self;
+       SUB_UseTargets();
+       remove(self);
+}
+
+void spawnfunc_trigger_gamestart()
+{
+       self.use = gamestart_use;
+       self.reset2 = spawnfunc_trigger_gamestart;
+
+       if(self.wait)
+       {
+               self.think = self.use;
+               self.nextthink = game_starttime + self.wait;
+       }
+       else
+               InitializeEntity(self, gamestart_use, INITPRIO_FINDTARGET);
+}
+#endif
diff --git a/qcsrc/common/triggers/trigger/gravity.qc b/qcsrc/common/triggers/trigger/gravity.qc
new file mode 100644 (file)
index 0000000..a709a1f
--- /dev/null
@@ -0,0 +1,106 @@
+#ifdef SVQC
+.entity trigger_gravity_check;
+void trigger_gravity_remove(entity own)
+{
+       if(own.trigger_gravity_check.owner == own)
+       {
+               UpdateCSQCProjectile(own);
+               own.gravity = own.trigger_gravity_check.gravity;
+               remove(own.trigger_gravity_check);
+       }
+       else
+               backtrace("Removing a trigger_gravity_check with no valid owner");
+       own.trigger_gravity_check = world;
+}
+void trigger_gravity_check_think()
+{
+       // This spawns when a player enters the gravity zone and checks if he left.
+       // Each frame, self.count is set to 2 by trigger_gravity_touch() and decreased by 1 here.
+       // It the player has left the gravity trigger, this will be allowed to reach 0 and indicate that.
+       if(self.count <= 0)
+       {
+               if(self.owner.trigger_gravity_check == self)
+                       trigger_gravity_remove(self.owner);
+               else
+                       remove(self);
+               return;
+       }
+       else
+       {
+               self.count -= 1;
+               self.nextthink = time;
+       }
+}
+
+void trigger_gravity_use()
+{
+       self.state = !self.state;
+}
+
+void trigger_gravity_touch()
+{
+       float g;
+
+       if(self.state != true)
+               return;
+
+       EXACTTRIGGER_TOUCH;
+
+       g = self.gravity;
+
+       if (!(self.spawnflags & 1))
+       {
+               if(other.trigger_gravity_check)
+               {
+                       if(self == other.trigger_gravity_check.enemy)
+                       {
+                               // same?
+                               other.trigger_gravity_check.count = 2; // gravity one more frame...
+                               return;
+                       }
+
+                       // compare prio
+                       if(self.cnt > other.trigger_gravity_check.enemy.cnt)
+                               trigger_gravity_remove(other);
+                       else
+                               return;
+               }
+               other.trigger_gravity_check = spawn();
+               other.trigger_gravity_check.enemy = self;
+               other.trigger_gravity_check.owner = other;
+               other.trigger_gravity_check.gravity = other.gravity;
+               other.trigger_gravity_check.think = trigger_gravity_check_think;
+               other.trigger_gravity_check.nextthink = time;
+               other.trigger_gravity_check.count = 2;
+               if(other.gravity)
+                       g *= other.gravity;
+       }
+
+       if (other.gravity != g)
+       {
+               other.gravity = g;
+               if(self.noise != "")
+                       sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
+               UpdateCSQCProjectile(self.owner);
+       }
+}
+
+void spawnfunc_trigger_gravity()
+{
+       if(self.gravity == 1)
+               return;
+
+       EXACTTRIGGER_INIT;
+       self.touch = trigger_gravity_touch;
+       if(self.noise != "")
+               precache_sound(self.noise);
+
+       self.state = true;
+       IFTARGETED
+       {
+               self.use = trigger_gravity_use;
+               if(self.spawnflags & 2)
+                       self.state = false;
+       }
+}
+#endif
diff --git a/qcsrc/common/triggers/trigger/heal.qc b/qcsrc/common/triggers/trigger/heal.qc
new file mode 100644 (file)
index 0000000..6d68610
--- /dev/null
@@ -0,0 +1,42 @@
+#ifdef SVQC
+.float triggerhealtime;
+void trigger_heal_touch()
+{
+       if (self.active != ACTIVE_ACTIVE)
+               return;
+
+       // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
+       if (other.iscreature)
+       {
+               if (other.takedamage)
+               if (!other.deadflag)
+               if (other.triggerhealtime < time)
+               {
+                       EXACTTRIGGER_TOUCH;
+                       other.triggerhealtime = time + 1;
+
+                       if (other.health < self.max_health)
+                       {
+                               other.health = min(other.health + self.health, self.max_health);
+                               other.pauserothealth_finished = max(other.pauserothealth_finished, time + autocvar_g_balance_pause_health_rot);
+                               sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
+                       }
+               }
+       }
+}
+
+void spawnfunc_trigger_heal()
+{
+       self.active = ACTIVE_ACTIVE;
+
+       EXACTTRIGGER_INIT;
+       self.touch = trigger_heal_touch;
+       if (!self.health)
+               self.health = 10;
+       if (!self.max_health)
+               self.max_health = 200; //Max health topoff for field
+       if(self.noise == "")
+               self.noise = "misc/mediumhealth.wav";
+       precache_sound(self.noise);
+}
+#endif
diff --git a/qcsrc/common/triggers/trigger/hurt.qc b/qcsrc/common/triggers/trigger/hurt.qc
new file mode 100644 (file)
index 0000000..d39c32c
--- /dev/null
@@ -0,0 +1,94 @@
+#ifdef SVQC
+void trigger_hurt_use()
+{
+       if(IS_PLAYER(activator))
+               self.enemy = activator;
+       else
+               self.enemy = world; // let's just destroy it, if taking over is too much work
+}
+
+.float triggerhurttime;
+void trigger_hurt_touch()
+{
+       if (self.active != ACTIVE_ACTIVE)
+               return;
+
+       if(self.team)
+               if(((self.spawnflags & 4) == 0) == (self.team != other.team))
+                       return;
+
+       // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
+       if (other.iscreature)
+       {
+               if (other.takedamage)
+               if (other.triggerhurttime < time)
+               {
+                       EXACTTRIGGER_TOUCH;
+                       other.triggerhurttime = time + 1;
+
+                       entity own;
+                       own = self.enemy;
+                       if (!IS_PLAYER(own))
+                       {
+                               own = self;
+                               self.enemy = world; // I still hate you all
+                       }
+
+                       Damage (other, self, own, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
+               }
+       }
+       else if(other.damagedbytriggers)
+       {
+               if(other.takedamage)
+               {
+                       EXACTTRIGGER_TOUCH;
+                       Damage(other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
+               }
+       }
+
+       return;
+}
+
+/*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ?
+Any object touching this will be hurt
+set dmg to damage amount
+defalt dmg = 5
+*/
+.entity trigger_hurt_next;
+entity trigger_hurt_last;
+entity trigger_hurt_first;
+void spawnfunc_trigger_hurt()
+{
+       EXACTTRIGGER_INIT;
+       self.active = ACTIVE_ACTIVE;
+       self.trigger_touch = trigger_hurt_touch;
+       self.think = trigger_think_generic;
+       self.nextthink = time;
+       self.use = trigger_hurt_use;
+       self.enemy = world; // I hate you all
+       if (!self.dmg)
+               self.dmg = 1000;
+       if (self.message == "")
+               self.message = "was in the wrong place";
+       if (self.message2 == "")
+               self.message2 = "was thrown into a world of hurt by";
+       // self.message = "someone like %s always gets wrongplaced";
+
+       if(!trigger_hurt_first)
+               trigger_hurt_first = self;
+       if(trigger_hurt_last)
+               trigger_hurt_last.trigger_hurt_next = self;
+       trigger_hurt_last = self;
+}
+
+float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end)
+{
+       entity th;
+
+       for(th = trigger_hurt_first; th; th = th.trigger_hurt_next)
+               if(tracebox_hits_box(start, mi, ma, end, th.absmin, th.absmax))
+                       return true;
+
+       return false;
+}
+#endif
diff --git a/qcsrc/common/triggers/trigger/impulse.qc b/qcsrc/common/triggers/trigger/impulse.qc
new file mode 100644 (file)
index 0000000..d436881
--- /dev/null
@@ -0,0 +1,146 @@
+#ifdef SVQC
+// targeted (directional) mode
+void trigger_impulse_touch1()
+{
+       entity targ;
+    float pushdeltatime;
+    float str;
+
+       if (self.active != ACTIVE_ACTIVE)
+               return;
+
+       if (!isPushable(other))
+               return;
+
+       EXACTTRIGGER_TOUCH;
+
+    targ = find(world, targetname, self.target);
+    if(!targ)
+    {
+        objerror("trigger_force without a (valid) .target!\n");
+        remove(self);
+        return;
+    }
+
+    str = min(self.radius, vlen(self.origin - other.origin));
+
+    if(self.falloff == 1)
+        str = (str / self.radius) * self.strength;
+    else if(self.falloff == 2)
+        str = (1 - (str / self.radius)) * self.strength;
+    else
+        str = self.strength;
+
+    pushdeltatime = time - other.lastpushtime;
+    if (pushdeltatime > 0.15) pushdeltatime = 0;
+    other.lastpushtime = time;
+    if(!pushdeltatime) return;
+
+    other.velocity = other.velocity + normalize(targ.origin - self.origin) * str * pushdeltatime;
+    other.flags &= ~FL_ONGROUND;
+    UpdateCSQCProjectile(other);
+}
+
+// Directionless (accelerator/decelerator) mode
+void trigger_impulse_touch2()
+{
+    float pushdeltatime;
+
+       if (self.active != ACTIVE_ACTIVE)
+               return;
+
+       if (!isPushable(other))
+               return;
+
+       EXACTTRIGGER_TOUCH;
+
+    pushdeltatime = time - other.lastpushtime;
+    if (pushdeltatime > 0.15) pushdeltatime = 0;
+    other.lastpushtime = time;
+    if(!pushdeltatime) return;
+
+    // div0: ticrate independent, 1 = identity (not 20)
+    other.velocity = other.velocity * pow(self.strength, pushdeltatime);
+    UpdateCSQCProjectile(other);
+}
+
+// Spherical (gravity/repulsor) mode
+void trigger_impulse_touch3()
+{
+    float pushdeltatime;
+    float str;
+
+       if (self.active != ACTIVE_ACTIVE)
+               return;
+
+       if (!isPushable(other))
+               return;
+
+       EXACTTRIGGER_TOUCH;
+
+    pushdeltatime = time - other.lastpushtime;
+    if (pushdeltatime > 0.15) pushdeltatime = 0;
+    other.lastpushtime = time;
+    if(!pushdeltatime) return;
+
+    setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
+
+       str = min(self.radius, vlen(self.origin - other.origin));
+
+    if(self.falloff == 1)
+        str = (1 - str / self.radius) * self.strength; // 1 in the inside
+    else if(self.falloff == 2)
+        str = (str / self.radius) * self.strength; // 0 in the inside
+    else
+        str = self.strength;
+
+    other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
+    UpdateCSQCProjectile(other);
+}
+
+/*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
+-------- KEYS --------
+target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
+         If not, this trigger acts like a damper/accelerator field.
+
+strength : This is how mutch force to add in the direction of .target each second
+           when .target is set. If not, this is hoe mutch to slow down/accelerate
+           someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
+
+radius   : If set, act as a spherical device rather then a liniar one.
+
+falloff : 0 = none, 1 = liniar, 2 = inverted liniar
+
+-------- NOTES --------
+Use a brush textured with common/origin in the trigger entity to determine the origin of the force
+in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
+*/
+
+void spawnfunc_trigger_impulse()
+{
+       self.active = ACTIVE_ACTIVE;
+
+       EXACTTRIGGER_INIT;
+    if(self.radius)
+    {
+        if(!self.strength) self.strength = 2000 * autocvar_g_triggerimpulse_radial_multiplier;
+        setorigin(self, self.origin);
+        setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
+        self.touch = trigger_impulse_touch3;
+    }
+    else
+    {
+        if(self.target)
+        {
+            if(!self.strength) self.strength = 950 * autocvar_g_triggerimpulse_directional_multiplier;
+            self.touch = trigger_impulse_touch1;
+        }
+        else
+        {
+            if(!self.strength) self.strength = 0.9;
+                       self.strength = pow(self.strength, autocvar_g_triggerimpulse_accel_power) * autocvar_g_triggerimpulse_accel_multiplier;
+            self.touch = trigger_impulse_touch2;
+        }
+    }
+}
+#endif
diff --git a/qcsrc/common/triggers/trigger/impulse.qh b/qcsrc/common/triggers/trigger/impulse.qh
new file mode 100644 (file)
index 0000000..67d6361
--- /dev/null
@@ -0,0 +1,10 @@
+#ifndef TRIGGER_IMPULSE_H
+#define TRIGGER_IMPULSE_H
+
+// tZorks trigger impulse / gravity
+.float radius;
+.float falloff;
+.float strength;
+.float lastpushtime;
+
+#endif
diff --git a/qcsrc/common/triggers/trigger/include.qc b/qcsrc/common/triggers/trigger/include.qc
new file mode 100644 (file)
index 0000000..1498634
--- /dev/null
@@ -0,0 +1,24 @@
+#include "include.qh"
+
+#include "counter.qc"
+#include "delay.qc"
+#include "disablerelay.qc"
+#include "flipflop.qc"
+#include "gamestart.qc"
+#include "gravity.qc"
+#include "heal.qc"
+#include "hurt.qc"
+#include "impulse.qc"
+#include "jumppads.qc"
+#include "keylock.qc"
+#include "magicear.qc"
+#include "monoflop.qc"
+#include "multi.qc"
+#include "multivibrator.qc"
+#include "relay.qc"
+#include "relay_activators.qc"
+#include "relay_if.qc"
+#include "relay_teamcheck.qc"
+#include "secret.qc"
+#include "swamp.qc"
+#include "teleport.qc"
diff --git a/qcsrc/common/triggers/trigger/include.qh b/qcsrc/common/triggers/trigger/include.qh
new file mode 100644 (file)
index 0000000..5c862b9
--- /dev/null
@@ -0,0 +1,10 @@
+#ifndef TRIGGERS_TRIGGER_INCLUDE_H
+#define TRIGGERS_TRIGGER_INCLUDE_H
+
+#include "multi.qh"
+#include "jumppads.qh"
+#include "secret.qh"
+#include "swamp.qh"
+#include "keylock.qh"
+
+#endif
diff --git a/qcsrc/common/triggers/trigger/jumppads.qc b/qcsrc/common/triggers/trigger/jumppads.qc
new file mode 100644 (file)
index 0000000..61cd92b
--- /dev/null
@@ -0,0 +1,476 @@
+// TODO: split target_push and put it in the target folder
+#ifdef SVQC
+#include "jumppads.qh"
+#include "../../movetypes/movetypes.qh"
+
+void trigger_push_use()
+{
+       if(teamplay)
+       {
+               self.team = activator.team;
+               self.SendFlags |= 2;
+       }
+}
+#endif
+
+/*
+       trigger_push_calculatevelocity
+
+       Arguments:
+         org - origin of the object which is to be pushed
+         tgt - target entity (can be either a point or a model entity; if it is
+               the latter, its midpoint is used)
+         ht  - jump height, measured from the higher one of org and tgt's midpoint
+
+       Returns: velocity for the jump
+       the global trigger_push_calculatevelocity_flighttime is set to the total
+       jump time
+ */
+
+vector trigger_push_calculatevelocity(vector org, entity tgt, float ht)
+{
+       float grav, sdist, zdist, vs, vz, jumpheight;
+       vector sdir, torg;
+
+       torg = tgt.origin + (tgt.mins + tgt.maxs) * 0.5;
+
+       grav = PHYS_GRAVITY;
+       if(PHYS_ENTGRAVITY(other))
+               grav *= PHYS_ENTGRAVITY(other);
+
+       zdist = torg.z - org.z;
+       sdist = vlen(torg - org - zdist * '0 0 1');
+       sdir = normalize(torg - org - zdist * '0 0 1');
+
+       // how high do we need to push the player?
+       jumpheight = fabs(ht);
+       if(zdist > 0)
+               jumpheight = jumpheight + zdist;
+
+       /*
+               STOP.
+
+               You will not understand the following equations anyway...
+               But here is what I did to get them.
+
+               I used the functions
+
+                 s(t) = t * vs
+                 z(t) = t * vz - 1/2 grav t^2
+
+               and solved for:
+
+                 s(ti) = sdist
+                 z(ti) = zdist
+                 max(z, ti) = jumpheight
+
+               From these three equations, you will find the three parameters vs, vz
+               and ti.
+        */
+
+       // push him so high...
+       vz = sqrt(fabs(2 * grav * jumpheight)); // NOTE: sqrt(positive)!
+
+       // we start with downwards velocity only if it's a downjump and the jump apex should be outside the jump!
+       if(ht < 0)
+               if(zdist < 0)
+                       vz = -vz;
+
+       vector solution;
+       solution = solve_quadratic(0.5 * grav, -vz, zdist); // equation "z(ti) = zdist"
+       // ALWAYS solvable because jumpheight >= zdist
+       if(!solution.z)
+               solution_y = solution.x; // just in case it is not solvable due to roundoff errors, assume two equal solutions at their center (this is mainly for the usual case with ht == 0)
+       if(zdist == 0)
+               solution_x = solution.y; // solution_x is 0 in this case, so don't use it, but rather use solution_y (which will be sqrt(0.5 * jumpheight / grav), actually)
+
+       if(zdist < 0)
+       {
+               // down-jump
+               if(ht < 0)
+               {
+                       // almost straight line type
+                       // jump apex is before the jump
+                       // we must take the larger one
+                       trigger_push_calculatevelocity_flighttime = solution.y;
+               }
+               else
+               {
+                       // regular jump
+                       // jump apex is during the jump
+                       // we must take the larger one too
+                       trigger_push_calculatevelocity_flighttime = solution.y;
+               }
+       }
+       else
+       {
+               // up-jump
+               if(ht < 0)
+               {
+                       // almost straight line type
+                       // jump apex is after the jump
+                       // we must take the smaller one
+                       trigger_push_calculatevelocity_flighttime = solution.x;
+               }
+               else
+               {
+                       // regular jump
+                       // jump apex is during the jump
+                       // we must take the larger one
+                       trigger_push_calculatevelocity_flighttime = solution.y;
+               }
+       }
+       vs = sdist / trigger_push_calculatevelocity_flighttime;
+
+       // finally calculate the velocity
+       return sdir * vs + '0 0 1' * vz;
+}
+
+void trigger_push_touch()
+{
+       if (self.active == ACTIVE_NOT)
+               return;
+
+#ifdef SVQC
+       if (!isPushable(other))
+               return;
+#endif
+
+       if(self.team)
+               if(((self.spawnflags & 4) == 0) == (DIFF_TEAM(self, other)))
+                       return;
+
+       EXACTTRIGGER_TOUCH;
+
+       if(self.enemy)
+       {
+               other.velocity = trigger_push_calculatevelocity(other.origin, self.enemy, self.height);
+               other.move_velocity = other.velocity;
+       }
+       else if(self.target)
+       {
+               entity e;
+               RandomSelection_Init();
+               for(e = world; (e = find(e, targetname, self.target)); )
+               {
+                       if(e.cnt)
+                               RandomSelection_Add(e, 0, string_null, e.cnt, 1);
+                       else
+                               RandomSelection_Add(e, 0, string_null, 1, 1);
+               }
+               other.velocity = trigger_push_calculatevelocity(other.origin, RandomSelection_chosen_ent, self.height);
+               other.move_velocity = other.velocity;
+       }
+       else
+       {
+               other.velocity = self.movedir;
+               other.move_velocity = other.velocity;
+       }
+
+       UNSET_ONGROUND(other);
+
+       other.move_flags &= ~FL_ONGROUND;
+
+#ifdef SVQC
+       if (IS_PLAYER(other))
+       {
+               // reset tracking of oldvelocity for impact damage (sudden velocity changes)
+               other.oldvelocity = other.velocity;
+
+               if(self.pushltime < time)  // prevent "snorring" sound when a player hits the jumppad more than once
+               {
+                       // flash when activated
+                       pointparticles(particleeffectnum("jumppad_activate"), other.origin, other.velocity, 1);
+                       sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
+                       self.pushltime = time + 0.2;
+               }
+               if(IS_REAL_CLIENT(other) || IS_BOT_CLIENT(other))
+               {
+                       bool found = false;
+                       for(int i = 0; i < other.jumppadcount && i < NUM_JUMPPADSUSED; ++i)
+                               if(other.(jumppadsused[i]) == self)
+                                       found = true;
+                       if(!found)
+                       {
+                               other.(jumppadsused[other.jumppadcount % NUM_JUMPPADSUSED]) = self;
+                               other.jumppadcount = other.jumppadcount + 1;
+                       }
+
+                       if(IS_REAL_CLIENT(other))
+                       {
+                               if(self.message)
+                                       centerprint(other, self.message);
+                       }
+                       else
+                               other.lastteleporttime = time;
+
+                       if (other.deadflag == DEAD_NO)
+                               animdecide_setaction(other, ANIMACTION_JUMP, true);
+               }
+               else
+                       other.jumppadcount = true;
+
+               // reset tracking of who pushed you into a hazard (for kill credit)
+               other.pushltime = 0;
+               other.istypefrag = 0;
+       }
+
+       if(self.enemy.target)
+       {
+               entity oldself;
+               oldself = self;
+               activator = other;
+               self = self.enemy;
+               SUB_UseTargets();
+               self = oldself;
+       }
+
+       if (other.flags & FL_PROJECTILE)
+       {
+               other.angles = vectoangles (other.velocity);
+               switch(other.movetype)
+               {
+                       case MOVETYPE_FLY:
+                               other.movetype = MOVETYPE_TOSS;
+                               other.gravity = 1;
+                               break;
+                       case MOVETYPE_BOUNCEMISSILE:
+                               other.movetype = MOVETYPE_BOUNCE;
+                               other.gravity = 1;
+                               break;
+               }
+               UpdateCSQCProjectile(other);
+       }
+
+       if (self.spawnflags & PUSH_ONCE)
+       {
+               self.touch = func_null;
+               self.think = SUB_Remove;
+               self.nextthink = time;
+       }
+#endif
+}
+
+#ifdef SVQC
+void trigger_push_link();
+void trigger_push_updatelink();
+#endif
+void trigger_push_findtarget()
+{
+       entity t;
+       vector org;
+
+       // first calculate a typical start point for the jump
+       org = (self.absmin + self.absmax) * 0.5;
+       org_z = self.absmax.z - PL_MIN_z;
+
+       if (self.target)
+       {
+               float n = 0;
+               for(t = world; (t = find(t, targetname, self.target)); )
+               {
+                       ++n;
+#ifdef SVQC
+                       entity e = spawn();
+                       setorigin(e, org);
+                       setsize(e, PL_MIN, PL_MAX);
+                       e.velocity = trigger_push_calculatevelocity(org, t, self.height);
+                       tracetoss(e, e);
+                       if(e.movetype == MOVETYPE_NONE)
+                               waypoint_spawnforteleporter(self, trace_endpos, vlen(trace_endpos - org) / vlen(e.velocity));
+                       remove(e);
+#endif
+               }
+
+               if(!n)
+               {
+                       // no dest!
+#ifdef SVQC
+                       objerror ("Jumppad with nonexistant target");
+#endif
+                       return;
+               }
+               else if(n == 1)
+               {
+                       // exactly one dest - bots love that
+                       self.enemy = find(world, targetname, self.target);
+               }
+               else
+               {
+                       // have to use random selection every single time
+                       self.enemy = world;
+               }
+       }
+#ifdef SVQC
+       else
+       {
+               entity e = spawn();
+               setorigin(e, org);
+               setsize(e, PL_MIN, PL_MAX);
+               e.velocity = self.movedir;
+               tracetoss(e, e);
+               waypoint_spawnforteleporter(self, trace_endpos, vlen(trace_endpos - org) / vlen(e.velocity));
+               remove(e);
+       }
+
+       trigger_push_link();
+       defer(0.1, trigger_push_updatelink);
+#endif
+}
+
+#ifdef SVQC
+float trigger_push_send(entity to, float sf)
+{
+       WriteByte(MSG_ENTITY, ENT_CLIENT_TRIGGER_PUSH);
+       WriteByte(MSG_ENTITY, sf);
+
+       if(sf & 1)
+       {
+               WriteByte(MSG_ENTITY, self.team);
+               WriteInt24_t(MSG_ENTITY, self.spawnflags);
+               WriteByte(MSG_ENTITY, self.active);
+               WriteByte(MSG_ENTITY, self.height);
+
+               trigger_common_write(true);
+       }
+
+       if(sf & 2)
+       {
+               WriteByte(MSG_ENTITY, self.team);
+               WriteByte(MSG_ENTITY, self.active);
+       }
+
+       return true;
+}
+
+void trigger_push_updatelink()
+{
+       self.SendFlags |= 1;
+}
+
+void trigger_push_link()
+{
+       Net_LinkEntity(self, false, 0, trigger_push_send);
+}
+#endif
+#ifdef SVQC
+/*
+ * ENTITY PARAMETERS:
+ *
+ *   target:  target of jump
+ *   height:  the absolute value is the height of the highest point of the jump
+ *            trajectory above the higher one of the player and the target.
+ *            the sign indicates whether the highest point is INSIDE (positive)
+ *            or OUTSIDE (negative) of the jump trajectory. General rule: use
+ *            positive values for targets mounted on the floor, and use negative
+ *            values to target a point on the ceiling.
+ *   movedir: if target is not set, this * speed * 10 is the velocity to be reached.
+ */
+void spawnfunc_trigger_push()
+{
+       SetMovedir ();
+
+       EXACTTRIGGER_INIT;
+
+       self.active = ACTIVE_ACTIVE;
+       self.use = trigger_push_use;
+       self.trigger_touch = trigger_push_touch;
+       self.think = trigger_think_generic;
+       self.nextthink = time;
+
+       // normal push setup
+       if (!self.speed)
+               self.speed = 1000;
+       self.movedir = self.movedir * self.speed * 10;
+
+       if (!self.noise)
+               self.noise = "misc/jumppad.wav";
+       precache_sound (self.noise);
+
+       // this must be called to spawn the teleport waypoints for bots
+       InitializeEntity(self, trigger_push_findtarget, INITPRIO_FINDTARGET);
+}
+
+
+float target_push_send(entity to, float sf)
+{
+       WriteByte(MSG_ENTITY, ENT_CLIENT_TARGET_PUSH);
+
+       WriteByte(MSG_ENTITY, self.cnt);
+       WriteString(MSG_ENTITY, self.targetname);
+       WriteCoord(MSG_ENTITY, self.origin_x);
+       WriteCoord(MSG_ENTITY, self.origin_y);
+       WriteCoord(MSG_ENTITY, self.origin_z);
+
+       return true;
+}
+
+void target_push_link()
+{
+       Net_LinkEntity(self, false, 0, target_push_send);
+       self.SendFlags |= 1; // update
+}
+
+void spawnfunc_target_push() { target_push_link(); }
+void spawnfunc_info_notnull() { target_push_link(); }
+void spawnfunc_target_position() { target_push_link(); }
+
+#endif
+
+#ifdef CSQC
+
+void ent_trigger_push()
+{
+       float sf = ReadByte();
+
+       if(sf & 1)
+       {
+               self.classname = "jumppad";
+               int mytm = ReadByte(); if(mytm) { self.team = mytm - 1; }
+               self.spawnflags = ReadInt24_t();
+               self.active = ReadByte();
+               self.height = ReadByte();
+
+               trigger_common_read(true);
+
+               self.entremove = trigger_remove_generic;
+               self.solid = SOLID_TRIGGER;
+               self.draw = trigger_draw_generic;
+               self.trigger_touch = trigger_push_touch;
+               self.drawmask = MASK_NORMAL;
+               self.move_time = time;
+               trigger_push_findtarget();
+       }
+
+       if(sf & 2)
+       {
+               self.team = ReadByte();
+               self.active = ReadByte();
+       }
+}
+
+void target_push_remove()
+{
+       if(self.classname)
+               strunzone(self.classname);
+       self.classname = string_null;
+
+       if(self.targetname)
+               strunzone(self.targetname);
+       self.targetname = string_null;
+}
+
+void ent_target_push()
+{
+       self.classname = "push_target";
+       self.cnt = ReadByte();
+       self.targetname = strzone(ReadString());
+       self.origin_x = ReadCoord();
+       self.origin_y = ReadCoord();
+       self.origin_z = ReadCoord();
+       setorigin(self, self.origin);
+
+       self.drawmask = MASK_NORMAL;
+       self.entremove = target_push_remove;
+}
+#endif
diff --git a/qcsrc/common/triggers/trigger/jumppads.qh b/qcsrc/common/triggers/trigger/jumppads.qh
new file mode 100644 (file)
index 0000000..bb0f475
--- /dev/null
@@ -0,0 +1,68 @@
+#ifndef T_JUMPPADS_H
+#define T_JUMPPADS_H
+
+const float PUSH_ONCE          = 1;
+const float PUSH_SILENT                = 2;
+
+.float pushltime;
+.float istypefrag;
+.float height;
+
+const int NUM_JUMPPADSUSED = 3;
+.float jumppadcount;
+.entity jumppadsused[NUM_JUMPPADSUSED];
+
+float trigger_push_calculatevelocity_flighttime;
+
+#ifdef SVQC
+void() SUB_UseTargets;
+void trigger_push_use();
+#endif
+
+#ifdef CSQC
+void ent_trigger_push();
+
+void ent_target_push();
+#endif
+
+/*
+       trigger_push_calculatevelocity
+
+       Arguments:
+         org - origin of the object which is to be pushed
+         tgt - target entity (can be either a point or a model entity; if it is
+               the latter, its midpoint is used)
+         ht  - jump height, measured from the higher one of org and tgt's midpoint
+
+       Returns: velocity for the jump
+       the global trigger_push_calculatevelocity_flighttime is set to the total
+       jump time
+ */
+
+vector trigger_push_calculatevelocity(vector org, entity tgt, float ht);
+
+void trigger_push_touch();
+
+.vector dest;
+void trigger_push_findtarget();
+
+/*
+ * ENTITY PARAMETERS:
+ *
+ *   target:  target of jump
+ *   height:  the absolute value is the height of the highest point of the jump
+ *            trajectory above the higher one of the player and the target.
+ *            the sign indicates whether the highest point is INSIDE (positive)
+ *            or OUTSIDE (negative) of the jump trajectory. General rule: use
+ *            positive values for targets mounted on the floor, and use negative
+ *            values to target a point on the ceiling.
+ *   movedir: if target is not set, this * speed * 10 is the velocity to be reached.
+ */
+#ifdef SVQC
+void spawnfunc_trigger_push();
+
+void spawnfunc_target_push();
+void spawnfunc_info_notnull();
+void spawnfunc_target_position();
+#endif
+#endif
diff --git a/qcsrc/common/triggers/trigger/keylock.qc b/qcsrc/common/triggers/trigger/keylock.qc
new file mode 100644 (file)
index 0000000..ec27d4b
--- /dev/null
@@ -0,0 +1,212 @@
+/**
+ * trigger given targets
+ */
+void trigger_keylock_trigger(string s)
+{
+       entity stemp = self;
+       entity otemp = other;
+       entity atemp = activator;
+
+       entity t;
+       for(t = world; (t = find(t, targetname, s)); )
+               if(t.use)
+               {
+                       self = t;
+                       other = stemp;
+                       activator = atemp;
+                       self.use();
+               }
+
+       self = stemp;
+       other = otemp;
+       activator = atemp;
+}
+
+/**
+ * kill killtarget of trigger keylock.
+ */
+void trigger_keylock_kill(string s)
+{
+       entity t;
+       for(t = world; (t = find(t, targetname, s)); )
+               remove(t);
+}
+
+void trigger_keylock_touch()
+{
+       bool key_used = false;
+       bool started_delay = false;
+
+       // only player may trigger the lock
+       if(!IS_PLAYER(other))
+               return;
+
+       // check silver key
+       if(self.itemkeys)
+               key_used = item_keys_usekey(self, other);
+
+       activator = other;
+
+       if(self.itemkeys)
+       {
+#ifdef SVQC
+               // at least one of the keys is missing
+               if(key_used)
+               {
+                       // one or more keys were given, but others are still missing!
+                       play2(other, self.noise1);
+                       Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_LOCKED_ALSONEED, item_keys_keylist(self.itemkeys));
+                       other.key_door_messagetime = time + 2;
+               }
+               else if(other.key_door_messagetime <= time)
+               {
+                       // no keys were given
+                       play2(other, self.noise2);
+                       Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_LOCKED_NEED, item_keys_keylist(self.itemkeys));
+                       other.key_door_messagetime = time + 2;
+               }
+#endif
+
+               // trigger target2
+               if(self.delay <= time || started_delay == true)
+               if(self.target2)
+               {
+                       trigger_keylock_trigger(self.target2);
+                       started_delay = true;
+                       self.delay = time + self.wait;
+               }
+       }
+       else
+       {
+#ifdef SVQC
+               // all keys were given!
+               play2(other, self.noise);
+               centerprint(other, self.message);
+#endif
+
+               if(self.target)
+                       trigger_keylock_trigger(self.target);
+
+               if(self.killtarget)
+                       trigger_keylock_kill(self.killtarget);
+
+               remove(self);
+       }
+
+}
+
+#ifdef SVQC
+bool trigger_keylock_send(entity to, int sf)
+{
+       WriteByte(MSG_ENTITY, ENT_CLIENT_KEYLOCK);
+
+       WriteInt24_t(MSG_ENTITY, self.itemkeys);
+       WriteByte(MSG_ENTITY, self.height);
+
+       trigger_common_write(true);
+
+       return true;
+}
+
+void trigger_keylock_link()
+{
+       // uncomment to network keylocks
+       //Net_LinkEntity(self, false, 0, trigger_keylock_send);
+}
+
+/*QUAKED trigger_keylock (.0 .5 .8) ?
+Keylock trigger.  Must target other entities.
+This trigger will trigger target entities when all required keys are provided.
+-------- KEYS --------
+itemkeys: A bit field with key IDs that are needed to open this lock.
+sounds: 1 to play misc/secret.wav, 2 to play misc/talk.wav, 3 to play misc/trigger1.wav (3 is default)
+target: trigger all entities with this targetname when triggered and all keys have been given to it, then remove this trigger
+target2: trigger all entities with this targetname when triggered without giving it all the required keys.
+killtarget: remove all entities with this targetname when triggered with all the needed keys.
+message: print this message to the player who activated the trigger when all needed keys have been given.
+message2: print this message to the player who activated the trigger when not all of the needed keys have been given.
+noise: sound to play when lock gets unlocked (default: see sounds)
+noise1: sound to play when only some of the needed key were used but not all (default: misc/decreasevalue.wav)
+noise2: sound to play when a key is missing (default: misc/talk.wav)
+wait: prevent triggering again for this amount of time (default: 5) - applies to target2, target3, target4.
+---------NOTES----------
+If spawned without any key specified in itemkeys, this trigger will display an error and remove itself.
+message2 and noise2 will be resent to the player every 2 seconds while he is in the trigger zone.
+*/
+void spawnfunc_trigger_keylock(void)
+{
+       if(!self.itemkeys) { remove(self); return; }
+
+       // set unlocked message
+       if(self.message == "")
+               self.message = "Unlocked!";
+
+       // set default unlock noise
+       if(self.noise == "")
+       {
+               if(self.sounds == 1)
+                       self.noise = "misc/secret.wav";
+               else if(self.sounds == 2)
+                       self.noise = "misc/talk.wav";
+               else //if (self.sounds == 3) {
+                       self.noise = "misc/trigger1.wav";
+       }
+
+       // set default use key sound
+       if(self.noise1 == "")
+               self.noise1 = "misc/decreasevalue.wav";
+
+       // set closed sourd
+       if(self.noise2 == "")
+               self.noise2 = "misc/talk.wav";
+
+       // delay between triggering message2 and trigger2
+       if(!self.wait) { self.wait = 5; }
+
+       // precache sounds
+       precache_sound(self.noise);
+       precache_sound(self.noise1);
+       precache_sound(self.noise2);
+
+       EXACTTRIGGER_INIT;
+
+       self.touch = trigger_keylock_touch;
+
+       trigger_keylock_link();
+}
+#elif defined(CSQC)
+void keylock_remove()
+{
+       if(self.target) { strunzone(self.target); }
+       self.target = string_null;
+
+       if(self.target2) { strunzone(self.target2); }
+       self.target2 = string_null;
+
+       if(self.target3) { strunzone(self.target3); }
+       self.target3 = string_null;
+
+       if(self.target4) { strunzone(self.target4); }
+       self.target4 = string_null;
+
+       if(self.killtarget) { strunzone(self.killtarget); }
+       self.killtarget = string_null;
+
+       if(self.targetname) { strunzone(self.targetname); }
+       self.targetname = string_null;
+}
+
+void ent_keylock()
+{
+       self.itemkeys = ReadInt24_t();
+       self.height = ReadByte();
+
+       trigger_common_read(true);
+
+       self.classname = "trigger_keylock";
+       self.drawmask = MASK_NORMAL;
+       self.draw = trigger_draw_generic;
+       self.trigger_touch = trigger_keylock_touch;
+       self.entremove = keylock_remove;
+}
+#endif
diff --git a/qcsrc/common/triggers/trigger/keylock.qh b/qcsrc/common/triggers/trigger/keylock.qh
new file mode 100644 (file)
index 0000000..b21145d
--- /dev/null
@@ -0,0 +1,20 @@
+#ifdef CSQC
+void ent_keylock();
+bool item_keys_usekey(entity l, entity p)
+{
+       float valid = l.itemkeys & p.itemkeys;
+
+       if (!valid) {
+               // other has none of the needed keys
+               return false;
+       } else if (l.itemkeys == valid) {
+               // ALL needed keys were given
+               l.itemkeys = 0;
+               return true;
+       } else {
+               // only some of the needed keys were given
+               l.itemkeys &= ~valid;
+               return true;
+       }
+}
+#endif
diff --git a/qcsrc/common/triggers/trigger/magicear.qc b/qcsrc/common/triggers/trigger/magicear.qc
new file mode 100644 (file)
index 0000000..f14b75c
--- /dev/null
@@ -0,0 +1,204 @@
+#ifdef SVQC
+float magicear_matched;
+float W_Tuba_HasPlayed(entity pl, string melody, float instrument, float ignorepitch, float mintempo, float maxtempo);
+string trigger_magicear_processmessage(entity ear, entity source, float teamsay, entity privatesay, string msgin)
+{
+       float domatch, dotrigger, matchstart, l;
+       string s, msg;
+       entity oldself;
+       string savemessage;
+
+       magicear_matched = false;
+
+       dotrigger = ((IS_PLAYER(source)) && (source.deadflag == DEAD_NO) && ((ear.radius == 0) || (vlen(source.origin - ear.origin) <= ear.radius)));
+       domatch = ((ear.spawnflags & 32) || dotrigger);
+
+       if (!domatch)
+               return msgin;
+
+       if (!msgin)
+       {
+               // we are in TUBA mode!
+               if (!(ear.spawnflags & 256))
+                       return msgin;
+
+               if(!W_Tuba_HasPlayed(source, ear.message, ear.movedir_x, !(ear.spawnflags & 512), ear.movedir_y, ear.movedir_z))
+                       return msgin;
+
+               magicear_matched = true;
+
+               if(dotrigger)
+               {
+                       oldself = self;
+                       activator = source;
+                       self = ear;
+                       savemessage = self.message;
+                       self.message = string_null;
+                       SUB_UseTargets();
+                       self.message = savemessage;
+                       self = oldself;
+               }
+
+               if(ear.netname != "")
+                       return ear.netname;
+
+               return msgin;
+       }
+
+       if(ear.spawnflags & 256) // ENOTUBA
+               return msgin;
+
+       if(privatesay)
+       {
+               if(ear.spawnflags & 4)
+                       return msgin;
+       }
+       else
+       {
+               if(!teamsay)
+                       if(ear.spawnflags & 1)
+                               return msgin;
+               if(teamsay > 0)
+                       if(ear.spawnflags & 2)
+                               return msgin;
+               if(teamsay < 0)
+                       if(ear.spawnflags & 8)
+                               return msgin;
+       }
+
+       matchstart = -1;
+       l = strlen(ear.message);
+
+       if(ear.spawnflags & 128)
+               msg = msgin;
+       else
+               msg = strdecolorize(msgin);
+
+       if(substring(ear.message, 0, 1) == "*")
+       {
+               if(substring(ear.message, -1, 1) == "*")
+               {
+                       // two wildcards
+                       // as we need multi-replacement here...
+                       s = substring(ear.message, 1, -2);
+                       l -= 2;
+                       if(strstrofs(msg, s, 0) >= 0)
+                               matchstart = -2; // we use strreplace on s
+               }
+               else
+               {
+                       // match at start
+                       s = substring(ear.message, 1, -1);
+                       l -= 1;
+                       if(substring(msg, -l, l) == s)
+                               matchstart = strlen(msg) - l;
+               }
+       }
+       else
+       {
+               if(substring(ear.message, -1, 1) == "*")
+               {
+                       // match at end
+                       s = substring(ear.message, 0, -2);
+                       l -= 1;
+                       if(substring(msg, 0, l) == s)
+                               matchstart = 0;
+               }
+               else
+               {
+                       // full match
+                       s = ear.message;
+                       if(msg == ear.message)
+                               matchstart = 0;
+               }
+       }
+
+       if(matchstart == -1) // no match
+               return msgin;
+
+       magicear_matched = true;
+
+       if(dotrigger)
+       {
+               oldself = self;
+               activator = source;
+               self = ear;
+               savemessage = self.message;
+               self.message = string_null;
+               SUB_UseTargets();
+               self.message = savemessage;
+               self = oldself;
+       }
+
+       if(ear.spawnflags & 16)
+       {
+               return ear.netname;
+       }
+       else if(ear.netname != "")
+       {
+               if(matchstart < 0)
+                       return strreplace(s, ear.netname, msg);
+               else
+                       return strcat(
+                               substring(msg, 0, matchstart),
+                               ear.netname,
+                               substring(msg, matchstart + l, -1)
+                       );
+       }
+       else
+               return msgin;
+}
+
+entity magicears;
+string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin)
+{
+       entity ear;
+       string msgout;
+       for(ear = magicears; ear; ear = ear.enemy)
+       {
+               msgout = trigger_magicear_processmessage(ear, source, teamsay, privatesay, msgin);
+               if(!(ear.spawnflags & 64))
+               if(magicear_matched)
+                       return msgout;
+               msgin = msgout;
+       }
+       return msgin;
+}
+
+void spawnfunc_trigger_magicear()
+{
+       self.enemy = magicears;
+       magicears = self;
+
+       // actually handled in "say" processing
+       // spawnflags:
+       //    1 = ignore say
+       //    2 = ignore teamsay
+       //    4 = ignore tell
+       //    8 = ignore tell to unknown player
+       //   16 = let netname replace the whole message (otherwise, netname is a word replacement if set)
+       //   32 = perform the replacement even if outside the radius or dead
+       //   64 = continue replacing/triggering even if this one matched
+       //  128 = don't decolorize message before matching
+       //  256 = message is a tuba note sequence (pitch.duration pitch.duration ...)
+       //  512 = tuba notes must be exact right pitch, no transposing
+       // message: either
+       //   *pattern*
+       // or
+       //   *pattern
+       // or
+       //   pattern*
+       // or
+       //   pattern
+       // netname:
+       //   if set, replacement for the matched text
+       // radius:
+       //   "hearing distance"
+       // target:
+       //   what to trigger
+       // movedir:
+       //   for spawnflags 256, defines 'instrument+1 mintempo maxtempo' (zero component doesn't matter)
+
+       self.movedir_x -= 1; // map to tuba instrument numbers
+}
+#endif
diff --git a/qcsrc/common/triggers/trigger/monoflop.qc b/qcsrc/common/triggers/trigger/monoflop.qc
new file mode 100644 (file)
index 0000000..45ce761
--- /dev/null
@@ -0,0 +1,49 @@
+#ifdef SVQC
+/*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
+"Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
+*/
+void monoflop_use()
+{
+       self.nextthink = time + self.wait;
+       self.enemy = activator;
+       if(self.state)
+               return;
+       self.state = 1;
+       SUB_UseTargets();
+}
+void monoflop_fixed_use()
+{
+       if(self.state)
+               return;
+       self.nextthink = time + self.wait;
+       self.state = 1;
+       self.enemy = activator;
+       SUB_UseTargets();
+}
+
+void monoflop_think()
+{
+       self.state = 0;
+       activator = self.enemy;
+       SUB_UseTargets();
+}
+
+void monoflop_reset()
+{
+       self.state = 0;
+       self.nextthink = 0;
+}
+
+void spawnfunc_trigger_monoflop()
+{
+       if(!self.wait)
+               self.wait = 1;
+       if(self.spawnflags & 1)
+               self.use = monoflop_fixed_use;
+       else
+               self.use = monoflop_use;
+       self.think = monoflop_think;
+       self.state = 0;
+       self.reset = monoflop_reset;
+}
+#endif
diff --git a/qcsrc/common/triggers/trigger/multi.qc b/qcsrc/common/triggers/trigger/multi.qc
new file mode 100644 (file)
index 0000000..ae73aaf
--- /dev/null
@@ -0,0 +1,204 @@
+// NOTE: also contains trigger_once at bottom
+
+#ifdef SVQC
+// the wait time has passed, so set back up for another activation
+void multi_wait()
+{
+       if (self.max_health)
+       {
+               self.health = self.max_health;
+               self.takedamage = DAMAGE_YES;
+               self.solid = SOLID_BBOX;
+       }
+}
+
+
+// the trigger was just touched/killed/used
+// self.enemy should be set to the activator so it can be held through a delay
+// so wait for the delay time before firing
+void multi_trigger()
+{
+       if (self.nextthink > time)
+       {
+               return;         // allready been triggered
+       }
+
+       if (self.classname == "trigger_secret")
+       {
+               if (!IS_PLAYER(self.enemy))
+                       return;
+               found_secrets = found_secrets + 1;
+               WriteByte (MSG_ALL, SVC_FOUNDSECRET);
+       }
+
+       if (self.noise)
+               sound (self.enemy, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
+
+// don't trigger again until reset
+       self.takedamage = DAMAGE_NO;
+
+       activator = self.enemy;
+       other = self.goalentity;
+       SUB_UseTargets();
+
+       if (self.wait > 0)
+       {
+               self.think = multi_wait;
+               self.nextthink = time + self.wait;
+       }
+       else if (self.wait == 0)
+       {
+               multi_wait(); // waiting finished
+       }
+       else
+       {       // we can't just remove (self) here, because this is a touch function
+               // called wheil C code is looping through area links...
+               self.touch = func_null;
+       }
+}
+
+void multi_use()
+{
+       self.goalentity = other;
+       self.enemy = activator;
+       multi_trigger();
+}
+
+void multi_touch()
+{
+       if(!(self.spawnflags & 2))
+       if(!other.iscreature)
+                       return;
+
+       if(self.team)
+               if(((self.spawnflags & 4) == 0) == (self.team != other.team))
+                       return;
+
+// if the trigger has an angles field, check player's facing direction
+       if (self.movedir != '0 0 0')
+       {
+               makevectors (other.angles);
+               if (v_forward * self.movedir < 0)
+                       return;         // not facing the right way
+       }
+
+       EXACTTRIGGER_TOUCH;
+
+       self.enemy = other;
+       self.goalentity = other;
+       multi_trigger ();
+}
+
+void multi_eventdamage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
+{
+       if (!self.takedamage)
+               return;
+       if(self.spawnflags & DOOR_NOSPLASH)
+               if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
+                       return;
+       self.health = self.health - damage;
+       if (self.health <= 0)
+       {
+               self.enemy = attacker;
+               self.goalentity = inflictor;
+               multi_trigger();
+       }
+}
+
+void multi_reset()
+{
+       if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
+               self.touch = multi_touch;
+       if (self.max_health)
+       {
+               self.health = self.max_health;
+               self.takedamage = DAMAGE_YES;
+               self.solid = SOLID_BBOX;
+       }
+       self.think = func_null;
+       self.nextthink = 0;
+       self.team = self.team_saved;
+}
+
+/*QUAKED spawnfunc_trigger_multiple (.5 .5 .5) ? notouch
+Variable sized repeatable trigger.  Must be targeted at one or more entities.  If "health" is set, the trigger must be killed to activate each time.
+If "delay" is set, the trigger waits some time after activating before firing.
+"wait" : Seconds between triggerings. (.2 default)
+If notouch is set, the trigger is only fired by other entities, not by touching.
+NOTOUCH has been obsoleted by spawnfunc_trigger_relay!
+sounds
+1)     secret
+2)     beep beep
+3)     large switch
+4)
+set "message" to text string
+*/
+void spawnfunc_trigger_multiple()
+{
+       self.reset = multi_reset;
+       if (self.sounds == 1)
+       {
+               precache_sound ("misc/secret.wav");
+               self.noise = "misc/secret.wav";
+       }
+       else if (self.sounds == 2)
+       {
+               precache_sound ("misc/talk.wav");
+               self.noise = "misc/talk.wav";
+       }
+       else if (self.sounds == 3)
+       {
+               precache_sound ("misc/trigger1.wav");
+               self.noise = "misc/trigger1.wav";
+       }
+
+       if (!self.wait)
+               self.wait = 0.2;
+       else if(self.wait < -1)
+               self.wait = 0;
+       self.use = multi_use;
+
+       EXACTTRIGGER_INIT;
+
+       self.team_saved = self.team;
+
+       if (self.health)
+       {
+               if (self.spawnflags & SPAWNFLAG_NOTOUCH)
+                       objerror ("health and notouch don't make sense\n");
+               self.max_health = self.health;
+               self.event_damage = multi_eventdamage;
+               self.takedamage = DAMAGE_YES;
+               self.solid = SOLID_BBOX;
+               setorigin (self, self.origin);  // make sure it links into the world
+       }
+       else
+       {
+               if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
+               {
+                       self.touch = multi_touch;
+                       setorigin (self, self.origin);  // make sure it links into the world
+               }
+       }
+}
+
+
+/*QUAKED spawnfunc_trigger_once (.5 .5 .5) ? notouch
+Variable sized trigger. Triggers once, then removes itself.  You must set the key "target" to the name of another object in the level that has a matching
+"targetname".  If "health" is set, the trigger must be killed to activate.
+If notouch is set, the trigger is only fired by other entities, not by touching.
+if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired.
+if "angle" is set, the trigger will only fire when someone is facing the direction of the angle.  Use "360" for an angle of 0.
+sounds
+1)     secret
+2)     beep beep
+3)     large switch
+4)
+set "message" to text string
+*/
+void spawnfunc_trigger_once()
+{
+       self.wait = -1;
+       spawnfunc_trigger_multiple();
+}
+#endif
diff --git a/qcsrc/common/triggers/trigger/multi.qh b/qcsrc/common/triggers/trigger/multi.qh
new file mode 100644 (file)
index 0000000..df14a51
--- /dev/null
@@ -0,0 +1,6 @@
+#ifdef SVQC
+void multi_trigger();
+void multi_reset();
+
+void spawnfunc_trigger_once();
+#endif
diff --git a/qcsrc/common/triggers/trigger/multivibrator.qc b/qcsrc/common/triggers/trigger/multivibrator.qc
new file mode 100644 (file)
index 0000000..02a258e
--- /dev/null
@@ -0,0 +1,73 @@
+#ifdef SVQC
+void multivibrator_send()
+{
+       float newstate;
+       float cyclestart;
+
+       cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
+
+       newstate = (time < cyclestart + self.wait);
+
+       activator = self;
+       if(self.state != newstate)
+               SUB_UseTargets();
+       self.state = newstate;
+
+       if(self.state)
+               self.nextthink = cyclestart + self.wait + 0.01;
+       else
+               self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
+}
+
+void multivibrator_toggle()
+{
+       if(self.nextthink == 0)
+       {
+               multivibrator_send();
+       }
+       else
+       {
+               if(self.state)
+               {
+                       SUB_UseTargets();
+                       self.state = 0;
+               }
+               self.nextthink = 0;
+       }
+}
+
+void multivibrator_reset()
+{
+       if(!(self.spawnflags & 1))
+               self.nextthink = 0; // wait for a trigger event
+       else
+               self.nextthink = max(1, time);
+}
+
+/*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
+"Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
+-------- KEYS --------
+target: trigger all entities with this targetname when it goes off
+targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
+phase: offset of the timing
+wait: "on" cycle time (default: 1)
+respawntime: "off" cycle time (default: same as wait)
+-------- SPAWNFLAGS --------
+START_ON: assume it is already turned on (when targeted)
+*/
+void spawnfunc_trigger_multivibrator()
+{
+       if(!self.wait)
+               self.wait = 1;
+       if(!self.respawntime)
+               self.respawntime = self.wait;
+
+       self.state = 0;
+       self.use = multivibrator_toggle;
+       self.think = multivibrator_send;
+       self.nextthink = max(1, time);
+
+       IFTARGETED
+               multivibrator_reset();
+}
+#endif
diff --git a/qcsrc/common/triggers/trigger/relay.qc b/qcsrc/common/triggers/trigger/relay.qc
new file mode 100644 (file)
index 0000000..e037028
--- /dev/null
@@ -0,0 +1,10 @@
+#ifdef SVQC
+/*QUAKED spawnfunc_trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
+This fixed size trigger cannot be touched, it can only be fired by other events.  It can contain killtargets, targets, delays, and messages.
+*/
+void spawnfunc_trigger_relay()
+{
+       self.use = SUB_UseTargets;
+       self.reset = spawnfunc_trigger_relay; // this spawnfunc resets fully
+}
+#endif
diff --git a/qcsrc/common/triggers/trigger/relay_activators.qc b/qcsrc/common/triggers/trigger/relay_activators.qc
new file mode 100644 (file)
index 0000000..83c0103
--- /dev/null
@@ -0,0 +1,45 @@
+#ifdef SVQC
+void relay_activators_use()
+{
+       entity trg, os;
+
+       os = self;
+
+       for(trg = world; (trg = find(trg, targetname, os.target)); )
+       {
+               self = trg;
+               if (trg.setactive)
+                       trg.setactive(os.cnt);
+               else
+               {
+                       //bprint("Not using setactive\n");
+                       if(os.cnt == ACTIVE_TOGGLE)
+                               if(trg.active == ACTIVE_ACTIVE)
+                                       trg.active = ACTIVE_NOT;
+                               else
+                                       trg.active = ACTIVE_ACTIVE;
+                       else
+                               trg.active = os.cnt;
+               }
+       }
+       self = os;
+}
+
+void spawnfunc_relay_activate()
+{
+       self.cnt = ACTIVE_ACTIVE;
+       self.use = relay_activators_use;
+}
+
+void spawnfunc_relay_deactivate()
+{
+       self.cnt = ACTIVE_NOT;
+       self.use = relay_activators_use;
+}
+
+void spawnfunc_relay_activatetoggle()
+{
+       self.cnt = ACTIVE_TOGGLE;
+       self.use = relay_activators_use;
+}
+#endif
diff --git a/qcsrc/common/triggers/trigger/relay_if.qc b/qcsrc/common/triggers/trigger/relay_if.qc
new file mode 100644 (file)
index 0000000..ade56c1
--- /dev/null
@@ -0,0 +1,20 @@
+#ifdef SVQC
+void trigger_relay_if_use()
+{
+       float n;
+       n = self.count;
+
+       // TODO make this generic AND faster than nextent()ing through all, if somehow possible
+       n = (cvar_string(self.netname) == cvar_string(self.message));
+       if(self.spawnflags & 1)
+               n = !n;
+
+       if(n)
+               SUB_UseTargets();
+}
+
+void spawnfunc_trigger_relay_if()
+{
+       self.use = trigger_relay_if_use;
+}
+#endif
diff --git a/qcsrc/common/triggers/trigger/relay_teamcheck.qc b/qcsrc/common/triggers/trigger/relay_teamcheck.qc
new file mode 100644 (file)
index 0000000..8a77cef
--- /dev/null
@@ -0,0 +1,35 @@
+#ifdef SVQC
+void trigger_relay_teamcheck_use()
+{
+       if(activator.team)
+       {
+               if(self.spawnflags & 2)
+               {
+                       if(DIFF_TEAM(activator, self))
+                               SUB_UseTargets();
+               }
+               else
+               {
+                       if(SAME_TEAM(activator, self))
+                               SUB_UseTargets();
+               }
+       }
+       else
+       {
+               if(self.spawnflags & 1)
+                       SUB_UseTargets();
+       }
+}
+
+void trigger_relay_teamcheck_reset()
+{
+       self.team = self.team_saved;
+}
+
+void spawnfunc_trigger_relay_teamcheck()
+{
+       self.team_saved = self.team;
+       self.use = trigger_relay_teamcheck_use;
+       self.reset = trigger_relay_teamcheck_reset;
+}
+#endif
diff --git a/qcsrc/common/triggers/trigger/secret.qc b/qcsrc/common/triggers/trigger/secret.qc
new file mode 100644 (file)
index 0000000..1902b7e
--- /dev/null
@@ -0,0 +1,85 @@
+#if defined(CSQC)
+#elif defined(MENUQC)
+#elif defined(SVQC)
+       #include "../../../dpdefs/progsdefs.qh"
+    #include "../../util.qh"
+    #include "../../../server/defs.qh"
+    #include "secret.qh"
+#endif
+
+#ifdef SVQC
+
+void secrets_setstatus() {
+       self.stat_secrets_total = secrets_total;
+       self.stat_secrets_found = secrets_found;
+}
+
+/**
+ * A secret has been found (maybe :P)
+ */
+void trigger_secret_touch() {
+       // only a player can trigger this
+       if (!IS_PLAYER(other))
+               return;
+
+       // update secrets found counter
+       secrets_found += 1;
+       //print("Secret found: ", ftos(secret_counter.cnt), "/");
+       //print(ftos(secret_counter.count), "\n");
+
+       // centerprint message (multi_touch() doesn't always call centerprint())
+       centerprint(other, self.message);
+       self.message = "";
+
+       // handle normal trigger features
+       multi_touch();
+       remove(self);
+}
+
+/*QUAKED trigger_secret (.5 .5 .5) ?
+Variable sized secret trigger. Can be targeted at one or more entities.
+Basically, it's a trigger_once (with restrictions, see notes) that additionally updates the number of secrets found.
+-------- KEYS --------
+sounds: 1 to play misc/secret.wav, 2 to play misc/talk.wav, 3 to play misc/trigger1.wav (default: 1)
+noise: path to sound file, if you want to play something else
+target: trigger all entities with this targetname when triggered
+message: print this message to the player who activated the trigger instead of the standard 'You found a secret!'
+killtarget: remove all entities with this targetname when triggered
+-------- NOTES --------
+You should create a common/trigger textured brush covering the entrance to a secret room/area.
+Trigger secret can only be trigger by a player's touch and can not be a target itself.
+*/
+void spawnfunc_trigger_secret() {
+       // FIXME: should it be disabled in most modes?
+
+       // update secrets count
+       secrets_total += 1;
+
+       // add default message
+       if (self.message == "")
+               self.message = "You found a secret!";
+
+       // set default sound
+       if (self.noise == "")
+       if (!self.sounds)
+               self.sounds = 1; // misc/secret.wav
+
+       // this entity can't be a target itself!!!!
+       self.targetname = "";
+
+       // you can't just shoot a room to find it, can you?
+       self.health = 0;
+
+       // a secret can not be delayed
+       self.delay = 0;
+
+       // convert this trigger to trigger_once
+       self.classname = "trigger_once";
+       spawnfunc_trigger_once();
+
+       // take over the touch() function, so we can mark secret as found
+       self.touch = trigger_secret_touch;
+       // ignore triggering;
+       self.use = func_null;
+}
+#endif
diff --git a/qcsrc/common/triggers/trigger/secret.qh b/qcsrc/common/triggers/trigger/secret.qh
new file mode 100644 (file)
index 0000000..c09da6b
--- /dev/null
@@ -0,0 +1,24 @@
+#ifndef SECRET_H
+#define SECRET_H
+#ifdef SVQC
+
+/**
+ * Total number of secrets on the map.
+ */
+float secrets_total;
+
+/**
+ * Total numbe of secrets found on the map.
+ */
+float secrets_found;
+
+
+.float stat_secrets_total;
+.float stat_secrets_found;
+
+/**
+ * update secrets status.
+ */
+void secrets_setstatus();
+#endif
+#endif
diff --git a/qcsrc/common/triggers/trigger/swamp.qc b/qcsrc/common/triggers/trigger/swamp.qc
new file mode 100644 (file)
index 0000000..8013f3b
--- /dev/null
@@ -0,0 +1,154 @@
+#if defined(CSQC)
+#elif defined(MENUQC)
+#elif defined(SVQC)
+       #include "../../../dpdefs/progsdefs.qh"
+    #include "../../../warpzonelib/util_server.qh"
+    #include "../../weapons/weapons.qh"
+    #include "../../../server/defs.qh"
+    #include "../../deathtypes.qh"
+#endif
+
+/*
+*              t_swamp.c
+*              Adds spawnfunc_trigger_swamp and suppoart routines for xonotic 1.2.1+
+*              Author tZork (Jakob MG)
+*              jakob@games43.se
+*              2005 11 29
+*/
+
+.float swamp_interval; //Hurt players in swamp with this interval
+.float swamp_slowdown; //Players in swamp get slowd down by this mutch 0-1 is slowdown 1-~ is speedup (!?)
+.entity swampslug;
+
+#ifdef SVQC
+void spawnfunc_trigger_swamp(void);
+#endif
+void swamp_touch(void);
+void swampslug_think();
+
+
+/*
+* Uses a entity calld swampslug to handle players in the swamp
+* It works like this: When the plyer enters teh swamp the spawnfunc_trigger_swamp
+* attaches a new "swampslug" to the player. As long as the plyer is inside
+* the swamp the swamp gives the slug new health. But the slug slowly kills itself
+* so when the player goes outside the swamp, it dies and releases the player from the
+* swamps curses (dmg/slowdown)
+*
+* I do it this way becuz there is no "untouch" event.
+*/
+void swampslug_think(void)
+{
+       //Slowly kill the slug
+       self.health = self.health - 1;
+
+       //Slug dead? then remove curses.
+       if(self.health <= 0)
+       {
+               self.owner.in_swamp = 0;
+               remove(self);
+               //centerprint(self.owner,"Killing slug...\n");
+               return;
+       }
+
+       // Slug still alive, so we are still in the swamp
+       // Or we have exited it very recently.
+       // Do the damage and renew the timer.
+#ifdef SVQC
+       Damage (self.owner, self, self, self.dmg, DEATH_SWAMP, other.origin, '0 0 0');
+#endif
+
+       self.nextthink = time + self.swamp_interval;
+}
+
+void swamp_touch(void)
+{
+       // If whatever thats touching the swamp is not a player
+       // or if its a dead player, just dont care abt it.
+       if(!IS_PLAYER(other) || PHYS_DEAD(other))
+               return;
+
+       EXACTTRIGGER_TOUCH;
+
+       // Chech if player alredy got a swampslug.
+       if(other.in_swamp != 1)
+       {
+               // If not attach one.
+               //centerprint(other,"Entering swamp!\n");
+               other.swampslug = spawn();
+               other.swampslug.health = 2;
+               other.swampslug.think = swampslug_think;
+               other.swampslug.nextthink = time;
+               other.swampslug.owner = other;
+               other.swampslug.dmg = self.dmg;
+               other.swampslug.swamp_interval = self.swamp_interval;
+               other.swamp_slowdown = self.swamp_slowdown;
+               other.in_swamp = 1;
+               return;
+       }
+
+       //other.in_swamp = 1;
+
+       //Revitalize players swampslug
+       other.swampslug.health = 2;
+}
+
+#ifdef SVQC
+float swamp_send(entity to, float sf)
+{
+       WriteByte(MSG_ENTITY, ENT_CLIENT_LADDER);
+
+       WriteByte(MSG_ENTITY, self.dmg); // can probably get away with using a single byte here
+       WriteByte(MSG_ENTITY, self.swamp_slowdown);
+       WriteByte(MSG_ENTITY, self.swamp_interval);
+
+       trigger_common_write(false);
+
+       return true;
+}
+
+void swamp_link()
+{
+       Net_LinkEntity(self, false, 0, func_ladder_send);
+}
+
+/*QUAKED spawnfunc_trigger_swamp (.5 .5 .5) ?
+Players gettin into the swamp will
+get slowd down and damaged
+*/
+void spawnfunc_trigger_swamp(void)
+{
+       // Init stuff
+       EXACTTRIGGER_INIT;
+       self.touch = swamp_touch;
+
+       // Setup default keys, if missing
+       if(self.dmg <= 0)
+               self.dmg = 5;
+       if(self.swamp_interval <= 0)
+               self.swamp_interval = 1;
+       if(self.swamp_slowdown <= 0)
+               self.swamp_slowdown = 0.5;
+
+       swamp_link();
+}
+
+#elif defined(CSQC)
+
+void ent_swamp()
+{
+       self.dmg = ReadByte();
+       self.swamp_slowdown = ReadByte();
+       self.swamp_interval = ReadByte();
+
+       trigger_common_read(false);
+
+       self.classname = "trigger_swamp";
+       self.solid = SOLID_TRIGGER;
+       self.draw = trigger_draw_generic;
+       self.trigger_touch = swamp_touch;
+       self.drawmask = MASK_NORMAL;
+       self.move_time = time;
+       self.entremove = trigger_remove_generic;
+}
+#endif
diff --git a/qcsrc/common/triggers/trigger/swamp.qh b/qcsrc/common/triggers/trigger/swamp.qh
new file mode 100644 (file)
index 0000000..86b1431
--- /dev/null
@@ -0,0 +1,15 @@
+#ifndef TRIGGER_SWAMP_H
+#define TRIGGER_SWAMP_H
+
+.float swamp_interval; //Hurt players in swamp with this interval
+.float swamp_slowdown; //Players in swamp get slowd down by this mutch 0-1 is slowdown 1-~ is speedup (!?)
+.entity swampslug;
+
+.float in_swamp;              // bool
+.entity swampslug;            // Uses this to release from swamp ("untouch" fix)
+
+#ifdef CSQC
+void ent_swamp();
+#endif
+
+#endif
diff --git a/qcsrc/common/triggers/trigger/teleport.qc b/qcsrc/common/triggers/trigger/teleport.qc
new file mode 100644 (file)
index 0000000..5ff5fcf
--- /dev/null
@@ -0,0 +1,77 @@
+#ifdef SVQC
+void trigger_teleport_use()
+{
+       if(teamplay)
+               self.team = activator.team;
+#ifdef SVQC
+       self.SendFlags |= SF_TRIGGER_UPDATE;
+#endif
+}
+
+void Teleport_Touch (void)
+{
+       entity oldself;
+       string s;
+
+       if (self.active != ACTIVE_ACTIVE)
+               return;
+
+       if (!other.teleportable)
+               return;
+
+       if(other.vehicle)
+       if(!other.vehicle.teleportable)
+               return;
+
+       if(other.turrcaps_flags & TFL_TURRCAPS_ISTURRET)
+               return;
+
+       if(other.deadflag != DEAD_NO)
+               return;
+
+       if(self.team)
+               if(((self.spawnflags & 4) == 0) == (self.team != other.team))
+                       return;
+
+       EXACTTRIGGER_TOUCH;
+
+       if(IS_PLAYER(other))
+               RemoveGrapplingHook(other);
+
+       entity e;
+       e = Simple_TeleportPlayer(self, other);
+
+       activator = other;
+       s = self.target; self.target = string_null;
+       SUB_UseTargets();
+       if (!self.target) self.target = s;
+
+       oldself = self;
+       self = e;
+       SUB_UseTargets();
+       self = oldself;
+}
+
+void spawnfunc_trigger_teleport()
+{
+       self.angles = '0 0 0';
+
+       EXACTTRIGGER_INIT;
+
+       self.active = ACTIVE_ACTIVE;
+
+       self.use = trigger_teleport_use;
+
+       // this must be called to spawn the teleport waypoints for bots
+       InitializeEntity(self, teleport_findtarget, INITPRIO_FINDTARGET);
+
+       if (self.target == "")
+       {
+               objerror ("Teleporter with no target");
+               return;
+       }
+
+       self.teleport_next = teleport_first;
+       teleport_first = self;
+}
+#endif
diff --git a/qcsrc/common/triggers/triggers.qc b/qcsrc/common/triggers/triggers.qc
new file mode 100644 (file)
index 0000000..fdffbb9
--- /dev/null
@@ -0,0 +1,297 @@
+void SUB_DontUseTargets() { }
+
+void() SUB_UseTargets;
+
+void DelayThink()
+{
+       activator = self.enemy;
+       SUB_UseTargets ();
+       remove(self);
+}
+
+void FixSize(entity e)
+{
+       e.mins_x = rint(e.mins_x);
+       e.mins_y = rint(e.mins_y);
+       e.mins_z = rint(e.mins_z);
+
+       e.maxs_x = rint(e.maxs_x);
+       e.maxs_y = rint(e.maxs_y);
+       e.maxs_z = rint(e.maxs_z);
+}
+
+#ifdef SVQC
+void trigger_common_write(bool withtarget)
+{
+       WriteByte(MSG_ENTITY, self.warpzone_isboxy);
+       WriteByte(MSG_ENTITY, self.scale);
+
+       if(withtarget)
+       {
+               WriteString(MSG_ENTITY, self.target);
+               WriteString(MSG_ENTITY, self.target2);
+               WriteString(MSG_ENTITY, self.target3);
+               WriteString(MSG_ENTITY, self.target4);
+               WriteString(MSG_ENTITY, self.targetname);
+               WriteString(MSG_ENTITY, self.killtarget);
+       }
+
+       WriteCoord(MSG_ENTITY, self.origin_x);
+       WriteCoord(MSG_ENTITY, self.origin_y);
+       WriteCoord(MSG_ENTITY, self.origin_z);
+
+       WriteCoord(MSG_ENTITY, self.mins_x);
+       WriteCoord(MSG_ENTITY, self.mins_y);
+       WriteCoord(MSG_ENTITY, self.mins_z);
+       WriteCoord(MSG_ENTITY, self.maxs_x);
+       WriteCoord(MSG_ENTITY, self.maxs_y);
+       WriteCoord(MSG_ENTITY, self.maxs_z);
+
+       WriteCoord(MSG_ENTITY, self.movedir_x);
+       WriteCoord(MSG_ENTITY, self.movedir_y);
+       WriteCoord(MSG_ENTITY, self.movedir_z);
+
+       WriteCoord(MSG_ENTITY, self.angles_x);
+       WriteCoord(MSG_ENTITY, self.angles_y);
+       WriteCoord(MSG_ENTITY, self.angles_z);
+}
+
+#elif defined(CSQC)
+
+void trigger_common_read(bool withtarget)
+{
+       self.warpzone_isboxy = ReadByte();
+       self.scale = ReadByte();
+
+       if(withtarget)
+       {
+               self.target = strzone(ReadString());
+               self.target2 = strzone(ReadString());
+               self.target3 = strzone(ReadString());
+               self.target4 = strzone(ReadString());
+               self.targetname = strzone(ReadString());
+               self.killtarget = strzone(ReadString());
+       }
+
+       self.origin_x = ReadCoord();
+       self.origin_y = ReadCoord();
+       self.origin_z = ReadCoord();
+       setorigin(self, self.origin);
+
+       self.mins_x = ReadCoord();
+       self.mins_y = ReadCoord();
+       self.mins_z = ReadCoord();
+       self.maxs_x = ReadCoord();
+       self.maxs_y = ReadCoord();
+       self.maxs_z = ReadCoord();
+       setsize(self, self.mins, self.maxs);
+
+       self.movedir_x = ReadCoord();
+       self.movedir_y = ReadCoord();
+       self.movedir_z = ReadCoord();
+
+       self.angles_x = ReadCoord();
+       self.angles_y = ReadCoord();
+       self.angles_z = ReadCoord();
+}
+
+void trigger_remove_generic()
+{
+       if(self.target) { strunzone(self.target); }
+       self.target = string_null;
+
+       if(self.target2) { strunzone(self.target2); }
+       self.target2 = string_null;
+
+       if(self.target3) { strunzone(self.target3); }
+       self.target3 = string_null;
+
+       if(self.target4) { strunzone(self.target4); }
+       self.target4 = string_null;
+
+       if(self.targetname) { strunzone(self.targetname); }
+       self.target = string_null;
+
+       if(self.killtarget) { strunzone(self.killtarget); }
+       self.killtarget = string_null;
+}
+#endif
+
+/*
+==============================
+SUB_UseTargets
+
+the global "activator" should be set to the entity that initiated the firing.
+
+If self.delay is set, a DelayedUse entity will be created that will actually
+do the SUB_UseTargets after that many seconds have passed.
+
+Centerprints any self.message to the activator.
+
+Removes all entities with a targetname that match self.killtarget,
+and removes them, so some events can remove other triggers.
+
+Search for (string)targetname in all entities that
+match (string)self.target and call their .use function
+
+==============================
+*/
+void SUB_UseTargets()
+{
+       entity t, stemp, otemp, act;
+       string s;
+       float i;
+
+//
+// check for a delay
+//
+       if (self.delay)
+       {
+       // create a temp object to fire at a later time
+               t = spawn();
+               t.classname = "DelayedUse";
+               t.nextthink = time + self.delay;
+               t.think = DelayThink;
+               t.enemy = activator;
+               t.message = self.message;
+               t.killtarget = self.killtarget;
+               t.target = self.target;
+               t.target2 = self.target2;
+               t.target3 = self.target3;
+               t.target4 = self.target4;
+               return;
+       }
+
+
+//
+// print the message
+//
+#ifdef SVQC
+       if(self)
+       if(IS_PLAYER(activator) && self.message != "")
+       if(IS_REAL_CLIENT(activator))
+       {
+               centerprint(activator, self.message);
+               if (self.noise == "")
+                       play2(activator, "misc/talk.wav");
+       }
+
+//
+// kill the killtagets
+//
+       s = self.killtarget;
+       if (s != "")
+       {
+               for(t = world; (t = find(t, targetname, s)); )
+                       remove(t);
+       }
+#endif
+
+//
+// fire targets
+//
+       act = activator;
+       stemp = self;
+       otemp = other;
+
+       if(stemp.target_random)
+               RandomSelection_Init();
+
+       for(i = 0; i < 4; ++i)
+       {
+               switch(i)
+               {
+                       default:
+                       case 0: s = stemp.target; break;
+                       case 1: s = stemp.target2; break;
+                       case 2: s = stemp.target3; break;
+                       case 3: s = stemp.target4; break;
+               }
+               if (s != "")
+               {
+                       for(t = world; (t = find(t, targetname, s)); )
+                       if(t.use)
+                       {
+                               if(stemp.target_random)
+                               {
+                                       RandomSelection_Add(t, 0, string_null, 1, 0);
+                               }
+                               else
+                               {
+                                       self = t;
+                                       other = stemp;
+                                       activator = act;
+                                       self.use();
+                               }
+                       }
+               }
+       }
+
+       if(stemp.target_random && RandomSelection_chosen_ent)
+       {
+               self = RandomSelection_chosen_ent;
+               other = stemp;
+               activator = act;
+               self.use();
+       }
+
+       activator = act;
+       self = stemp;
+       other = otemp;
+}
+
+#ifdef SVQC
+void trigger_think_generic()
+{
+       self.nextthink = time;
+
+       entity e;
+       if(self.trigger_touch)
+       for(e = findradius((self.absmin + self.absmax) * 0.5, vlen(self.absmax - self.absmin) * 0.5 + 1); e; e = e.chain)
+       {
+               vector emin = e.absmin, emax = e.absmax;
+               if(self.solid == SOLID_BSP)
+               {
+                       emin -= '1 1 1';
+                       emax += '1 1 1';
+               }
+               if(boxesoverlap(emin, emax, self.absmin, self.absmax)) // quick
+               if(WarpZoneLib_BoxTouchesBrush(emin, emax, self, e)) // accurate
+               {
+                       other = e;
+                       self.trigger_touch();
+               }
+       }
+}
+#endif
+
+#ifdef CSQC
+void trigger_touch_generic(void() touchfunc)
+{
+       entity e;
+       for(e = findradius((self.absmin + self.absmax) * 0.5, vlen(self.absmax - self.absmin) * 0.5 + 1); e; e = e.chain)
+       if(e.isplayermodel || e.classname == "csqcprojectile")
+       {
+               vector emin = e.absmin, emax = e.absmax;
+               if(self.solid == SOLID_BSP)
+               {
+                       emin -= '1 1 1';
+                       emax += '1 1 1';
+               }
+               if(boxesoverlap(emin, emax, self.absmin, self.absmax)) // quick
+               if(WarpZoneLib_BoxTouchesBrush(emin, emax, self, e)) // accurate
+               {
+                       other = e;
+                       touchfunc();
+               }
+       }
+}
+void trigger_draw_generic()
+{
+       float dt = time - self.move_time;
+       self.move_time = time;
+       if(dt <= 0) { return; }
+
+       if(self.trigger_touch) { trigger_touch_generic(self.trigger_touch); }
+}
+#endif
diff --git a/qcsrc/common/triggers/triggers.qh b/qcsrc/common/triggers/triggers.qh
new file mode 100644 (file)
index 0000000..c50d95b
--- /dev/null
@@ -0,0 +1,58 @@
+#ifndef TRIGGERS_H
+#define TRIGGERS_H
+
+const float SF_TRIGGER_INIT = 1;
+const float SF_TRIGGER_UPDATE = 2;
+const float SF_TRIGGER_RESET = 4;
+
+const float    SPAWNFLAG_NOMESSAGE = 1;
+const float    SPAWNFLAG_NOTOUCH = 1;
+
+.void() trigger_touch;
+
+.float height;
+
+.float nottargeted;
+#define IFTARGETED if(!self.nottargeted && self.targetname != "")
+
+.string bgmscript;
+.float bgmscriptattack;
+.float bgmscriptdecay;
+.float bgmscriptsustain;
+.float bgmscriptrelease;
+
+.float lip;
+
+// used elsewhere (will fix)
+#ifdef SVQC
+void trigger_common_write(bool withtarget);
+
+string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin);
+
+void target_voicescript_next(entity pl);
+void target_voicescript_clear(entity pl);
+#endif
+
+.float volume, atten;
+
+.vector dest;
+
+#ifdef CSQC
+void trigger_common_read(bool withtarget);
+void trigger_remove_generic();
+
+float WarpZoneLib_ExactTrigger_Touch();
+#define EXACTTRIGGER_TOUCH if(WarpZoneLib_ExactTrigger_Touch()) return
+
+.float active;
+.string target;
+.string targetname;
+
+const int ACTIVE_NOT           = 0;
+const int ACTIVE_ACTIVE        = 1;
+const int ACTIVE_IDLE          = 2;
+const int ACTIVE_BUSY          = 2;
+const int ACTIVE_TOGGLE                = 3;
+#endif
+
+#endif
index 99fecda0bbfc2855600c02a3ec2629845511f730..53a7e3aeb604562a8a8183bb0fbe26c250de3297 100644 (file)
@@ -2583,7 +2583,7 @@ void FindConnectedComponent(entity e, .entity fld, findNextEntityNearFunction_t
 #ifdef SVQC
 vector combine_to_vector(float x, float y, float z)
 {
-       vector result; result.x = x; result.y = y; result.z = z;
+       vector result; result_x = x; result_y = y; result_z = z;
        return result;
 }
 
index 0010449a025923feff26357579fc0a98fd2fe9d0..569a15d97fa68eed9818e6d66933927384f5f702 100644 (file)
@@ -38,6 +38,8 @@ PORTO_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
 #endif
 #else
 #ifdef SVQC
+#include "../triggers/trigger/jumppads.qh"
+
 void spawnfunc_weapon_porto(void) { weapon_defaultspawnfunc(WEP_PORTO); }
 
 void W_Porto_Success(void)
index 55fcc0e14ffa597cbfaace6da0d52f09a4329f4d..f85ff90dc15d609943ce753b2baab80894fba382 100644 (file)
@@ -17,7 +17,7 @@
        #include "../../client/autocvars.qh"
        #include "../deathtypes.qh"
        #include "../../csqcmodellib/interpolate.qh"
-       #include "../../client/movetypes.qh"
+       #include "../movetypes/movetypes.qh"
        #include "../../client/main.qh"
        #include "../../csqcmodellib/cl_model.qh"
 #elif defined(MENUQC)
index 1f3017afdefd64766649fc7636b9602034b718a9..62b60a4c4fd07fa87ddcce3e263ff742e13fbdf0 100644 (file)
@@ -35,6 +35,7 @@
 #endif
 
 float autocvar_cl_movement_errorcompensation = 0;
+float autocvar_cl_movement = 2; // testing purposes
 
 // engine stuff
 float pmove_onground; // weird engine flag we shouldn't really use but have to for now
@@ -132,6 +133,17 @@ void CSQCPlayer_SavePrediction()
        csqcplayer_status = CSQCPLAYERSTATUS_PREDICTED;
 }
 
+void CSQC_ClientMovement_PlayerMove_Frame();
+
+void CSQCPlayer_Physics(void)
+{
+       switch(autocvar_cl_movement)
+       {
+               case 1: runstandardplayerphysics(self); break;
+               case 2: CSQC_ClientMovement_PlayerMove_Frame(); break;
+       }
+}
+
 void CSQCPlayer_PredictTo(float endframe, float apply_error)
 {
        CSQCPlayer_Unpredict();
@@ -166,7 +178,7 @@ void CSQCPlayer_PredictTo(float endframe, float apply_error)
                {
                        if (!getinputstate(csqcplayer_moveframe))
                                break;
-                       runstandardplayerphysics(self);
+                       CSQCPlayer_Physics();
                        CSQCPlayer_SetMinsMaxs();
                        csqcplayer_moveframe++;
                }
index 4effe6492446b65192b91d838bebd11da4828c1b..cd4f8d797669a1aa1174b124a9c8c6ce91a0c07d 100644 (file)
@@ -420,6 +420,7 @@ float( float a, ... ) min = #94;
 float( float b, ... ) max = #95;
 float(float minimum, float val, float maximum) bound = #96;
 float(float f, float f) pow = #97;
+entity(entity start, .entity fld, entity match) findentity = #98;
 entity(entity start, .float fld, float match) findfloat = #98;
 float(string s) checkextension = #99;
 // FrikaC and Telejano range #100-#199
index bcb9d28d70240e779e942a25b3c5764e7252298a..e595e47a9be79f908b87cf4a73501d212ef951ac 100644 (file)
@@ -634,13 +634,14 @@ string autocvar_sv_eventlog_files_namesuffix;
 float autocvar_sv_eventlog_files_timestamps;
 float autocvar_sv_friction;
 float autocvar_sv_friction_on_land;
+var float autocvar_sv_friction_slick = 0.5;
 float autocvar_sv_gameplayfix_q2airaccelerate;
 float autocvar_sv_gentle;
 #define autocvar_sv_gravity cvar("sv_gravity")
 string autocvar_sv_intermission_cdtrack;
-string autocvar_sv_jumpspeedcap_max;
+float autocvar_sv_jumpspeedcap_max;
 float autocvar_sv_jumpspeedcap_max_disable_on_ramps;
-string autocvar_sv_jumpspeedcap_min;
+float autocvar_sv_jumpspeedcap_min;
 float autocvar_sv_jumpvelocity;
 float autocvar_sv_logscores_bots;
 float autocvar_sv_logscores_console;
index 0ecafaf1e87c7aa62fda4892d2c38ef300e05683..7ba2e1612e8530f9d51d8b73b79d55ce0a29b9da 100644 (file)
@@ -2,6 +2,7 @@
 #include "role_onslaught.qc"
 #include "role_keyhunt.qc"
 #include "roles.qc"
+#include "../../../common/triggers/trigger/jumppads.qh"
 
 void havocbot_ai()
 {
index 2c091d56e15f19bed4cda5890861ef29fae33db2..650855989edebccc0ee31cf1aba8984b6a634b2f 100644 (file)
@@ -121,7 +121,6 @@ void CampaignPreInit()
        strunzone(title);
 }
 
-string GetMapname();
 void CampaignPostInit()
 {
        // now some sanity checks
index 657ec4d7fd3c0e15f6477c5a56c766b5a5effcb1..b8eaf6e4255093ee11a37cf94fe1e5c07d99b89d 100644 (file)
@@ -1,7 +1,7 @@
 #include "cheats.qh"
 #include "g_damage.qh"
 #include "race.qh"
-#include "t_teleporters.qh"
+#include "../common/triggers/teleporters.qh"
 
 #if defined(CSQC)
 #elif defined(MENUQC)
     #include "../warpzonelib/util_server.qh"
     #include "../common/constants.qh"
     #include "../common/util.qh"
+       #include "../common/triggers/func/breakable.qh"
     #include "../common/monsters/monsters.qh"
     #include "../common/weapons/weapons.qh"
     #include "weapons/tracing.qh"
     #include "autocvars.qh"
     #include "defs.qh"
     #include "../common/deathtypes.qh"
+       #include "../common/triggers/subs.qh"
+       #include "../common/triggers/func/breakable.qh"
     #include "mutators/mutators_include.qh"
     #include "../csqcmodellib/sv_model.qh"
 #endif
index 3bc5cc762f231b2e92f8df7dfdfe4b05e9d55bb1..a49268d194fb872826ca878f575612f796ed5931 100644 (file)
@@ -9,7 +9,6 @@
 #include "portals.qh"
 #include "teamplay.qh"
 #include "playerdemo.qh"
-#include "secret.qh"
 
 #include "bot/bot.qh"
 #include "bot/navigation.qh"
 #include "weapons/weaponsystem.qh"
 
 #include "../common/net_notice.qh"
+#include "../common/physics.qh"
+
+#include "../common/triggers/subs.qh"
+#include "../common/triggers/triggers.qh"
+#include "../common/triggers/trigger/secret.qh"
 
 #include "../common/monsters/sv_monsters.qh"
 
@@ -1661,55 +1665,17 @@ void SetZoomState(float z)
 
 void GetPressedKeys(void) {
        MUTATOR_CALLHOOK(GetPressedKeys);
-       if (self.movement.x > 0) // get if movement keys are pressed
-       {       // forward key pressed
-               self.pressedkeys |= KEY_FORWARD;
-               self.pressedkeys &= ~KEY_BACKWARD;
-       }
-       else if (self.movement.x < 0)
-       {       // backward key pressed
-               self.pressedkeys |= KEY_BACKWARD;
-               self.pressedkeys &= ~KEY_FORWARD;
-       }
-       else
-       {       // no x input
-               self.pressedkeys &= ~KEY_FORWARD;
-               self.pressedkeys &= ~KEY_BACKWARD;
-       }
-
-       if (self.movement.y > 0)
-       {       // right key pressed
-               self.pressedkeys |= KEY_RIGHT;
-               self.pressedkeys &= ~KEY_LEFT;
-       }
-       else if (self.movement.y < 0)
-       {       // left key pressed
-               self.pressedkeys |= KEY_LEFT;
-               self.pressedkeys &= ~KEY_RIGHT;
-       }
-       else
-       {       // no y input
-               self.pressedkeys &= ~KEY_RIGHT;
-               self.pressedkeys &= ~KEY_LEFT;
-       }
-
-       if (self.BUTTON_JUMP) // get if jump and crouch keys are pressed
-               self.pressedkeys |= KEY_JUMP;
-       else
-               self.pressedkeys &= ~KEY_JUMP;
-       if (self.BUTTON_CROUCH)
-               self.pressedkeys |= KEY_CROUCH;
-       else
-               self.pressedkeys &= ~KEY_CROUCH;
-
-       if (self.BUTTON_ATCK)
-               self.pressedkeys |= KEY_ATCK;
-       else
-               self.pressedkeys &= ~KEY_ATCK;
-       if (self.BUTTON_ATCK2)
-               self.pressedkeys |= KEY_ATCK2;
-       else
-               self.pressedkeys &= ~KEY_ATCK2;
+       #define X(var,bit,flag) (flag ? var |= bit : var &= ~bit)
+       X(self.pressedkeys, KEY_FORWARD,        PHYS_INPUT_MOVEVALUES(self)_x > 0);
+       X(self.pressedkeys, KEY_BACKWARD,       PHYS_INPUT_MOVEVALUES(self)_x < 0);
+       X(self.pressedkeys, KEY_RIGHT,          PHYS_INPUT_MOVEVALUES(self)_y > 0);
+       X(self.pressedkeys, KEY_LEFT,           PHYS_INPUT_MOVEVALUES(self)_y < 0);
+
+       X(self.pressedkeys, KEY_JUMP,           PHYS_INPUT_BUTTON_JUMP(self));
+       X(self.pressedkeys, KEY_CROUCH,         PHYS_INPUT_BUTTON_CROUCH(self));
+       X(self.pressedkeys, KEY_ATCK,           PHYS_INPUT_BUTTON_ATCK(self));
+       X(self.pressedkeys, KEY_ATCK2,          PHYS_INPUT_BUTTON_ATCK2(self));
+       #undef X
 }
 
 /*
diff --git a/qcsrc/server/cl_physics.qc b/qcsrc/server/cl_physics.qc
deleted file mode 100644 (file)
index 3373423..0000000
+++ /dev/null
@@ -1,1366 +0,0 @@
-#if defined(CSQC)
-#elif defined(MENUQC)
-#elif defined(SVQC)
-       #include "../dpdefs/progsdefs.qh"
-    #include "../dpdefs/dpextensions.qh"
-    #include "../warpzonelib/mathlib.qh"
-    #include "../warpzonelib/server.qh"
-    #include "../common/constants.qh"
-    #include "../common/util.qh"
-    #include "../common/animdecide.qh"
-    #include "../common/monsters/sv_monsters.qh"
-    #include "../common/weapons/weapons.qh"
-    #include "t_items.qh"
-    #include "autocvars.qh"
-    #include "defs.qh"
-    #include "../common/notifications.qh"
-    #include "mutators/mutators_include.qh"
-    #include "../common/mapinfo.qh"
-    #include "../csqcmodellib/sv_model.qh"
-    #include "anticheat.qh"
-    #include "cheats.qh"
-    #include "g_hook.qh"
-    #include "race.qh"
-    #include "playerdemo.qh"
-#endif
-
-.float race_penalty;
-.float restart_jump;
-
-.float ladder_time;
-.entity ladder_entity;
-.float gravity;
-.float swamp_slowdown;
-.int lastflags;
-.float lastground;
-.float wasFlying;
-.float spectatorspeed;
-
-/*
-=============
-PlayerJump
-
-When you press the jump key
-returns true if handled
-=============
-*/
-float PlayerJump (void)
-{
-       if(self.frozen)
-               return true; // no jumping in freezetag when frozen
-
-       if(self.player_blocked)
-               return true; // no jumping while blocked
-
-       float doublejump = false;
-       float mjumpheight = autocvar_sv_jumpvelocity;
-
-       player_multijump = doublejump;
-       player_jumpheight = mjumpheight;
-       if(MUTATOR_CALLHOOK(PlayerJump))
-               return true;
-
-       doublejump = player_multijump;
-       mjumpheight = player_jumpheight;
-
-       if (autocvar_sv_doublejump)
-       {
-               tracebox(self.origin + '0 0 0.01', self.mins, self.maxs, self.origin - '0 0 0.01', MOVE_NORMAL, self);
-               if (trace_fraction < 1 && trace_plane_normal.z > 0.7)
-               {
-                       doublejump = true;
-
-                       // we MUST clip velocity here!
-                       float f;
-                       f = self.velocity * trace_plane_normal;
-                       if(f < 0)
-                               self.velocity -= f * trace_plane_normal;
-               }
-       }
-
-       if (self.waterlevel >= WATERLEVEL_SWIMMING)
-       {
-               self.velocity_z = self.stat_sv_maxspeed * 0.7;
-               return true;
-       }
-
-       if (!doublejump)
-               if (!(self.flags & FL_ONGROUND))
-                       return !(self.flags & FL_JUMPRELEASED);
-
-       if(self.cvar_cl_movement_track_canjump)
-               if (!(self.flags & FL_JUMPRELEASED))
-                       return true;
-
-       // sv_jumpspeedcap_min/sv_jumpspeedcap_max act as baseline
-       // velocity bounds.  Final velocity is bound between (jumpheight *
-       // min + jumpheight) and (jumpheight * max + jumpheight);
-
-       if(autocvar_sv_jumpspeedcap_min != "")
-       {
-               float minjumpspeed;
-
-               minjumpspeed = mjumpheight * stof(autocvar_sv_jumpspeedcap_min);
-
-               if (self.velocity.z < minjumpspeed)
-                       mjumpheight += minjumpspeed - self.velocity.z;
-       }
-
-       if(autocvar_sv_jumpspeedcap_max != "")
-       {
-               // don't do jump speedcaps on ramps to preserve old xonotic ramjump style
-               tracebox(self.origin + '0 0 0.01', self.mins, self.maxs, self.origin - '0 0 0.01', MOVE_NORMAL, self);
-
-               if(!(trace_fraction < 1 && trace_plane_normal.z < 0.98 && autocvar_sv_jumpspeedcap_max_disable_on_ramps))
-               {
-                       float maxjumpspeed;
-
-                       maxjumpspeed = mjumpheight * stof(autocvar_sv_jumpspeedcap_max);
-
-                       if (self.velocity.z > maxjumpspeed)
-                               mjumpheight -= self.velocity.z - maxjumpspeed;
-               }
-       }
-
-       if(!(self.lastflags & FL_ONGROUND))
-       {
-               if(autocvar_speedmeter)
-                       dprint(strcat("landing velocity: ", vtos(self.velocity), " (abs: ", ftos(vlen(self.velocity)), ")\n"));
-               if(self.lastground < time - 0.3)
-               {
-                       self.velocity_x *= (1 - autocvar_sv_friction_on_land);
-                       self.velocity_y *= (1 - autocvar_sv_friction_on_land);
-               }
-               if(self.jumppadcount > 1)
-                       dprint(strcat(ftos(self.jumppadcount), "x jumppad combo\n"));
-               self.jumppadcount = 0;
-       }
-
-       self.velocity_z = self.velocity.z + mjumpheight;
-       self.oldvelocity_z = self.velocity.z;
-
-       self.flags &= ~FL_ONGROUND;
-       self.flags &= ~FL_JUMPRELEASED;
-
-       animdecide_setaction(self, ANIMACTION_JUMP, true);
-
-       if(autocvar_g_jump_grunt)
-               PlayerSound(playersound_jump, CH_PLAYER, VOICETYPE_PLAYERSOUND);
-
-       self.restart_jump = -1; // restart jump anim next time
-       // value -1 is used to not use the teleport bit (workaround for tiny hitch when re-jumping)
-       return true;
-}
-void CheckWaterJump()
-{
-       vector start, end;
-
-// check for a jump-out-of-water
-       makevectors (self.angles);
-       start = self.origin;
-       start.z = start.z + 8;
-       v_forward.z = 0;
-       normalize(v_forward);
-       end = start + v_forward*24;
-       traceline (start, end, true, self);
-       if (trace_fraction < 1)
-       {       // solid at waist
-               start.z = start.z + self.maxs.z - 8;
-               end = start + v_forward*24;
-               self.movedir = trace_plane_normal * -50;
-               traceline (start, end, true, self);
-               if (trace_fraction == 1)
-               {       // open at eye level
-                       self.flags |= FL_WATERJUMP;
-                       self.velocity_z = 225;
-                       self.flags &= ~FL_JUMPRELEASED;
-                       self.teleport_time = time + 2;  // safety net
-                       return;
-               }
-       }
-}
-
-.float jetpack_stopped;
-// Hack: shouldn't need to know about this
-.float multijump_count;
-void CheckPlayerJump()
-{
-       float was_flying = self.items & IT_USING_JETPACK;
-
-       if (self.cvar_cl_jetpack_jump < 2)
-               self.items &= ~IT_USING_JETPACK;
-
-       if (self.BUTTON_JUMP || self.BUTTON_JETPACK)
-       {
-               float air_jump = !PlayerJump() || self.multijump_count > 0; // PlayerJump() has important side effects
-               float activate = self.cvar_cl_jetpack_jump && air_jump && self.BUTTON_JUMP || self.BUTTON_JETPACK;
-               float has_fuel = !autocvar_g_jetpack_fuel || self.ammo_fuel || self.items & IT_UNLIMITED_WEAPON_AMMO;
-               if (!(self.items & IT_JETPACK)) { }
-               else if (self.jetpack_stopped) { }
-               else if (!has_fuel)
-               {
-                       if (was_flying) // TODO: ran out of fuel message
-                               Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_JETPACK_NOFUEL);
-                       else if (activate)
-                               Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_JETPACK_NOFUEL);
-                       self.jetpack_stopped = true;
-                       self.items &= ~IT_USING_JETPACK;
-               }
-               else if (activate && !self.frozen)
-                       self.items |= IT_USING_JETPACK;
-       }
-       else
-       {
-               self.jetpack_stopped = false;
-               self.items &= ~IT_USING_JETPACK;
-       }
-       if (!self.BUTTON_JUMP)
-               self.flags |= FL_JUMPRELEASED;
-
-       if (self.waterlevel == WATERLEVEL_SWIMMING)
-               CheckWaterJump ();
-}
-
-float racecar_angle(float forward, float down)
-{
-       float ret, angle_mult;
-
-       if(forward < 0)
-       {
-               forward = -forward;
-               down = -down;
-       }
-
-       ret = vectoyaw('0 1 0' * down + '1 0 0' * forward);
-
-       angle_mult = forward / (800 + forward);
-
-       if(ret > 180)
-               return ret * angle_mult + 360 * (1 - angle_mult);
-       else
-               return ret * angle_mult;
-}
-
-void RaceCarPhysics()
-{
-       // using this move type for "big rigs"
-       // the engine does not push the entity!
-
-       float accel, steer, f, myspeed, steerfactor;
-       vector angles_save, rigvel;
-
-       angles_save = self.angles;
-       accel = bound(-1, self.movement.x / self.stat_sv_maxspeed, 1);
-       steer = bound(-1, self.movement.y / self.stat_sv_maxspeed, 1);
-
-       if(g_bugrigs_reverse_speeding)
-       {
-               if(accel < 0)
-               {
-                       // back accel is DIGITAL
-                       // to prevent speedhack
-                       if(accel < -0.5)
-                               accel = -1;
-                       else
-                               accel = 0;
-               }
-       }
-
-       self.angles_x = 0;
-       self.angles_z = 0;
-       makevectors(self.angles); // new forward direction!
-
-       if(self.flags & FL_ONGROUND || g_bugrigs_air_steering)
-       {
-               float upspeed, accelfactor;
-
-               myspeed = self.velocity * v_forward;
-               upspeed = self.velocity * v_up;
-
-               // responsiveness factor for steering and acceleration
-               f = 1 / (1 + pow(max(-myspeed, myspeed) / g_bugrigs_speed_ref, g_bugrigs_speed_pow));
-               //MAXIMA: f(v) := 1 / (1 + (v / g_bugrigs_speed_ref) ^ g_bugrigs_speed_pow);
-
-               if(myspeed < 0 && g_bugrigs_reverse_spinning)
-                       steerfactor = -myspeed * g_bugrigs_steer;
-               else
-                       steerfactor = -myspeed * f * g_bugrigs_steer;
-
-               if(myspeed < 0 && g_bugrigs_reverse_speeding)
-                       accelfactor = g_bugrigs_accel;
-               else
-                       accelfactor = f * g_bugrigs_accel;
-               //MAXIMA: accel(v) := f(v) * g_bugrigs_accel;
-
-               if(accel < 0)
-               {
-                       if(myspeed > 0)
-                       {
-                               myspeed = max(0, myspeed - frametime * (g_bugrigs_friction_floor - g_bugrigs_friction_brake * accel));
-                       }
-                       else
-                       {
-                               if(!g_bugrigs_reverse_speeding)
-                                       myspeed = min(0, myspeed + frametime * g_bugrigs_friction_floor);
-                       }
-               }
-               else
-               {
-                       if(myspeed >= 0)
-                       {
-                               myspeed = max(0, myspeed - frametime * g_bugrigs_friction_floor);
-                       }
-                       else
-                       {
-                               if(g_bugrigs_reverse_stopping)
-                                       myspeed = 0;
-                               else
-                                       myspeed = min(0, myspeed + frametime * (g_bugrigs_friction_floor + g_bugrigs_friction_brake * accel));
-                       }
-               }
-               // terminal velocity = velocity at which 50 == accelfactor, that is, 1549 units/sec
-               //MAXIMA: friction(v) := g_bugrigs_friction_floor;
-
-               self.angles_y += steer * frametime * steerfactor; // apply steering
-               makevectors(self.angles); // new forward direction!
-
-               myspeed += accel * accelfactor * frametime;
-
-               rigvel = myspeed * v_forward + '0 0 1' * upspeed;
-       }
-       else
-       {
-               myspeed = vlen(self.velocity);
-
-               // responsiveness factor for steering and acceleration
-               f = 1 / (1 + pow(max(0, myspeed / g_bugrigs_speed_ref), g_bugrigs_speed_pow));
-               steerfactor = -myspeed * f;
-               self.angles_y += steer * frametime * steerfactor; // apply steering
-
-               rigvel = self.velocity;
-               makevectors(self.angles); // new forward direction!
-       }
-
-       rigvel = rigvel * max(0, 1 - vlen(rigvel) * g_bugrigs_friction_air * frametime);
-       //MAXIMA: airfriction(v) := v * v * g_bugrigs_friction_air;
-       //MAXIMA: total_acceleration(v) := accel(v) - friction(v) - airfriction(v);
-       //MAXIMA: solve(total_acceleration(v) = 0, v);
-
-       if(g_bugrigs_planar_movement)
-       {
-               vector rigvel_xy, neworigin, up;
-               float mt;
-
-               rigvel.z -= frametime * autocvar_sv_gravity; // 4x gravity plays better
-               rigvel_xy = vec2(rigvel);
-
-               if(g_bugrigs_planar_movement_car_jumping)
-                       mt = MOVE_NORMAL;
-               else
-                       mt = MOVE_NOMONSTERS;
-
-               tracebox(self.origin, self.mins, self.maxs, self.origin + '0 0 1024', mt, self);
-               up = trace_endpos - self.origin;
-
-               // BUG RIGS: align the move to the surface instead of doing collision testing
-               // can we move?
-               tracebox(trace_endpos, self.mins, self.maxs, trace_endpos + rigvel_xy * frametime, mt, self);
-
-               // align to surface
-               tracebox(trace_endpos, self.mins, self.maxs, trace_endpos - up + '0 0 1' * rigvel.z * frametime, mt, self);
-
-               if(trace_fraction < 0.5)
-               {
-                       trace_fraction = 1;
-                       neworigin = self.origin;
-               }
-               else
-                       neworigin = trace_endpos;
-
-               if(trace_fraction < 1)
-               {
-                       // now set angles_x so that the car points parallel to the surface
-                       self.angles = vectoangles(
-                                       '1 0 0' * v_forward.x * trace_plane_normal.z
-                                       +
-                                       '0 1 0' * v_forward.y * trace_plane_normal.z
-                                       +
-                                       '0 0 1' * -(v_forward.x * trace_plane_normal.x + v_forward.y * trace_plane_normal.y)
-                                       );
-                       self.flags |= FL_ONGROUND;
-               }
-               else
-               {
-                       // now set angles_x so that the car points forward, but is tilted in velocity direction
-                       self.flags &= ~FL_ONGROUND;
-               }
-
-               self.velocity = (neworigin - self.origin) * (1.0 / frametime);
-               self.movetype = MOVETYPE_NOCLIP;
-       }
-       else
-       {
-               rigvel.z -= frametime * autocvar_sv_gravity; // 4x gravity plays better
-               self.velocity = rigvel;
-               self.movetype = MOVETYPE_FLY;
-       }
-
-       trace_fraction = 1;
-       tracebox(self.origin, self.mins, self.maxs, self.origin - '0 0 4', MOVE_NORMAL, self);
-       if(trace_fraction != 1)
-       {
-               self.angles = vectoangles2(
-                               '1 0 0' * v_forward.x * trace_plane_normal.z
-                               +
-                               '0 1 0' * v_forward.y * trace_plane_normal.z
-                               +
-                               '0 0 1' * -(v_forward.x * trace_plane_normal.x + v_forward.y * trace_plane_normal.y),
-                               trace_plane_normal
-                               );
-       }
-       else
-       {
-               vector vel_local;
-
-               vel_local.x = v_forward * self.velocity;
-               vel_local.y = v_right * self.velocity;
-               vel_local.z = v_up * self.velocity;
-
-               self.angles_x = racecar_angle(vel_local.x, vel_local.z);
-               self.angles_z = racecar_angle(-vel_local.y, vel_local.z);
-       }
-
-       // smooth the angles
-       vector vf1, vu1, smoothangles;
-       makevectors(self.angles);
-       f = bound(0, frametime * g_bugrigs_angle_smoothing, 1);
-       if(f == 0)
-               f = 1;
-       vf1 = v_forward * f;
-       vu1 = v_up * f;
-       makevectors(angles_save);
-       vf1 = vf1 + v_forward * (1 - f);
-       vu1 = vu1 + v_up * (1 - f);
-       smoothangles = vectoangles2(vf1, vu1);
-       self.angles_x = -smoothangles.x;
-       self.angles_z =  smoothangles.z;
-}
-
-float IsMoveInDirection(vector mv, float angle) // key mix factor
-{
-       if(mv.x == 0 && mv.y == 0)
-               return 0; // avoid division by zero
-       angle -= RAD2DEG * atan2(mv.y, mv.x);
-       angle = remainder(angle, 360) / 45;
-       if(angle >  1)
-               return 0;
-       if(angle < -1)
-               return 0;
-       return 1 - fabs(angle);
-}
-
-float GeomLerp(float a, float lerp, float b)
-{
-       if(a == 0)
-       {
-               if(lerp < 1)
-                       return 0;
-               else
-                       return b;
-       }
-       if(b == 0)
-       {
-               if(lerp > 0)
-                       return 0;
-               else
-                       return a;
-       }
-       return a * pow(fabs(b / a), lerp);
-}
-
-void CPM_PM_Aircontrol(vector wishdir, float wishspeed)
-{
-       float zspeed, xyspeed, dot, k;
-
-#if 0
-       // this doesn't play well with analog input
-       if(self.movement_x == 0 || self.movement.y != 0)
-               return; // can't control movement if not moving forward or backward
-       k = 32;
-#else
-       k = 32 * (2 * IsMoveInDirection(self.movement, 0) - 1);
-       if(k <= 0)
-               return;
-#endif
-
-       k *= bound(0, wishspeed / autocvar_sv_maxairspeed, 1);
-
-       zspeed = self.velocity.z;
-       self.velocity_z = 0;
-       xyspeed = vlen(self.velocity); self.velocity = normalize(self.velocity);
-
-       dot = self.velocity * wishdir;
-
-       if(dot > 0) // we can't change direction while slowing down
-       {
-               k *= pow(dot, autocvar_sv_aircontrol_power)*frametime;
-               xyspeed = max(0, xyspeed - autocvar_sv_aircontrol_penalty * sqrt(max(0, 1 - dot*dot)) * k/32);
-               k *= autocvar_sv_aircontrol;
-               self.velocity = normalize(self.velocity * xyspeed + wishdir * k);
-       }
-
-       self.velocity = self.velocity * xyspeed;
-       self.velocity_z = zspeed;
-}
-
-float AdjustAirAccelQW(float accelqw, float factor)
-{
-       return copysign(bound(0.000001, 1 - (1 - fabs(accelqw)) * factor, 1), accelqw);
-}
-
-// example config for alternate speed clamping:
-//   sv_airaccel_qw 0.8
-//   sv_airaccel_sideways_friction 0
-//   prvm_globalset server speedclamp_mode 1
-//     (or 2)
-void PM_Accelerate(vector wishdir, float wishspeed, float wishspeed0, float accel, float accelqw, float stretchfactor, float sidefric, float speedlimit)
-{
-       float vel_straight;
-       float velZ;
-       vector vel_perpend;
-       float step;
-
-       vector vel_xy;
-       float vel_xy_current;
-       float vel_xy_backward, vel_xy_forward;
-       float speedclamp;
-
-       if(stretchfactor > 0)
-               speedclamp = stretchfactor;
-       else if(accelqw < 0)
-               speedclamp = 1; // full clamping, no stretch
-       else
-               speedclamp = -1; // no clamping
-
-       if(accelqw < 0)
-               accelqw = -accelqw;
-
-       if(autocvar_sv_gameplayfix_q2airaccelerate)
-               wishspeed0 = wishspeed;
-
-       vel_straight = self.velocity * wishdir;
-       velZ = self.velocity.z;
-       vel_xy = vec2(self.velocity);
-       vel_perpend = vel_xy - vel_straight * wishdir;
-
-       step = accel * frametime * wishspeed0;
-
-       vel_xy_current  = vlen(vel_xy);
-       if(speedlimit)
-               accelqw = AdjustAirAccelQW(accelqw, (speedlimit - bound(wishspeed, vel_xy_current, speedlimit)) / max(1, speedlimit - wishspeed));
-       vel_xy_forward  = vel_xy_current + bound(0, wishspeed - vel_xy_current, step) * accelqw + step * (1 - accelqw);
-       vel_xy_backward = vel_xy_current - bound(0, wishspeed + vel_xy_current, step) * accelqw - step * (1 - accelqw);
-       if(vel_xy_backward < 0)
-               vel_xy_backward = 0; // not that it REALLY occurs that this would cause wrong behaviour afterwards
-
-       vel_straight = vel_straight + bound(0, wishspeed - vel_straight, step) * accelqw + step * (1 - accelqw);
-
-       if(sidefric < 0 && (vel_perpend*vel_perpend))
-               // negative: only apply so much sideways friction to stay below the speed you could get by "braking"
-       {
-               float f, fminimum;
-               f = max(0, 1 + frametime * wishspeed * sidefric);
-               fminimum = (vel_xy_backward*vel_xy_backward - vel_straight*vel_straight) / (vel_perpend*vel_perpend);
-               // this cannot be > 1
-               if(fminimum <= 0)
-                       vel_perpend = vel_perpend * max(0, f);
-               else
-               {
-                       fminimum = sqrt(fminimum);
-                       vel_perpend = vel_perpend * max(fminimum, f);
-               }
-       }
-       else
-               vel_perpend = vel_perpend * max(0, 1 - frametime * wishspeed * sidefric);
-
-       vel_xy = vel_straight * wishdir + vel_perpend;
-
-       if(speedclamp >= 0)
-       {
-               float vel_xy_preclamp;
-               vel_xy_preclamp = vlen(vel_xy);
-               if(vel_xy_preclamp > 0) // prevent division by zero
-               {
-                       vel_xy_current += (vel_xy_forward - vel_xy_current) * speedclamp;
-                       if(vel_xy_current < vel_xy_preclamp)
-                               vel_xy = vel_xy * (vel_xy_current / vel_xy_preclamp);
-               }
-       }
-
-       self.velocity = vel_xy + velZ * '0 0 1';
-}
-
-void PM_AirAccelerate(vector wishdir, float wishspeed)
-{
-       vector curvel, wishvel, acceldir, curdir;
-       float addspeed, accelspeed, curspeed, f;
-       float dot;
-
-       if(wishspeed == 0)
-               return;
-
-       curvel = self.velocity;
-       curvel.z = 0;
-       curspeed = vlen(curvel);
-
-       if(wishspeed > curspeed * 1.01)
-       {
-               wishspeed = min(wishspeed, curspeed + autocvar_sv_warsowbunny_airforwardaccel * self.stat_sv_maxspeed * frametime);
-       }
-       else
-       {
-               f = max(0, (autocvar_sv_warsowbunny_topspeed - curspeed) / (autocvar_sv_warsowbunny_topspeed - self.stat_sv_maxspeed));
-               wishspeed = max(curspeed, self.stat_sv_maxspeed) + autocvar_sv_warsowbunny_accel * f * self.stat_sv_maxspeed * frametime;
-       }
-       wishvel = wishdir * wishspeed;
-       acceldir = wishvel - curvel;
-       addspeed = vlen(acceldir);
-       acceldir = normalize(acceldir);
-
-       accelspeed = min(addspeed, autocvar_sv_warsowbunny_turnaccel * self.stat_sv_maxspeed * frametime);
-
-       if(autocvar_sv_warsowbunny_backtosideratio < 1)
-       {
-               curdir = normalize(curvel);
-               dot = acceldir * curdir;
-               if(dot < 0)
-                       acceldir = acceldir - (1 - autocvar_sv_warsowbunny_backtosideratio) * dot * curdir;
-       }
-
-       self.velocity += accelspeed * acceldir;
-}
-
-.vector movement_old;
-.float buttons_old;
-.vector v_angle_old;
-.string lastclassname;
-
-.float() PlayerPhysplug;
-
-string specialcommand = "xwxwxsxsxaxdxaxdx1x ";
-.float specialcommand_pos;
-void SpecialCommand()
-{
-#ifdef TETRIS
-       TetrisImpulse();
-#else
-       if(!CheatImpulse(99))
-               print("A hollow voice says \"Plugh\".\n");
-#endif
-}
-
-float speedaward_speed;
-string speedaward_holder;
-string speedaward_uid;
-void race_send_speedaward(float msg)
-{
-       // send the best speed of the round
-       WriteByte(msg, SVC_TEMPENTITY);
-       WriteByte(msg, TE_CSQC_RACE);
-       WriteByte(msg, RACE_NET_SPEED_AWARD);
-       WriteInt24_t(msg, floor(speedaward_speed+0.5));
-       WriteString(msg, speedaward_holder);
-}
-
-float speedaward_alltimebest;
-string speedaward_alltimebest_holder;
-string speedaward_alltimebest_uid;
-void race_send_speedaward_alltimebest(float msg)
-{
-       // send the best speed
-       WriteByte(msg, SVC_TEMPENTITY);
-       WriteByte(msg, TE_CSQC_RACE);
-       WriteByte(msg, RACE_NET_SPEED_AWARD_BEST);
-       WriteInt24_t(msg, floor(speedaward_alltimebest+0.5));
-       WriteString(msg, speedaward_alltimebest_holder);
-}
-
-string GetMapname(void);
-float speedaward_lastupdate;
-float speedaward_lastsent;
-void SV_PlayerPhysics()
-{
-       vector wishvel, wishdir, v;
-       float wishspeed, f, maxspd_mod, spd, maxairspd, airaccel, swampspd_mod, buttons;
-       string temps;
-       int buttons_prev;
-       float not_allowed_to_move;
-       string c;
-
-       WarpZone_PlayerPhysics_FixVAngle();
-
-       maxspd_mod = 1;
-       if(self.ballcarried)
-               if(g_keepaway)
-                       maxspd_mod *= autocvar_g_keepaway_ballcarrier_highspeed;
-
-       maxspd_mod *= autocvar_g_movement_highspeed;
-
-       // fix physics stats for g_movement_highspeed
-       // TODO maybe rather use maxairspeed? needs testing
-       self.stat_sv_airaccel_qw = AdjustAirAccelQW(autocvar_sv_airaccel_qw, maxspd_mod);
-       if(autocvar_sv_airstrafeaccel_qw)
-               self.stat_sv_airstrafeaccel_qw = AdjustAirAccelQW(autocvar_sv_airstrafeaccel_qw, maxspd_mod);
-       else
-               self.stat_sv_airstrafeaccel_qw = 0;
-       self.stat_sv_airspeedlimit_nonqw = autocvar_sv_airspeedlimit_nonqw * maxspd_mod;
-       self.stat_sv_maxspeed = autocvar_sv_maxspeed * maxspd_mod; // also slow walking
-
-    if(self.PlayerPhysplug)
-        if(self.PlayerPhysplug())
-            return;
-
-       self.race_movetime_frac += frametime;
-       f = floor(self.race_movetime_frac);
-       self.race_movetime_frac -= f;
-       self.race_movetime_count += f;
-       self.race_movetime = self.race_movetime_frac + self.race_movetime_count;
-
-       anticheat_physics();
-
-       buttons = self.BUTTON_ATCK + 2 * self.BUTTON_JUMP + 4 * self.BUTTON_ATCK2 + 8 * self.BUTTON_ZOOM + 16 * self.BUTTON_CROUCH + 32 * self.BUTTON_HOOK + 64 * self.BUTTON_USE + 128 * (self.movement.x < 0) + 256 * (self.movement.x > 0) + 512 * (self.movement.y < 0) + 1024 * (self.movement.y > 0);
-
-       if(!buttons)
-               c = "x";
-       else if(buttons == 1)
-               c = "1";
-       else if(buttons == 2)
-               c = " ";
-       else if(buttons == 128)
-               c = "s";
-       else if(buttons == 256)
-               c = "w";
-       else if(buttons == 512)
-               c = "a";
-       else if(buttons == 1024)
-               c = "d";
-       else
-               c = "?";
-
-       if(c == substring(specialcommand, self.specialcommand_pos, 1))
-       {
-               self.specialcommand_pos += 1;
-               if(self.specialcommand_pos >= strlen(specialcommand))
-               {
-                       self.specialcommand_pos = 0;
-                       SpecialCommand();
-                       return;
-               }
-       }
-       else if(self.specialcommand_pos && (c != substring(specialcommand, self.specialcommand_pos - 1, 1)))
-               self.specialcommand_pos = 0;
-
-       if(sv_maxidle > 0)
-       {
-               if(buttons != self.buttons_old || self.movement != self.movement_old || self.v_angle != self.v_angle_old)
-                       self.parm_idlesince = time;
-       }
-       buttons_prev = self.buttons_old;
-       self.buttons_old = buttons;
-       self.movement_old = self.movement;
-       self.v_angle_old = self.v_angle;
-
-       if(time < self.nickspamtime)
-       if(self.nickspamcount >= autocvar_g_nick_flood_penalty_yellow)
-       {
-               // slight annoyance for nick change scripts
-               self.movement = -1 * self.movement;
-               self.BUTTON_ATCK = self.BUTTON_JUMP = self.BUTTON_ATCK2 = self.BUTTON_ZOOM = self.BUTTON_CROUCH = self.BUTTON_HOOK = self.BUTTON_USE = 0;
-
-               if(self.nickspamcount >= autocvar_g_nick_flood_penalty_red) // if you are persistent and the slight annoyance above does not stop you, I'll show you!
-               {
-                       self.angles_x = random() * 360;
-                       self.angles_y = random() * 360;
-                       // at least I'm not forcing retardedview by also assigning to angles_z
-                       self.fixangle = true;
-               }
-       }
-
-       if (self.punchangle != '0 0 0')
-       {
-               f = vlen(self.punchangle) - 10 * frametime;
-               if (f > 0)
-                       self.punchangle = normalize(self.punchangle) * f;
-               else
-                       self.punchangle = '0 0 0';
-       }
-
-       if (self.punchvector != '0 0 0')
-       {
-               f = vlen(self.punchvector) - 30 * frametime;
-               if (f > 0)
-                       self.punchvector = normalize(self.punchvector) * f;
-               else
-                       self.punchvector = '0 0 0';
-       }
-
-       if (IS_BOT_CLIENT(self))
-       {
-               if(playerdemo_read())
-                       return;
-               bot_think();
-       }
-
-       if(IS_PLAYER(self))
-       {
-               if(self.race_penalty)
-                       if(time > self.race_penalty)
-                               self.race_penalty = 0;
-
-               not_allowed_to_move = 0;
-               if(self.race_penalty)
-                       not_allowed_to_move = 1;
-               if(time < game_starttime)
-                       not_allowed_to_move = 1;
-
-               if(not_allowed_to_move)
-               {
-                       self.velocity = '0 0 0';
-                       self.movetype = MOVETYPE_NONE;
-                       self.disableclientprediction = 2;
-               }
-               else if(self.disableclientprediction == 2)
-               {
-                       if(self.movetype == MOVETYPE_NONE)
-                               self.movetype = MOVETYPE_WALK;
-                       self.disableclientprediction = 0;
-               }
-       }
-
-       if (self.movetype == MOVETYPE_NONE)
-               return;
-
-       // when we get here, disableclientprediction cannot be 2
-       self.disableclientprediction = 0;
-       if(time < self.ladder_time)
-               self.disableclientprediction = 1;
-
-       if(time < self.spider_slowness)
-       {
-               self.stat_sv_maxspeed *= 0.5; // half speed while slow from spider
-               self.stat_sv_airspeedlimit_nonqw *= 0.5;
-       }
-
-       if(self.frozen)
-       {
-               if(autocvar_sv_dodging_frozen && IS_REAL_CLIENT(self))
-               {
-                       self.movement_x = bound(-5, self.movement.x, 5);
-                       self.movement_y = bound(-5, self.movement.y, 5);
-                       self.movement_z = bound(-5, self.movement.z, 5);
-               }
-               else
-                       self.movement = '0 0 0';
-               self.disableclientprediction = 1;
-
-               vector midpoint = ((self.absmin + self.absmax) * 0.5);
-               if(pointcontents(midpoint) == CONTENT_WATER)
-               {
-                       self.velocity = self.velocity * 0.5;
-
-                       if(pointcontents(midpoint + '0 0 16') == CONTENT_WATER)
-                               { self.velocity_z = 200; }
-               }
-       }
-
-       MUTATOR_CALLHOOK(PlayerPhysics);
-
-       if(self.player_blocked)
-       {
-               self.movement = '0 0 0';
-               self.disableclientprediction = 1;
-       }
-
-       maxspd_mod = 1;
-
-       swampspd_mod = 1;
-       if(self.in_swamp) {
-               swampspd_mod = self.swamp_slowdown; //cvar("g_balance_swamp_moverate");
-       }
-
-       // conveyors: first fix velocity
-       if(self.conveyor.state)
-               self.velocity -= self.conveyor.movedir;
-
-       if (!IS_PLAYER(self))
-       {
-               maxspd_mod = autocvar_sv_spectator_speed_multiplier;
-               if(!self.spectatorspeed)
-                       self.spectatorspeed = maxspd_mod;
-               if(self.impulse && self.impulse <= 19 || (self.impulse >= 200 && self.impulse <= 209) || (self.impulse >= 220 && self.impulse <= 229))
-               {
-                       if(self.lastclassname != "player")
-                       {
-                               if(self.impulse == 10 || self.impulse == 15 || self.impulse == 18 || (self.impulse >= 200 && self.impulse <= 209))
-                                       self.spectatorspeed = bound(1, self.spectatorspeed + 0.5, 5);
-                               else if(self.impulse == 11)
-                                       self.spectatorspeed = maxspd_mod;
-                               else if(self.impulse == 12 || self.impulse == 16  || self.impulse == 19 || (self.impulse >= 220 && self.impulse <= 229))
-                                       self.spectatorspeed = bound(1, self.spectatorspeed - 0.5, 5);
-                               else if(self.impulse >= 1 && self.impulse <= 9)
-                                       self.spectatorspeed = 1 + 0.5 * (self.impulse - 1);
-                       } // otherwise just clear
-                       self.impulse = 0;
-               }
-               maxspd_mod = self.spectatorspeed;
-       }
-
-       spd = max(self.stat_sv_maxspeed, autocvar_sv_maxairspeed) * maxspd_mod * swampspd_mod;
-       if(self.speed != spd)
-       {
-               self.speed = spd;
-               temps = ftos(spd);
-               stuffcmd(self, strcat("cl_forwardspeed ", temps, "\n"));
-               stuffcmd(self, strcat("cl_backspeed ", temps, "\n"));
-               stuffcmd(self, strcat("cl_sidespeed ", temps, "\n"));
-               stuffcmd(self, strcat("cl_upspeed ", temps, "\n"));
-       }
-
-       maxspd_mod *= swampspd_mod; // only one common speed modder please!
-       swampspd_mod = 1;
-
-       // if dead, behave differently
-       if (self.deadflag)
-               goto end;
-
-       if (!self.fixangle && !g_bugrigs)
-       {
-               self.angles_x = 0;
-               self.angles_y = self.v_angle.y;
-               self.angles_z = 0;
-       }
-
-       if(self.flags & FL_ONGROUND)
-       if(IS_PLAYER(self)) // no fall sounds for observers thank you very much
-       if(self.wasFlying)
-       {
-               self.wasFlying = 0;
-
-               if(self.waterlevel < WATERLEVEL_SWIMMING)
-               if(time >= self.ladder_time)
-               if (!self.hook)
-               {
-                       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_dphitq3surfaceflags & Q3SURFACEFLAG_NOSTEPS))
-                       {
-                               if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_METALSTEPS)
-                                       GlobalSound(globalsound_metalfall, CH_PLAYER, VOICETYPE_PLAYERSOUND);
-                               else
-                                       GlobalSound(globalsound_fall, CH_PLAYER, VOICETYPE_PLAYERSOUND);
-                       }
-               }
-       }
-
-       if(IsFlying(self))
-               self.wasFlying = 1;
-
-       if(IS_PLAYER(self))
-               CheckPlayerJump();
-
-       if (self.flags & FL_WATERJUMP )
-       {
-               self.velocity_x = self.movedir.x;
-               self.velocity_y = self.movedir.y;
-               if (time > self.teleport_time || self.waterlevel == WATERLEVEL_NONE)
-               {
-                       self.flags &= ~FL_WATERJUMP;
-                       self.teleport_time = 0;
-               }
-       }
-       else if (g_bugrigs && IS_PLAYER(self))
-       {
-               RaceCarPhysics();
-       }
-       else if (self.movetype == MOVETYPE_NOCLIP || self.movetype == MOVETYPE_FLY || self.movetype == MOVETYPE_FLY_WORLDONLY)
-       {
-               // noclipping or flying
-               self.flags &= ~FL_ONGROUND;
-
-               self.velocity = self.velocity * (1 - frametime * autocvar_sv_friction);
-               makevectors(self.v_angle);
-               //wishvel = v_forward * self.movement_x + v_right * self.movement_y + v_up * self.movement_z;
-               wishvel = v_forward * self.movement.x + v_right * self.movement.y + '0 0 1' * self.movement.z;
-               // acceleration
-               wishdir = normalize(wishvel);
-               wishspeed = vlen(wishvel);
-               if (wishspeed > self.stat_sv_maxspeed*maxspd_mod)
-                       wishspeed = self.stat_sv_maxspeed*maxspd_mod;
-               if (time >= self.teleport_time)
-                       PM_Accelerate(wishdir, wishspeed, wishspeed, autocvar_sv_accelerate*maxspd_mod, 1, 0, 0, 0);
-       }
-       else if (self.waterlevel >= WATERLEVEL_SWIMMING)
-       {
-               // swimming
-               self.flags &= ~FL_ONGROUND;
-
-               makevectors(self.v_angle);
-               //wishvel = v_forward * self.movement_x + v_right * self.movement_y + v_up * self.movement_z;
-               wishvel = v_forward * self.movement.x + v_right * self.movement.y + '0 0 1' * self.movement.z;
-               if (wishvel == '0 0 0')
-                       wishvel = '0 0 -60'; // drift towards bottom
-
-               wishdir = normalize(wishvel);
-               wishspeed = vlen(wishvel);
-               if (wishspeed > self.stat_sv_maxspeed*maxspd_mod)
-                       wishspeed = self.stat_sv_maxspeed*maxspd_mod;
-               wishspeed = wishspeed * 0.7;
-
-               // water friction
-               self.velocity = self.velocity * (1 - frametime * autocvar_sv_friction);
-
-               // water acceleration
-               PM_Accelerate(wishdir, wishspeed, wishspeed, autocvar_sv_accelerate*maxspd_mod, 1, 0, 0, 0);
-       }
-       else if (time < self.ladder_time)
-       {
-               // on a spawnfunc_func_ladder or swimming in spawnfunc_func_water
-               self.flags &= ~FL_ONGROUND;
-
-               float g;
-               g = autocvar_sv_gravity * frametime;
-               if(self.gravity)
-                       g *= self.gravity;
-               if(autocvar_sv_gameplayfix_gravityunaffectedbyticrate)
-               {
-                       g *= 0.5;
-                       self.velocity_z += g;
-               }
-
-               self.velocity = self.velocity * (1 - frametime * autocvar_sv_friction);
-               makevectors(self.v_angle);
-               //wishvel = v_forward * self.movement_x + v_right * self.movement_y + v_up * self.movement_z;
-               wishvel = v_forward * self.movement.x + v_right * self.movement.y + '0 0 1' * self.movement.z;
-               self.velocity_z += g;
-               if (self.ladder_entity.classname == "func_water")
-               {
-                       f = vlen(wishvel);
-                       if (f > self.ladder_entity.speed)
-                               wishvel = wishvel * (self.ladder_entity.speed / f);
-
-                       self.watertype = self.ladder_entity.skin;
-                       f = self.ladder_entity.origin.z + self.ladder_entity.maxs.z;
-                       if ((self.origin.z + self.view_ofs.z) < f)
-                               self.waterlevel = WATERLEVEL_SUBMERGED;
-                       else if ((self.origin.z + (self.mins.z + self.maxs.z) * 0.5) < f)
-                               self.waterlevel = WATERLEVEL_SWIMMING;
-                       else if ((self.origin.z + self.mins.z + 1) < f)
-                               self.waterlevel = WATERLEVEL_WETFEET;
-                       else
-                       {
-                               self.waterlevel = WATERLEVEL_NONE;
-                               self.watertype = CONTENT_EMPTY;
-                       }
-               }
-               // acceleration
-               wishdir = normalize(wishvel);
-               wishspeed = vlen(wishvel);
-               if (wishspeed > self.stat_sv_maxspeed*maxspd_mod)
-                       wishspeed = self.stat_sv_maxspeed*maxspd_mod;
-               if (time >= self.teleport_time)
-               {
-                       // water acceleration
-                       PM_Accelerate(wishdir, wishspeed, wishspeed, autocvar_sv_accelerate*maxspd_mod, 1, 0, 0, 0);
-               }
-       }
-       else if (self.items & IT_USING_JETPACK)
-       {
-               //makevectors(self.v_angle_y * '0 1 0');
-               makevectors(self.v_angle);
-               wishvel = v_forward * self.movement.x + v_right * self.movement.y;
-               // add remaining speed as Z component
-               maxairspd = autocvar_sv_maxairspeed*max(1, maxspd_mod);
-               // fix speedhacks :P
-               wishvel = normalize(wishvel) * min(vlen(wishvel) / maxairspd, 1);
-               // add the unused velocity as up component
-               wishvel.z = 0;
-
-               // if(self.BUTTON_JUMP)
-                       wishvel.z = sqrt(max(0, 1 - wishvel * wishvel));
-
-               // it is now normalized, so...
-               float a_side, a_up, a_add, a_diff;
-               a_side = autocvar_g_jetpack_acceleration_side;
-               a_up = autocvar_g_jetpack_acceleration_up;
-               a_add = autocvar_g_jetpack_antigravity * autocvar_sv_gravity;
-
-               wishvel.x *= a_side;
-               wishvel.y *= a_side;
-               wishvel.z *= a_up;
-               wishvel.z += a_add;
-
-               float best;
-               best = 0;
-               //////////////////////////////////////////////////////////////////////////////////////
-               // finding the maximum over all vectors of above form
-               // with wishvel having an absolute value of 1
-               //////////////////////////////////////////////////////////////////////////////////////
-               // we're finding the maximum over
-               //   f(a_side, a_up, a_add, z) := a_side * (1 - z^2) + (a_add + a_up * z)^2;
-               // for z in the range from -1 to 1
-               //////////////////////////////////////////////////////////////////////////////////////
-               // maximum is EITHER attained at the single extreme point:
-               a_diff = a_side * a_side - a_up * a_up;
-               if(a_diff != 0)
-               {
-                       f = a_add * a_up / a_diff; // this is the zero of diff(f(a_side, a_up, a_add, z), z)
-                       if(f > -1 && f < 1) // can it be attained?
-                       {
-                               best = (a_diff + a_add * a_add) * (a_diff + a_up * a_up) / a_diff;
-                               //print("middle\n");
-                       }
-               }
-               // OR attained at z = 1:
-               f = (a_up + a_add) * (a_up + a_add);
-               if(f > best)
-               {
-                       best = f;
-                       //print("top\n");
-               }
-               // OR attained at z = -1:
-               f = (a_up - a_add) * (a_up - a_add);
-               if(f > best)
-               {
-                       best = f;
-                       //print("bottom\n");
-               }
-               best = sqrt(best);
-               //////////////////////////////////////////////////////////////////////////////////////
-
-               //print("best possible acceleration: ", ftos(best), "\n");
-
-               float fxy, fz;
-               fxy = bound(0, 1 - (self.velocity * normalize(wishvel.x * '1 0 0' + wishvel.y * '0 1 0')) / autocvar_g_jetpack_maxspeed_side, 1);
-               if(wishvel.z - autocvar_sv_gravity > 0)
-                       fz = bound(0, 1 - self.velocity.z / autocvar_g_jetpack_maxspeed_up, 1);
-               else
-                       fz = bound(0, 1 + self.velocity.z / autocvar_g_jetpack_maxspeed_up, 1);
-
-               wishvel.x *= fxy;
-               wishvel.y *= fxy;
-               wishvel.z = (wishvel.z - autocvar_sv_gravity) * fz + autocvar_sv_gravity;
-
-               float fvel;
-               fvel = min(1, vlen(wishvel) / best);
-               if(autocvar_g_jetpack_fuel && !(self.items & IT_UNLIMITED_WEAPON_AMMO))
-                       f = min(1, self.ammo_fuel / (autocvar_g_jetpack_fuel * frametime * fvel));
-               else
-                       f = 1;
-
-               //print("this acceleration: ", ftos(vlen(wishvel) * f), "\n");
-
-               if (f > 0 && wishvel != '0 0 0')
-               {
-                       self.velocity = self.velocity + wishvel * f * frametime;
-                       if (!(self.items & IT_UNLIMITED_WEAPON_AMMO))
-                               self.ammo_fuel -= autocvar_g_jetpack_fuel * frametime * fvel * f;
-                       self.flags &= ~FL_ONGROUND;
-                       self.items |= IT_USING_JETPACK;
-
-                       // jetpack also inhibits health regeneration, but only for 1 second
-                       self.pauseregen_finished = max(self.pauseregen_finished, time + autocvar_g_balance_pause_fuel_regen);
-               }
-       }
-       else if (self.flags & FL_ONGROUND)
-       {
-               // we get here if we ran out of ammo
-               if((self.items & IT_JETPACK) && self.BUTTON_HOOK && !(buttons_prev & 32) && self.ammo_fuel < 0.01)
-                       Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_JETPACK_NOFUEL);
-
-               // walking
-               makevectors(self.v_angle.y * '0 1 0');
-               wishvel = v_forward * self.movement.x + v_right * self.movement.y;
-
-               if(!(self.lastflags & FL_ONGROUND))
-               {
-                       if(autocvar_speedmeter)
-                               dprint(strcat("landing velocity: ", vtos(self.velocity), " (abs: ", ftos(vlen(self.velocity)), ")\n"));
-                       if(self.lastground < time - 0.3)
-                               self.velocity = self.velocity * (1 - autocvar_sv_friction_on_land);
-                       if(self.jumppadcount > 1)
-                               dprint(strcat(ftos(self.jumppadcount), "x jumppad combo\n"));
-                       self.jumppadcount = 0;
-               }
-
-               v = self.velocity;
-               v.z = 0;
-               f = vlen(v);
-               if(f > 0)
-               {
-                       if (f < autocvar_sv_stopspeed)
-                               f = 1 - frametime * (autocvar_sv_stopspeed / f) * autocvar_sv_friction;
-                       else
-                               f = 1 - frametime * autocvar_sv_friction;
-                       if (f > 0)
-                               self.velocity = self.velocity * f;
-                       else
-                               self.velocity = '0 0 0';
-                       /*
-                          Mathematical analysis time!
-
-                          Our goal is to invert this mess.
-
-                          For the two cases we get:
-                               v = v0 * (1 - frametime * (autocvar_sv_stopspeed / v0) * autocvar_sv_friction)
-                                 = v0 - frametime * autocvar_sv_stopspeed * autocvar_sv_friction
-                               v0 = v + frametime * autocvar_sv_stopspeed * autocvar_sv_friction
-                          and
-                               v = v0 * (1 - frametime * autocvar_sv_friction)
-                               v0 = v / (1 - frametime * autocvar_sv_friction)
-
-                          These cases would be chosen ONLY if:
-                               v0 < autocvar_sv_stopspeed
-                               v + frametime * autocvar_sv_stopspeed * autocvar_sv_friction < autocvar_sv_stopspeed
-                               v < autocvar_sv_stopspeed * (1 - frametime * autocvar_sv_friction)
-                          and, respectively:
-                               v0 >= autocvar_sv_stopspeed
-                               v / (1 - frametime * autocvar_sv_friction) >= autocvar_sv_stopspeed
-                               v >= autocvar_sv_stopspeed * (1 - frametime * autocvar_sv_friction)
-                        */
-               }
-
-               // acceleration
-               wishdir = normalize(wishvel);
-               wishspeed = vlen(wishvel);
-               if (wishspeed > self.stat_sv_maxspeed*maxspd_mod)
-                       wishspeed = self.stat_sv_maxspeed*maxspd_mod;
-               if (self.crouch)
-                       wishspeed = wishspeed * 0.5;
-               if (time >= self.teleport_time)
-                       PM_Accelerate(wishdir, wishspeed, wishspeed, autocvar_sv_accelerate*maxspd_mod, 1, 0, 0, 0);
-       }
-       else
-       {
-               float wishspeed0;
-               // we get here if we ran out of ammo
-               if((self.items & IT_JETPACK) && self.BUTTON_HOOK && !(buttons_prev & 32) && self.ammo_fuel < 0.01)
-                       Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_JETPACK_NOFUEL);
-
-               if(maxspd_mod < 1)
-               {
-                       maxairspd = autocvar_sv_maxairspeed*maxspd_mod;
-                       airaccel = autocvar_sv_airaccelerate*maxspd_mod;
-               }
-               else
-               {
-                       maxairspd = autocvar_sv_maxairspeed;
-                       airaccel = autocvar_sv_airaccelerate;
-               }
-               // airborn
-               makevectors(self.v_angle.y * '0 1 0');
-               wishvel = v_forward * self.movement.x + v_right * self.movement.y;
-               // acceleration
-               wishdir = normalize(wishvel);
-               wishspeed = wishspeed0 = vlen(wishvel);
-               if (wishspeed0 > self.stat_sv_maxspeed*maxspd_mod)
-                       wishspeed0 = self.stat_sv_maxspeed*maxspd_mod;
-               if (wishspeed > maxairspd)
-                       wishspeed = maxairspd;
-               if (self.crouch)
-                       wishspeed = wishspeed * 0.5;
-               if (time >= self.teleport_time)
-               {
-                       float accelerating;
-                       float wishspeed2;
-                       float airaccelqw;
-                       float strafity;
-
-                       airaccelqw = self.stat_sv_airaccel_qw;
-                       accelerating = (self.velocity * wishdir > 0);
-                       wishspeed2 = wishspeed;
-
-                       // CPM
-                       if(autocvar_sv_airstopaccelerate)
-                       {
-                               vector curdir;
-                               curdir = self.velocity;
-                               curdir.z = 0;
-                               curdir = normalize(curdir);
-                               airaccel = airaccel + (autocvar_sv_airstopaccelerate*maxspd_mod - airaccel) * max(0, -(curdir * wishdir));
-                       }
-                       // note that for straight forward jumping:
-                       // step = accel * frametime * wishspeed0;
-                       // accel  = bound(0, wishspeed - vel_xy_current, step) * accelqw + step * (1 - accelqw);
-                       // -->
-                       // dv/dt = accel * maxspeed (when slow)
-                       // dv/dt = accel * maxspeed * (1 - accelqw) (when fast)
-                       // log dv/dt = logaccel + logmaxspeed (when slow)
-                       // log dv/dt = logaccel + logmaxspeed + log(1 - accelqw) (when fast)
-                       strafity = IsMoveInDirection(self.movement, -90) + IsMoveInDirection(self.movement, +90); // if one is nonzero, other is always zero
-                       if(autocvar_sv_maxairstrafespeed)
-                               wishspeed = min(wishspeed, GeomLerp(autocvar_sv_maxairspeed*maxspd_mod, strafity, autocvar_sv_maxairstrafespeed*maxspd_mod));
-                       if(autocvar_sv_airstrafeaccelerate)
-                               airaccel = GeomLerp(airaccel, strafity, autocvar_sv_airstrafeaccelerate*maxspd_mod);
-                       if(self.stat_sv_airstrafeaccel_qw)
-                               airaccelqw = copysign(1-GeomLerp(1-fabs(self.stat_sv_airaccel_qw), strafity, 1-fabs(self.stat_sv_airstrafeaccel_qw)), ((strafity > 0.5) ? self.stat_sv_airstrafeaccel_qw : self.stat_sv_airaccel_qw));
-                       // !CPM
-
-                       if(autocvar_sv_warsowbunny_turnaccel && accelerating && self.movement_y == 0 && self.movement.x != 0)
-                               PM_AirAccelerate(wishdir, wishspeed);
-                       else
-                               PM_Accelerate(wishdir, wishspeed, wishspeed0, airaccel, airaccelqw, autocvar_sv_airaccel_qw_stretchfactor, autocvar_sv_airaccel_sideways_friction / maxairspd, self.stat_sv_airspeedlimit_nonqw);
-
-                       if(autocvar_sv_aircontrol)
-                               CPM_PM_Aircontrol(wishdir, wishspeed2);
-               }
-       }
-
-       if((g_cts || g_race) && !IS_OBSERVER(self))
-       {
-               if(vlen(self.velocity - self.velocity.z * '0 0 1') > speedaward_speed)
-               {
-                       speedaward_speed = vlen(self.velocity - self.velocity.z * '0 0 1');
-                       speedaward_holder = self.netname;
-                       speedaward_uid = self.crypto_idfp;
-                       speedaward_lastupdate = time;
-               }
-               if(speedaward_speed > speedaward_lastsent && time - speedaward_lastupdate > 1)
-               {
-                       string rr = (g_cts) ? CTS_RECORD : RACE_RECORD;
-                       race_send_speedaward(MSG_ALL);
-                       speedaward_lastsent = speedaward_speed;
-                       if (speedaward_speed > speedaward_alltimebest && speedaward_uid != "")
-                       {
-                               speedaward_alltimebest = speedaward_speed;
-                               speedaward_alltimebest_holder = speedaward_holder;
-                               speedaward_alltimebest_uid = speedaward_uid;
-                               db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/speed"), ftos(speedaward_alltimebest));
-                               db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/crypto_idfp"), speedaward_alltimebest_uid);
-                               race_send_speedaward_alltimebest(MSG_ALL);
-                       }
-               }
-       }
-
-       // WEAPONTODO
-       float xyspeed;
-       xyspeed = vlen('1 0 0' * self.velocity.x + '0 1 0' * self.velocity.y);
-       if(self.weapon == WEP_VORTEX && WEP_CVAR(vortex, charge) && WEP_CVAR(vortex, charge_velocity_rate) && xyspeed > WEP_CVAR(vortex, charge_minspeed))
-       {
-               // add a maximum of charge_velocity_rate when going fast (f = 1), gradually increasing from minspeed (f = 0) to maxspeed
-               xyspeed = min(xyspeed, WEP_CVAR(vortex, charge_maxspeed));
-               f = (xyspeed - WEP_CVAR(vortex, charge_minspeed)) / (WEP_CVAR(vortex, charge_maxspeed) - WEP_CVAR(vortex, charge_minspeed));
-               // add the extra charge
-               self.vortex_charge = min(1, self.vortex_charge + WEP_CVAR(vortex, charge_velocity_rate) * f * frametime);
-       }
-:end
-       if(self.flags & FL_ONGROUND)
-               self.lastground = time;
-
-       // conveyors: then break velocity again
-       if(self.conveyor.state)
-               self.velocity += self.conveyor.movedir;
-
-       self.lastflags = self.flags;
-       self.lastclassname = self.classname;
-}
index 49d1c1b4691c3136f11c4fd3aa87c27a816c9ea9..49ae14a6a24a7339d6868bc66bd779a9260b54a6 100644 (file)
@@ -1,10 +1,11 @@
 #include "cl_player.qh"
-#include "g_triggers.qh"
 #include "g_violence.qh"
 #include "miscfunctions.qh"
 
 #include "weapons/weaponstats.qh"
 
+#include "../common/animdecide.qh"
+
 void CopyBody_Think(void)
 {
        if(self.CopyBody_nextthink && time > self.CopyBody_nextthink)
index 1d4cc55232382a6fcd7f2491325b6364ec5a9736..c1def6b821e469d43d1183f371ff1039e0009d53 100644 (file)
@@ -23,5 +23,4 @@ const int NUM_PLAYERSKINS_TEAMPLAY = 3;
 
 const int ASSAULT_VALUE_INACTIVE = 1000;
 
-const int DOOR_NOSPLASH = 256; // generic anti-splashdamage spawnflag
 #endif
index 822c5163c384c1476656d825ab0a009f549ac406..c430029d0c31ce95513bf835599a425bb2f0c1ed 100644 (file)
@@ -46,8 +46,6 @@ float g_jetpack;
 float sv_clones;
 float sv_foginterval;
 
-entity activator;
-
 float player_count;
 float currentbots;
 float bots_would_leave;
@@ -70,21 +68,6 @@ float server_is_dedicated;
 //.string      map;
 
 //.float       worldtype;
-.float delay;
-.float wait;
-.float lip;
-//.float       light_lev;
-.float speed;
-//.float       style;
-//.float       skill;
-.float sounds;
-.string  platmovetype;
-.float platmovetype_start, platmovetype_end;
-
-.string killtarget;
-
-.vector        pos1, pos2;
-.vector        mangle;
 
 .float pain_finished;                  //Added by Supajoe
 .float pain_frame;                     //"
@@ -94,14 +77,7 @@ float server_is_dedicated;
 .float invincible_finished;
 .float superweapons_finished;
 
-.vector                finaldest, finalangle;          //plat.qc stuff
-.void()                think1;
-.float state;
-.float         t_length, t_width;
-
-.vector destvec;               // for rain
-.vector destvec2;              // for train
-.float cnt;            // for rain
+.float cnt; // used in too many places
 .float count;
 //.float cnt2;
 
@@ -113,15 +89,6 @@ float server_is_dedicated;
 .float fade_time;
 .float fade_rate;
 
-// player animation state
-.float animstate_startframe;
-.float animstate_numframes;
-.float animstate_framerate;
-.float animstate_starttime;
-.float animstate_endtime;
-.float animstate_override;
-.float animstate_looping;
-
 // weapon animation vectors:
 .vector anim_fire1;
 .vector anim_fire2;
@@ -210,26 +177,13 @@ const int W_TICSPERFRAME = 2;
 
 void weapon_defaultspawnfunc(float wpn);
 
-.vector dest1, dest2;
-
 float gameover;
 float intermission_running;
 float intermission_exittime;
 float alreadychangedlevel;
 
-// Keys player is holding
-.float itemkeys;
-// message delay for func_door locked by keys and key locks
-// this field is used on player entities
-.float key_door_messagetime;
-
-
 .float version;
 
-//swamp
-.float in_swamp;              // bool
-.entity swampslug;            // Uses this to release from swamp ("untouch" fix)
-
 // footstep interval
 .float nextstep;
 
@@ -284,10 +238,6 @@ float default_weapon_alpha;
 
 .float version_nagtime;
 
-const int NUM_JUMPPADSUSED = 3;
-.float jumppadcount;
-.entity jumppadsused[NUM_JUMPPADSUSED];
-
 string gamemode_name;
 
 float startitem_failed;
@@ -447,11 +397,6 @@ float round_starttime; //point in time when the countdown to round start is over
 .float stat_game_starttime;
 .float stat_round_starttime;
 
-.float stat_sv_airaccel_qw;
-.float stat_sv_airstrafeaccel_qw;
-.float stat_sv_airspeedlimit_nonqw;
-.float stat_sv_maxspeed;
-
 void W_Porto_Remove (entity p);
 
 .int projectiledeathtype;
@@ -464,16 +409,6 @@ void W_Porto_Remove (entity p);
 // may be useful to all weapons
 .float bulletcounter;
 
-void target_voicescript_next(entity pl);
-void target_voicescript_clear(entity pl);
-
-.string target2;
-.string target3;
-.string target4;
-.string curvetarget;
-.float target_random;
-.float trigger_reverse;
-
 // Nexball
 .entity ballcarried; // Also used for keepaway
 .float metertime;
@@ -594,8 +529,6 @@ void PlayerUseKey();
 typedef vector(entity player, entity spot, vector current) spawn_evalfunc_t;
 .spawn_evalfunc_t spawn_evalfunc;
 
-.entity conveyor;
-
 string modname;
 
 .float missile_flags;
diff --git a/qcsrc/server/func_breakable.qc b/qcsrc/server/func_breakable.qc
deleted file mode 100644 (file)
index be6104f..0000000
+++ /dev/null
@@ -1,312 +0,0 @@
-#include "weapons/common.qh"
-
-.entity sprite;
-
-.float dmg;
-.float dmg_edge;
-.float dmg_radius;
-.float dmg_force;
-.float debrismovetype;
-.float debrissolid;
-.vector debrisvelocity;
-.vector debrisvelocityjitter;
-.vector debrisavelocityjitter;
-.float debristime;
-.float debristimejitter;
-.float debrisfadetime;
-.float debrisdamageforcescale;
-.float debrisskin;
-
-.string mdl_dead; // or "" to hide when broken
-.string debris; // space separated list of debris models
-// other fields:
-//   mdl = particle effect name
-//   count = particle effect multiplier
-//   targetname = target to trigger to unbreak the model
-//   target = targets to trigger when broken
-//   health = amount of damage it can take
-//   spawnflags:
-//     1 = start disabled (needs to be triggered to activate)
-//     2 = indicate damage
-// notes:
-//   for mdl_dead to work, origin must be set (using a common/origin brush).
-//   Otherwise mdl_dead will be displayed at the map origin, and nobody would
-//   want that!
-
-void func_breakable_damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force);
-
-//
-// func_breakable
-// - basically func_assault_destructible for general gameplay use
-//
-void LaunchDebris (string debrisname, vector force)
-{
-       entity dbr = spawn();
-       setorigin(dbr, self.absmin
-                  + '1 0 0' * random() * (self.absmax.x - self.absmin.x)
-                  + '0 1 0' * random() * (self.absmax.y - self.absmin.y)
-                  + '0 0 1' * random() * (self.absmax.z - self.absmin.z));
-       setmodel (dbr, debrisname );
-       dbr.skin = self.debrisskin;
-       dbr.colormap = self.colormap; // inherit team colors
-       dbr.owner = self; // do not be affected by our own explosion
-       dbr.movetype = self.debrismovetype;
-       dbr.solid = self.debrissolid;
-       if(dbr.solid != SOLID_BSP) // SOLID_BSP has exact collision, MAYBE this works? TODO check this out
-               setsize(dbr, '0 0 0', '0 0 0'); // needed for performance, until engine can deal better with it
-       dbr.velocity_x = self.debrisvelocity.x + self.debrisvelocityjitter.x * crandom();
-       dbr.velocity_y = self.debrisvelocity.y + self.debrisvelocityjitter.y * crandom();
-       dbr.velocity_z = self.debrisvelocity.z + self.debrisvelocityjitter.z * crandom();
-       self.velocity = self.velocity + force * self.debrisdamageforcescale;
-       dbr.avelocity_x = random()*self.debrisavelocityjitter.x;
-       dbr.avelocity_y = random()*self.debrisavelocityjitter.y;
-       dbr.avelocity_z = random()*self.debrisavelocityjitter.z;
-       dbr.damageforcescale = self.debrisdamageforcescale;
-       if(dbr.damageforcescale)
-               dbr.takedamage = DAMAGE_YES;
-       SUB_SetFade(dbr, time + self.debristime + crandom() * self.debristimejitter, self.debrisfadetime);
-}
-
-void func_breakable_colormod()
-{
-       float h;
-       if (!(self.spawnflags & 2))
-               return;
-       h = self.health / self.max_health;
-       if(h < 0.25)
-               self.colormod = '1 0 0';
-       else if(h <= 0.75)
-               self.colormod = '1 0 0' + '0 1 0' * (2 * h - 0.5);
-       else
-               self.colormod = '1 1 1';
-
-       CSQCMODEL_AUTOUPDATE();
-}
-
-void func_breakable_look_destroyed()
-{
-       float floorZ;
-
-       if(self.solid == SOLID_BSP) // in case a misc_follow moved me, save the current origin first
-               self.dropped_origin = self.origin;
-
-       if(self.mdl_dead == "")
-               self.effects |= EF_NODRAW;
-       else {
-               if (self.origin == '0 0 0')     {       // probably no origin brush, so don't spawn in the middle of the map..
-                       floorZ = self.absmin.z;
-                       setorigin(self,((self.absmax+self.absmin)*.5));
-                       self.origin_z = floorZ;
-               }
-               setmodel(self, self.mdl_dead);
-               self.effects &= ~EF_NODRAW;
-       }
-
-       CSQCMODEL_AUTOUPDATE();
-
-       self.solid = SOLID_NOT;
-}
-
-void func_breakable_look_restore()
-{
-       setmodel(self, self.mdl);
-       self.effects &= ~EF_NODRAW;
-
-       if(self.mdl_dead != "") // only do this if we use mdl_dead, to behave better with misc_follow
-               setorigin(self, self.dropped_origin);
-
-       CSQCMODEL_AUTOUPDATE();
-
-       self.solid = SOLID_BSP;
-}
-
-void func_breakable_behave_destroyed()
-{
-       self.health = self.max_health;
-       self.takedamage = DAMAGE_NO;
-       self.bot_attack = false;
-       self.event_damage = func_null;
-       self.state = 1;
-       func_breakable_colormod();
-       if (self.noise1)
-               stopsound (self, CH_TRIGGER_SINGLE);
-}
-
-void func_breakable_behave_restore()
-{
-       self.health = self.max_health;
-       if(self.sprite)
-       {
-               WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health);
-               WaypointSprite_UpdateHealth(self.sprite, self.health);
-       }
-       self.takedamage = DAMAGE_AIM;
-       self.bot_attack = true;
-       self.event_damage = func_breakable_damage;
-       self.state = 0;
-       self.nextthink = 0; // cancel auto respawn
-       func_breakable_colormod();
-       if (self.noise1)
-               sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
-}
-
-void func_breakable_init_for_player(entity player)
-{
-       if (self.noise1 && self.state == 0 && clienttype(player) == CLIENTTYPE_REAL)
-       {
-               msg_entity = player;
-               soundto (MSG_ONE, self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
-       }
-}
-
-void func_breakable_destroyed()
-{
-       func_breakable_look_destroyed();
-       func_breakable_behave_destroyed();
-
-       CSQCMODEL_AUTOUPDATE();
-}
-
-void func_breakable_restore()
-{
-       func_breakable_look_restore();
-       func_breakable_behave_restore();
-
-       CSQCMODEL_AUTOUPDATE();
-}
-
-vector debrisforce; // global, set before calling this
-void func_breakable_destroy() {
-       float n, i;
-       string oldmsg;
-
-       activator = self.owner;
-       self.owner = world; // set by W_PrepareExplosionByDamage
-
-       // now throw around the debris
-       n = tokenize_console(self.debris);
-       for(i = 0; i < n; ++i)
-               LaunchDebris(argv(i), debrisforce);
-
-       func_breakable_destroyed();
-
-       if(self.noise)
-               sound (self, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
-
-       if(self.dmg)
-               RadiusDamage(self, activator, self.dmg, self.dmg_edge, self.dmg_radius, self, world, self.dmg_force, DEATH_HURTTRIGGER, world);
-
-       if(self.cnt)
-               pointparticles(self.cnt, self.absmin * 0.5 + self.absmax * 0.5, '0 0 0', self.count);
-
-       if(self.respawntime)
-       {
-               self.think = func_breakable_restore;
-               self.nextthink = time + self.respawntime + crandom() * self.respawntimejitter;
-       }
-
-       oldmsg = self.message;
-       self.message = "";
-       SUB_UseTargets();
-       self.message = oldmsg;
-}
-
-void func_breakable_damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
-{
-       if(self.state == 1)
-               return;
-       if(self.spawnflags & DOOR_NOSPLASH)
-               if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
-                       return;
-       if(self.team)
-               if(attacker.team == self.team)
-                       return;
-       self.health = self.health - damage;
-       if(self.sprite)
-       {
-               WaypointSprite_Ping(self.sprite);
-               WaypointSprite_UpdateHealth(self.sprite, self.health);
-       }
-       func_breakable_colormod();
-
-       if(self.health <= 0)
-       {
-               debrisforce = force;
-               W_PrepareExplosionByDamage(attacker, func_breakable_destroy);
-       }
-}
-
-void func_breakable_reset()
-{
-       self.team = self.team_saved;
-       func_breakable_look_restore();
-       if(self.spawnflags & 1)
-               func_breakable_behave_destroyed();
-       else
-               func_breakable_behave_restore();
-
-       CSQCMODEL_AUTOUPDATE();
-}
-
-// destructible walls that can be used to trigger target_objective_decrease
-void spawnfunc_func_breakable() {
-       float n, i;
-       if(!self.health)
-               self.health = 100;
-       self.max_health = self.health;
-
-       // yes, I know, MOVETYPE_NONE is not available here, not that one would want it here anyway
-       if(!self.debrismovetype) self.debrismovetype = MOVETYPE_BOUNCE;
-       if(!self.debrissolid) self.debrissolid = SOLID_NOT;
-       if(self.debrisvelocity == '0 0 0') self.debrisvelocity = '0 0 140';
-       if(self.debrisvelocityjitter == '0 0 0') self.debrisvelocityjitter = '70 70 70';
-       if(self.debrisavelocityjitter == '0 0 0') self.debrisavelocityjitter = '600 600 600';
-       if(!self.debristime) self.debristime = 3.5;
-       if(!self.debristimejitter) self.debristime = 2.5;
-
-       if(self.mdl != "")
-               self.cnt = particleeffectnum(self.mdl);
-       if(self.count == 0)
-               self.count = 1;
-
-       if(self.message == "")
-               self.message = "got too close to an explosion";
-       if(self.message2 == "")
-               self.message2 = "was pushed into an explosion by";
-       if(!self.dmg_radius)
-               self.dmg_radius = 150;
-       if(!self.dmg_force)
-               self.dmg_force = 200;
-
-       self.mdl = self.model;
-       SetBrushEntityModel();
-
-       self.use = func_breakable_restore;
-
-       // precache all the models
-       if (self.mdl_dead)
-               precache_model(self.mdl_dead);
-       n = tokenize_console(self.debris);
-       for(i = 0; i < n; ++i)
-               precache_model(argv(i));
-       if(self.noise)
-               precache_sound(self.noise);
-       if(self.noise1)
-               precache_sound(self.noise1);
-
-       self.team_saved = self.team;
-       self.dropped_origin = self.origin;
-
-       self.reset = func_breakable_reset;
-       func_breakable_reset();
-
-       self.init_for_player_needed = 1;
-       self.init_for_player = func_breakable_init_for_player;
-
-       CSQCMODEL_AUTOINIT();
-}
-
-// for use in maps with a "model" key set
-void spawnfunc_misc_breakablemodel() {
-       spawnfunc_func_breakable();
-}
index d170cf4c020db0b7e229777a858fb5c55afb6825..14f74d00e511808c217b780cdc1f43bc398045aa 100644 (file)
@@ -25,6 +25,7 @@
     #include "g_hook.qh"
     #include "scores.qh"
     #include "spawnpoints.qh"
+       #include "../common/movetypes/movetypes.qh"
 #endif
 
 float Damage_DamageInfo_SendEntity(entity to, float sf)
@@ -66,18 +67,6 @@ void Damage_DamageInfo(vector org, float coredamage, float edgedamage, float rad
        Net_LinkEntity(e, false, 0.2, Damage_DamageInfo_SendEntity);
 }
 
-float IsFlying(entity a)
-{
-       if(a.flags & FL_ONGROUND)
-               return 0;
-       if(a.waterlevel >= WATERLEVEL_SWIMMING)
-               return 0;
-       traceline(a.origin, a.origin - '0 0 48', MOVE_NORMAL, a);
-       if(trace_fraction < 1)
-               return 0;
-       return 1;
-}
-
 void UpdateFrags(entity player, float f)
 {
        PlayerTeamScore_AddScore(player, f);
@@ -929,8 +918,12 @@ void Damage (entity targ, entity inflictor, entity attacker, float damage, float
                        farcent.think = SUB_Remove;
                }
                else
+               {
                        self.velocity = self.velocity + farce;
+                       self.move_velocity = self.velocity;
+               }
                self.flags &= ~FL_ONGROUND;
+               self.move_flags &= ~FL_ONGROUND;
                UpdateCSQCProjectile(self);
        }
        // apply damage
index 310833e4367ce2f036ad10c4570be683bfb4f275..05032c2d057185334c2accde72c6f6b1ddfbde6c 100644 (file)
@@ -15,6 +15,7 @@
     #include "command/common.qh"
     #include "g_hook.qh"
     #include "round_handler.qh"
+       #include "weapons/common.qh"
 #endif
 
 /*============================================
index 6e1c06d056f2eb5bf3306240042ad3b933af56bc..95b83f3d7a9c1a5a076736dc7c1ff99a68066228 100644 (file)
@@ -4,6 +4,7 @@
        #include "../dpdefs/progsdefs.qh"
     #include "../dpdefs/dpextensions.qh"
     #include "../common/constants.qh"
+       #include "../common/triggers/subs.qh"
     #include "autocvars.qh"
     #include "constants.qh"
     #include "defs.qh"
index 74a8cf7f96126135f1ec45074a86d9d0036e3201..d42cc6750f6d9000ef8cc318c088770a30ac0906 100644 (file)
@@ -1,7 +1,5 @@
 #include "g_subs.qh"
 
-void SUB_NullThink(void) { }
-
 void spawnfunc_info_null (void)
 {
        remove(self);
@@ -52,380 +50,6 @@ void updateanim(entity e)
        //print(ftos(time), " -> ", ftos(e.frame), "\n");
 }
 
-/*
-==================
-SUB_Remove
-
-Remove self
-==================
-*/
-void SUB_Remove (void)
-{
-       remove (self);
-}
-
-/*
-==================
-SUB_Friction
-
-Applies some friction to self
-==================
-*/
-void SUB_Friction (void)
-{
-       self.nextthink = time;
-       if(self.flags & FL_ONGROUND)
-               self.velocity = self.velocity * (1 - frametime * self.friction);
-}
-
-/*
-==================
-SUB_VanishOrRemove
-
-Makes client invisible or removes non-client
-==================
-*/
-void SUB_VanishOrRemove (entity ent)
-{
-       if (IS_CLIENT(ent))
-       {
-               // vanish
-               ent.alpha = -1;
-               ent.effects = 0;
-               ent.glow_size = 0;
-               ent.pflags = 0;
-       }
-       else
-       {
-               // remove
-               remove (ent);
-       }
-}
-
-void SUB_SetFade_Think (void)
-{
-       if(self.alpha == 0)
-               self.alpha = 1;
-       self.think = SUB_SetFade_Think;
-       self.nextthink = time;
-       self.alpha -= frametime * self.fade_rate;
-       if (self.alpha < 0.01)
-               SUB_VanishOrRemove(self);
-       else
-               self.nextthink = time;
-}
-
-/*
-==================
-SUB_SetFade
-
-Fade 'ent' out when time >= 'when'
-==================
-*/
-void SUB_SetFade (entity ent, float when, float fadetime)
-{
-       ent.fade_rate = 1/fadetime;
-       ent.think = SUB_SetFade_Think;
-       ent.nextthink = when;
-}
-
-/*
-=============
-SUB_CalcMove
-
-calculate self.velocity and self.nextthink to reach dest from
-self.origin traveling at speed
-===============
-*/
-void SUB_CalcMoveDone (void)
-{
-       // After moving, set origin to exact final destination
-
-       setorigin (self, self.finaldest);
-       self.velocity = '0 0 0';
-       self.nextthink = -1;
-       if (self.think1)
-               self.think1 ();
-}
-
-void SUB_CalcMove_controller_think (void)
-{
-       entity oldself;
-       float traveltime;
-       float phasepos;
-       float nexttick;
-       vector delta;
-       vector delta2;
-       vector veloc;
-       vector angloc;
-       vector nextpos;
-       delta = self.destvec;
-       delta2 = self.destvec2;
-       if(time < self.animstate_endtime) {
-               nexttick = time + sys_frametime;
-
-               traveltime = self.animstate_endtime - self.animstate_starttime;
-               phasepos = (nexttick - self.animstate_starttime) / traveltime; // range: [0, 1]
-               phasepos = cubic_speedfunc(self.platmovetype_start, self.platmovetype_end, phasepos);
-               nextpos = self.origin + (delta * phasepos) + (delta2 * phasepos * phasepos);
-               // derivative: delta + 2 * delta2 * phasepos (e.g. for angle positioning)
-
-               if(self.owner.platmovetype_turn)
-               {
-                       vector destangle;
-                       destangle = delta + 2 * delta2 * phasepos;
-                       destangle = vectoangles(destangle);
-                       destangle.x = -destangle.x; // flip up / down orientation
-
-                       // take the shortest distance for the angles
-                       self.owner.angles_x -= 360 * floor((self.owner.angles.x - destangle.x) / 360 + 0.5);
-                       self.owner.angles_y -= 360 * floor((self.owner.angles.y - destangle.y) / 360 + 0.5);
-                       self.owner.angles_z -= 360 * floor((self.owner.angles.z - destangle.z) / 360 + 0.5);
-                       angloc = destangle - self.owner.angles;
-                       angloc = angloc * (1 / sys_frametime); // so it arrives for the next frame
-                       self.owner.avelocity = angloc;
-               }
-               if(nexttick < self.animstate_endtime)
-                       veloc = nextpos - self.owner.origin;
-               else
-                       veloc = self.finaldest - self.owner.origin;
-               veloc = veloc * (1 / sys_frametime); // so it arrives for the next frame
-
-               self.owner.velocity = veloc;
-               self.nextthink = nexttick;
-       } else {
-               // derivative: delta + 2 * delta2 (e.g. for angle positioning)
-               oldself = self;
-               self.owner.think = self.think1;
-               self = self.owner;
-               remove(oldself);
-               self.think();
-       }
-}
-
-void SUB_CalcMove_controller_setbezier (entity controller, vector org, vector control, vector dest)
-{
-       // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + dest * t * t
-       // 2 * control * t - 2 * control * t * t + dest * t * t
-       // 2 * control * t + (dest - 2 * control) * t * t
-
-       controller.origin = org; // starting point
-       control -= org;
-       dest -= org;
-
-       controller.destvec = 2 * control; // control point
-       controller.destvec2 = dest - 2 * control; // quadratic part required to reach end point
-       // also: initial d/dphasepos origin = 2 * control, final speed = 2 * (dest - control)
-}
-
-void SUB_CalcMove_controller_setlinear (entity controller, vector org, vector dest)
-{
-       // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + dest * t * t
-       // 2 * control * t - 2 * control * t * t + dest * t * t
-       // 2 * control * t + (dest - 2 * control) * t * t
-
-       controller.origin = org; // starting point
-       dest -= org;
-
-       controller.destvec = dest; // end point
-       controller.destvec2 = '0 0 0';
-}
-
-void SUB_CalcMove_Bezier (vector tcontrol, vector tdest, float tspeedtype, float tspeed, void() func)
-{
-       float   traveltime;
-       entity controller;
-
-       if (!tspeed)
-               objerror ("No speed is defined!");
-
-       self.think1 = func;
-       self.finaldest = tdest;
-       self.think = SUB_CalcMoveDone;
-
-       switch(tspeedtype)
-       {
-               default:
-               case TSPEED_START:
-                       traveltime = 2 * vlen(tcontrol - self.origin) / tspeed;
-                       break;
-               case TSPEED_END:
-                       traveltime = 2 * vlen(tcontrol - tdest)       / tspeed;
-                       break;
-               case TSPEED_LINEAR:
-                       traveltime = vlen(tdest - self.origin)        / tspeed;
-                       break;
-               case TSPEED_TIME:
-                       traveltime = tspeed;
-                       break;
-       }
-
-       if (traveltime < 0.1) // useless anim
-       {
-               self.velocity = '0 0 0';
-               self.nextthink = self.ltime + 0.1;
-               return;
-       }
-
-       controller = spawn();
-       controller.classname = "SUB_CalcMove_controller";
-       controller.owner = self;
-       controller.platmovetype = self.platmovetype;
-       controller.platmovetype_start = self.platmovetype_start;
-       controller.platmovetype_end = self.platmovetype_end;
-       SUB_CalcMove_controller_setbezier(controller, self.origin, tcontrol, tdest);
-       controller.finaldest = (tdest + '0 0 0.125'); // where do we want to end? Offset to overshoot a bit.
-       controller.animstate_starttime = time;
-       controller.animstate_endtime = time + traveltime;
-       controller.think = SUB_CalcMove_controller_think;
-       controller.think1 = self.think;
-
-       // the thinking is now done by the controller
-       self.think = SUB_NullThink; // for PushMove
-       self.nextthink = self.ltime + traveltime;
-
-       // invoke controller
-       self = controller;
-       self.think();
-       self = self.owner;
-}
-
-void SUB_CalcMove (vector tdest, float tspeedtype, float tspeed, void() func)
-{
-       vector  delta;
-       float   traveltime;
-
-       if (!tspeed)
-               objerror ("No speed is defined!");
-
-       self.think1 = func;
-       self.finaldest = tdest;
-       self.think = SUB_CalcMoveDone;
-
-       if (tdest == self.origin)
-       {
-               self.velocity = '0 0 0';
-               self.nextthink = self.ltime + 0.1;
-               return;
-       }
-
-       delta = tdest - self.origin;
-
-       switch(tspeedtype)
-       {
-               default:
-               case TSPEED_START:
-               case TSPEED_END:
-               case TSPEED_LINEAR:
-                       traveltime = vlen (delta) / tspeed;
-                       break;
-               case TSPEED_TIME:
-                       traveltime = tspeed;
-                       break;
-       }
-
-       // Very short animations don't really show off the effect
-       // of controlled animation, so let's just use linear movement.
-       // Alternatively entities can choose to specify non-controlled movement.
-        // The only currently implemented alternative movement is linear (value 1)
-       if (traveltime < 0.15 || (self.platmovetype_start == 1 && self.platmovetype_end == 1)) // is this correct?
-       {
-               self.velocity = delta * (1/traveltime); // QuakeC doesn't allow vector/float division
-               self.nextthink = self.ltime + traveltime;
-               return;
-       }
-
-       // now just run like a bezier curve...
-       SUB_CalcMove_Bezier((self.origin + tdest) * 0.5, tdest, tspeedtype, tspeed, func);
-}
-
-void SUB_CalcMoveEnt (entity ent, vector tdest, float tspeedtype, float tspeed, void() func)
-{
-       entity  oldself;
-
-       oldself = self;
-       self = ent;
-
-       SUB_CalcMove (tdest, tspeedtype, tspeed, func);
-
-       self = oldself;
-}
-
-/*
-=============
-SUB_CalcAngleMove
-
-calculate self.avelocity and self.nextthink to reach destangle from
-self.angles rotating
-
-The calling function should make sure self.think is valid
-===============
-*/
-void SUB_CalcAngleMoveDone (void)
-{
-       // After rotating, set angle to exact final angle
-       self.angles = self.finalangle;
-       self.avelocity = '0 0 0';
-       self.nextthink = -1;
-       if (self.think1)
-               self.think1 ();
-}
-
-// FIXME: I fixed this function only for rotation around the main axes
-void SUB_CalcAngleMove (vector destangle, float tspeedtype, float tspeed, void() func)
-{
-       vector  delta;
-       float   traveltime;
-
-       if (!tspeed)
-               objerror ("No speed is defined!");
-
-       // take the shortest distance for the angles
-       self.angles_x -= 360 * floor((self.angles.x - destangle.x) / 360 + 0.5);
-       self.angles_y -= 360 * floor((self.angles.y - destangle.y) / 360 + 0.5);
-       self.angles_z -= 360 * floor((self.angles.z - destangle.z) / 360 + 0.5);
-       delta = destangle - self.angles;
-
-       switch(tspeedtype)
-       {
-               default:
-               case TSPEED_START:
-               case TSPEED_END:
-               case TSPEED_LINEAR:
-                       traveltime = vlen (delta) / tspeed;
-                       break;
-               case TSPEED_TIME:
-                       traveltime = tspeed;
-                       break;
-       }
-
-       self.think1 = func;
-       self.finalangle = destangle;
-       self.think = SUB_CalcAngleMoveDone;
-
-       if (traveltime < 0.1)
-       {
-               self.avelocity = '0 0 0';
-               self.nextthink = self.ltime + 0.1;
-               return;
-       }
-
-       self.avelocity = delta * (1 / traveltime);
-       self.nextthink = self.ltime + traveltime;
-}
-
-void SUB_CalcAngleMoveEnt (entity ent, vector destangle, float tspeedtype, float tspeed, void() func)
-{
-       entity  oldself;
-
-       oldself = self;
-       self = ent;
-
-       SUB_CalcAngleMove (destangle, tspeedtype, tspeed, func);
-
-       self = oldself;
-}
-
 /*
 ==================
 main
index 04477e62919dc48477448f104366f39d2f8224e6..d62bbb6124e57bd014b2976bde0d5532990b5f98 100644 (file)
@@ -70,12 +70,6 @@ void SUB_CalcMove_controller_setbezier (entity controller, vector org, vector co
 
 void SUB_CalcMove_controller_setlinear (entity controller, vector org, vector dest);
 
-float TSPEED_TIME = -1;
-float TSPEED_LINEAR = 0;
-float TSPEED_START = 1;
-float TSPEED_END = 2;
-// TODO average too?
-
 void SUB_CalcMove_Bezier (vector tcontrol, vector tdest, float tspeedtype, float tspeed, void() func);
 
 void SUB_CalcMove (vector tdest, float tspeedtype, float tspeed, void() func);
diff --git a/qcsrc/server/g_triggers.qc b/qcsrc/server/g_triggers.qc
deleted file mode 100644 (file)
index 9bb1e06..0000000
+++ /dev/null
@@ -1,2129 +0,0 @@
-#include "g_triggers.qh"
-#include "t_jumppads.qh"
-
-void SUB_DontUseTargets()
-{
-}
-
-
-void DelayThink()
-{
-       activator = self.enemy;
-       SUB_UseTargets ();
-       remove(self);
-}
-
-/*
-==============================
-SUB_UseTargets
-
-the global "activator" should be set to the entity that initiated the firing.
-
-If self.delay is set, a DelayedUse entity will be created that will actually
-do the SUB_UseTargets after that many seconds have passed.
-
-Centerprints any self.message to the activator.
-
-Removes all entities with a targetname that match self.killtarget,
-and removes them, so some events can remove other triggers.
-
-Search for (string)targetname in all entities that
-match (string)self.target and call their .use function
-
-==============================
-*/
-void SUB_UseTargets()
-{
-       entity t, stemp, otemp, act;
-       string s;
-       float i;
-
-//
-// check for a delay
-//
-       if (self.delay)
-       {
-       // create a temp object to fire at a later time
-               t = spawn();
-               t.classname = "DelayedUse";
-               t.nextthink = time + self.delay;
-               t.think = DelayThink;
-               t.enemy = activator;
-               t.message = self.message;
-               t.killtarget = self.killtarget;
-               t.target = self.target;
-               t.target2 = self.target2;
-               t.target3 = self.target3;
-               t.target4 = self.target4;
-               return;
-       }
-
-
-//
-// print the message
-//
-       if(self)
-       if(IS_PLAYER(activator) && self.message != "")
-       if(IS_REAL_CLIENT(activator))
-       {
-               centerprint(activator, self.message);
-               if (self.noise == "")
-                       play2(activator, "misc/talk.wav");
-       }
-
-//
-// kill the killtagets
-//
-       s = self.killtarget;
-       if (s != "")
-       {
-               for(t = world; (t = find(t, targetname, s)); )
-                       remove(t);
-       }
-
-//
-// fire targets
-//
-       act = activator;
-       stemp = self;
-       otemp = other;
-
-       if(stemp.target_random)
-               RandomSelection_Init();
-
-       for(i = 0; i < 4; ++i)
-       {
-               switch(i)
-               {
-                       default:
-                       case 0: s = stemp.target; break;
-                       case 1: s = stemp.target2; break;
-                       case 2: s = stemp.target3; break;
-                       case 3: s = stemp.target4; break;
-               }
-               if (s != "")
-               {
-                       for(t = world; (t = find(t, targetname, s)); )
-                       if(t.use)
-                       {
-                               if(stemp.target_random)
-                               {
-                                       RandomSelection_Add(t, 0, string_null, 1, 0);
-                               }
-                               else
-                               {
-                                       self = t;
-                                       other = stemp;
-                                       activator = act;
-                                       self.use();
-                               }
-                       }
-               }
-       }
-
-       if(stemp.target_random && RandomSelection_chosen_ent)
-       {
-               self = RandomSelection_chosen_ent;
-               other = stemp;
-               activator = act;
-               self.use();
-       }
-
-       activator = act;
-       self = stemp;
-       other = otemp;
-}
-
-
-//=============================================================================
-
-// the wait time has passed, so set back up for another activation
-void multi_wait()
-{
-       if (self.max_health)
-       {
-               self.health = self.max_health;
-               self.takedamage = DAMAGE_YES;
-               self.solid = SOLID_BBOX;
-       }
-}
-
-
-// the trigger was just touched/killed/used
-// self.enemy should be set to the activator so it can be held through a delay
-// so wait for the delay time before firing
-void multi_trigger()
-{
-       if (self.nextthink > time)
-       {
-               return;         // allready been triggered
-       }
-
-       if (self.classname == "trigger_secret")
-       {
-               if (!IS_PLAYER(self.enemy))
-                       return;
-               found_secrets = found_secrets + 1;
-               WriteByte (MSG_ALL, SVC_FOUNDSECRET);
-       }
-
-       if (self.noise)
-               sound (self.enemy, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
-
-// don't trigger again until reset
-       self.takedamage = DAMAGE_NO;
-
-       activator = self.enemy;
-       other = self.goalentity;
-       SUB_UseTargets();
-
-       if (self.wait > 0)
-       {
-               self.think = multi_wait;
-               self.nextthink = time + self.wait;
-       }
-       else if (self.wait == 0)
-       {
-               multi_wait(); // waiting finished
-       }
-       else
-       {       // we can't just remove (self) here, because this is a touch function
-               // called wheil C code is looping through area links...
-               self.touch = func_null;
-       }
-}
-
-void multi_use()
-{
-       self.goalentity = other;
-       self.enemy = activator;
-       multi_trigger();
-}
-
-void multi_touch()
-{
-       if(!(self.spawnflags & 2))
-       if(!other.iscreature)
-                       return;
-
-       if(self.team)
-               if(((self.spawnflags & 4) == 0) == (self.team != other.team))
-                       return;
-
-// if the trigger has an angles field, check player's facing direction
-       if (self.movedir != '0 0 0')
-       {
-               makevectors (other.angles);
-               if (v_forward * self.movedir < 0)
-                       return;         // not facing the right way
-       }
-
-       EXACTTRIGGER_TOUCH;
-
-       self.enemy = other;
-       self.goalentity = other;
-       multi_trigger ();
-}
-
-void multi_eventdamage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
-{
-       if (!self.takedamage)
-               return;
-       if(self.spawnflags & DOOR_NOSPLASH)
-               if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
-                       return;
-       self.health = self.health - damage;
-       if (self.health <= 0)
-       {
-               self.enemy = attacker;
-               self.goalentity = inflictor;
-               multi_trigger();
-       }
-}
-
-void multi_reset()
-{
-       if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
-               self.touch = multi_touch;
-       if (self.max_health)
-       {
-               self.health = self.max_health;
-               self.takedamage = DAMAGE_YES;
-               self.solid = SOLID_BBOX;
-       }
-       self.think = func_null;
-       self.nextthink = 0;
-       self.team = self.team_saved;
-}
-
-/*QUAKED spawnfunc_trigger_multiple (.5 .5 .5) ? notouch
-Variable sized repeatable trigger.  Must be targeted at one or more entities.  If "health" is set, the trigger must be killed to activate each time.
-If "delay" is set, the trigger waits some time after activating before firing.
-"wait" : Seconds between triggerings. (.2 default)
-If notouch is set, the trigger is only fired by other entities, not by touching.
-NOTOUCH has been obsoleted by spawnfunc_trigger_relay!
-sounds
-1)     secret
-2)     beep beep
-3)     large switch
-4)
-set "message" to text string
-*/
-void spawnfunc_trigger_multiple()
-{
-       self.reset = multi_reset;
-       if (self.sounds == 1)
-       {
-               precache_sound ("misc/secret.wav");
-               self.noise = "misc/secret.wav";
-       }
-       else if (self.sounds == 2)
-       {
-               precache_sound ("misc/talk.wav");
-               self.noise = "misc/talk.wav";
-       }
-       else if (self.sounds == 3)
-       {
-               precache_sound ("misc/trigger1.wav");
-               self.noise = "misc/trigger1.wav";
-       }
-
-       if (!self.wait)
-               self.wait = 0.2;
-       else if(self.wait < -1)
-               self.wait = 0;
-       self.use = multi_use;
-
-       EXACTTRIGGER_INIT;
-
-       self.team_saved = self.team;
-
-       if (self.health)
-       {
-               if (self.spawnflags & SPAWNFLAG_NOTOUCH)
-                       objerror ("health and notouch don't make sense\n");
-               self.max_health = self.health;
-               self.event_damage = multi_eventdamage;
-               self.takedamage = DAMAGE_YES;
-               self.solid = SOLID_BBOX;
-               setorigin (self, self.origin);  // make sure it links into the world
-       }
-       else
-       {
-               if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
-               {
-                       self.touch = multi_touch;
-                       setorigin (self, self.origin);  // make sure it links into the world
-               }
-       }
-}
-
-
-/*QUAKED spawnfunc_trigger_once (.5 .5 .5) ? notouch
-Variable sized trigger. Triggers once, then removes itself.  You must set the key "target" to the name of another object in the level that has a matching
-"targetname".  If "health" is set, the trigger must be killed to activate.
-If notouch is set, the trigger is only fired by other entities, not by touching.
-if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired.
-if "angle" is set, the trigger will only fire when someone is facing the direction of the angle.  Use "360" for an angle of 0.
-sounds
-1)     secret
-2)     beep beep
-3)     large switch
-4)
-set "message" to text string
-*/
-void spawnfunc_trigger_once()
-{
-       self.wait = -1;
-       spawnfunc_trigger_multiple();
-}
-
-//=============================================================================
-
-/*QUAKED spawnfunc_trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
-This fixed size trigger cannot be touched, it can only be fired by other events.  It can contain killtargets, targets, delays, and messages.
-*/
-void spawnfunc_trigger_relay()
-{
-       self.use = SUB_UseTargets;
-       self.reset = spawnfunc_trigger_relay; // this spawnfunc resets fully
-}
-
-void delay_use()
-{
-    self.think = SUB_UseTargets;
-    self.nextthink = self.wait;
-}
-
-void delay_reset()
-{
-       self.think = func_null;
-       self.nextthink = 0;
-}
-
-void spawnfunc_trigger_delay()
-{
-    if(!self.wait)
-        self.wait = 1;
-
-    self.use = delay_use;
-    self.reset = delay_reset;
-}
-
-//=============================================================================
-
-
-void counter_use()
-{
-       self.count -= 1;
-       if (self.count < 0)
-               return;
-
-       if (self.count == 0)
-       {
-               if(IS_PLAYER(activator) && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
-                       Send_Notification(NOTIF_ONE, activator, MSG_CENTER, CENTER_SEQUENCE_COMPLETED);
-
-               self.enemy = activator;
-               multi_trigger ();
-       }
-       else
-       {
-               if(IS_PLAYER(activator) && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
-               if(self.count >= 4)
-                       Send_Notification(NOTIF_ONE, activator, MSG_CENTER, CENTER_SEQUENCE_COUNTER);
-               else
-                       Send_Notification(NOTIF_ONE, activator, MSG_CENTER, CENTER_SEQUENCE_COUNTER_FEWMORE, self.count);
-       }
-}
-
-void counter_reset()
-{
-       self.count = self.cnt;
-       multi_reset();
-}
-
-/*QUAKED spawnfunc_trigger_counter (.5 .5 .5) ? nomessage
-Acts as an intermediary for an action that takes multiple inputs.
-
-If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
-
-After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
-*/
-void spawnfunc_trigger_counter()
-{
-       self.wait = -1;
-       if (!self.count)
-               self.count = 2;
-       self.cnt = self.count;
-
-       self.use = counter_use;
-       self.reset = counter_reset;
-}
-
-void trigger_hurt_use()
-{
-       if(IS_PLAYER(activator))
-               self.enemy = activator;
-       else
-               self.enemy = world; // let's just destroy it, if taking over is too much work
-}
-
-void trigger_hurt_touch()
-{
-       if (self.active != ACTIVE_ACTIVE)
-               return;
-
-       if(self.team)
-               if(((self.spawnflags & 4) == 0) == (self.team != other.team))
-                       return;
-
-       // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
-       if (other.iscreature)
-       {
-               if (other.takedamage)
-               if (other.triggerhurttime < time)
-               {
-                       EXACTTRIGGER_TOUCH;
-                       other.triggerhurttime = time + 1;
-
-                       entity own;
-                       own = self.enemy;
-                       if (!IS_PLAYER(own))
-                       {
-                               own = self;
-                               self.enemy = world; // I still hate you all
-                       }
-
-                       Damage (other, self, own, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
-               }
-       }
-       else if(other.damagedbytriggers)
-       {
-               if(other.takedamage)
-               {
-                       EXACTTRIGGER_TOUCH;
-                       Damage(other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
-               }
-       }
-
-       return;
-}
-
-/*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ?
-Any object touching this will be hurt
-set dmg to damage amount
-defalt dmg = 5
-*/
-void spawnfunc_trigger_hurt()
-{
-       EXACTTRIGGER_INIT;
-       self.active = ACTIVE_ACTIVE;
-       self.touch = trigger_hurt_touch;
-       self.use = trigger_hurt_use;
-       self.enemy = world; // I hate you all
-       if (!self.dmg)
-               self.dmg = 1000;
-       if (self.message == "")
-               self.message = "was in the wrong place";
-       if (self.message2 == "")
-               self.message2 = "was thrown into a world of hurt by";
-       // self.message = "someone like %s always gets wrongplaced";
-
-       if(!trigger_hurt_first)
-               trigger_hurt_first = self;
-       if(trigger_hurt_last)
-               trigger_hurt_last.trigger_hurt_next = self;
-       trigger_hurt_last = self;
-}
-
-float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end)
-{
-       entity th;
-
-       for(th = trigger_hurt_first; th; th = th.trigger_hurt_next)
-               if(tracebox_hits_box(start, mi, ma, end, th.absmin, th.absmax))
-                       return true;
-
-       return false;
-}
-
-//////////////////////////////////////////////////////////////
-//
-//
-//
-//Trigger heal --a04191b92fbd93aa67214ef7e72d6d2e
-//
-//////////////////////////////////////////////////////////////
-
-void trigger_heal_touch()
-{
-       if (self.active != ACTIVE_ACTIVE)
-               return;
-
-       // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
-       if (other.iscreature)
-       {
-               if (other.takedamage)
-               if (!other.deadflag)
-               if (other.triggerhealtime < time)
-               {
-                       EXACTTRIGGER_TOUCH;
-                       other.triggerhealtime = time + 1;
-
-                       if (other.health < self.max_health)
-                       {
-                               other.health = min(other.health + self.health, self.max_health);
-                               other.pauserothealth_finished = max(other.pauserothealth_finished, time + autocvar_g_balance_pause_health_rot);
-                               sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
-                       }
-               }
-       }
-}
-
-void spawnfunc_trigger_heal()
-{
-       self.active = ACTIVE_ACTIVE;
-
-       EXACTTRIGGER_INIT;
-       self.touch = trigger_heal_touch;
-       if (!self.health)
-               self.health = 10;
-       if (!self.max_health)
-               self.max_health = 200; //Max health topoff for field
-       if(self.noise == "")
-               self.noise = "misc/mediumhealth.wav";
-       precache_sound(self.noise);
-}
-
-
-//////////////////////////////////////////////////////////////
-//
-//
-//
-//End trigger_heal
-//
-//////////////////////////////////////////////////////////////
-
-void trigger_gravity_remove(entity own)
-{
-       if(own.trigger_gravity_check.owner == own)
-       {
-               UpdateCSQCProjectile(own);
-               own.gravity = own.trigger_gravity_check.gravity;
-               remove(own.trigger_gravity_check);
-       }
-       else
-               backtrace("Removing a trigger_gravity_check with no valid owner");
-       own.trigger_gravity_check = world;
-}
-void trigger_gravity_check_think()
-{
-       // This spawns when a player enters the gravity zone and checks if he left.
-       // Each frame, self.count is set to 2 by trigger_gravity_touch() and decreased by 1 here.
-       // It the player has left the gravity trigger, this will be allowed to reach 0 and indicate that.
-       if(self.count <= 0)
-       {
-               if(self.owner.trigger_gravity_check == self)
-                       trigger_gravity_remove(self.owner);
-               else
-                       remove(self);
-               return;
-       }
-       else
-       {
-               self.count -= 1;
-               self.nextthink = time;
-       }
-}
-
-void trigger_gravity_use()
-{
-       self.state = !self.state;
-}
-
-void trigger_gravity_touch()
-{
-       float g;
-
-       if(self.state != true)
-               return;
-
-       EXACTTRIGGER_TOUCH;
-
-       g = self.gravity;
-
-       if (!(self.spawnflags & 1))
-       {
-               if(other.trigger_gravity_check)
-               {
-                       if(self == other.trigger_gravity_check.enemy)
-                       {
-                               // same?
-                               other.trigger_gravity_check.count = 2; // gravity one more frame...
-                               return;
-                       }
-
-                       // compare prio
-                       if(self.cnt > other.trigger_gravity_check.enemy.cnt)
-                               trigger_gravity_remove(other);
-                       else
-                               return;
-               }
-               other.trigger_gravity_check = spawn();
-               other.trigger_gravity_check.enemy = self;
-               other.trigger_gravity_check.owner = other;
-               other.trigger_gravity_check.gravity = other.gravity;
-               other.trigger_gravity_check.think = trigger_gravity_check_think;
-               other.trigger_gravity_check.nextthink = time;
-               other.trigger_gravity_check.count = 2;
-               if(other.gravity)
-                       g *= other.gravity;
-       }
-
-       if (other.gravity != g)
-       {
-               other.gravity = g;
-               if(self.noise != "")
-                       sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
-               UpdateCSQCProjectile(self.owner);
-       }
-}
-
-void spawnfunc_trigger_gravity()
-{
-       if(self.gravity == 1)
-               return;
-
-       EXACTTRIGGER_INIT;
-       self.touch = trigger_gravity_touch;
-       if(self.noise != "")
-               precache_sound(self.noise);
-
-       self.state = true;
-       IFTARGETED
-       {
-               self.use = trigger_gravity_use;
-               if(self.spawnflags & 2)
-                       self.state = false;
-       }
-}
-
-//=============================================================================
-
-// TODO add a way to do looped sounds with sound(); then complete this entity
-void target_speaker_use_activator()
-{
-       if (!IS_REAL_CLIENT(activator))
-               return;
-       string snd;
-       if(substring(self.noise, 0, 1) == "*")
-       {
-               var .string sample;
-               sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
-               if(GetPlayerSoundSampleField_notFound)
-                       snd = "misc/null.wav";
-               else if(activator.sample == "")
-                       snd = "misc/null.wav";
-               else
-               {
-                       tokenize_console(activator.sample);
-                       float n;
-                       n = stof(argv(1));
-                       if(n > 0)
-                               snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
-                       else
-                               snd = strcat(argv(0), ".wav"); // randomization
-               }
-       }
-       else
-               snd = self.noise;
-       msg_entity = activator;
-       soundto(MSG_ONE, self, CH_TRIGGER, snd, VOL_BASE * self.volume, self.atten);
-}
-void target_speaker_use_on()
-{
-       string snd;
-       if(substring(self.noise, 0, 1) == "*")
-       {
-               var .string sample;
-               sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
-               if(GetPlayerSoundSampleField_notFound)
-                       snd = "misc/null.wav";
-               else if(activator.sample == "")
-                       snd = "misc/null.wav";
-               else
-               {
-                       tokenize_console(activator.sample);
-                       float n;
-                       n = stof(argv(1));
-                       if(n > 0)
-                               snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
-                       else
-                               snd = strcat(argv(0), ".wav"); // randomization
-               }
-       }
-       else
-               snd = self.noise;
-       sound(self, CH_TRIGGER_SINGLE, snd, VOL_BASE * self.volume, self.atten);
-       if(self.spawnflags & 3)
-               self.use = target_speaker_use_off;
-}
-void target_speaker_use_off()
-{
-       sound(self, CH_TRIGGER_SINGLE, "misc/null.wav", VOL_BASE * self.volume, self.atten);
-       self.use = target_speaker_use_on;
-}
-void target_speaker_reset()
-{
-       if(self.spawnflags & 1) // LOOPED_ON
-       {
-               if(self.use == target_speaker_use_on)
-                       target_speaker_use_on();
-       }
-       else if(self.spawnflags & 2)
-       {
-               if(self.use == target_speaker_use_off)
-                       target_speaker_use_off();
-       }
-}
-
-void spawnfunc_target_speaker()
-{
-       // TODO: "*" prefix to sound file name
-       // TODO: wait and random (just, HOW? random is not a field)
-       if(self.noise)
-               precache_sound (self.noise);
-
-       if(!self.atten && !(self.spawnflags & 4))
-       {
-               IFTARGETED
-                       self.atten = ATTEN_NORM;
-               else
-                       self.atten = ATTEN_STATIC;
-       }
-       else if(self.atten < 0)
-               self.atten = 0;
-
-       if(!self.volume)
-               self.volume = 1;
-
-       IFTARGETED
-       {
-               if(self.spawnflags & 8) // ACTIVATOR
-                       self.use = target_speaker_use_activator;
-               else if(self.spawnflags & 1) // LOOPED_ON
-               {
-                       target_speaker_use_on();
-                       self.reset = target_speaker_reset;
-               }
-               else if(self.spawnflags & 2) // LOOPED_OFF
-               {
-                       self.use = target_speaker_use_on;
-                       self.reset = target_speaker_reset;
-               }
-               else
-                       self.use = target_speaker_use_on;
-       }
-       else if(self.spawnflags & 1) // LOOPED_ON
-       {
-               ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
-               remove(self);
-       }
-       else if(self.spawnflags & 2) // LOOPED_OFF
-       {
-               objerror("This sound entity can never be activated");
-       }
-       else
-       {
-               // Quake/Nexuiz fallback
-               ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
-               remove(self);
-       }
-}
-
-
-void spawnfunc_func_stardust() {
-       self.effects = EF_STARDUST;
-}
-
-float pointparticles_SendEntity(entity to, float fl)
-{
-       WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
-
-       // optional features to save space
-       fl = fl & 0x0F;
-       if(self.spawnflags & 2)
-               fl |= 0x10; // absolute count on toggle-on
-       if(self.movedir != '0 0 0' || self.velocity != '0 0 0')
-               fl |= 0x20; // 4 bytes - saves CPU
-       if(self.waterlevel || self.count != 1)
-               fl |= 0x40; // 4 bytes - obscure features almost never used
-       if(self.mins != '0 0 0' || self.maxs != '0 0 0')
-               fl |= 0x80; // 14 bytes - saves lots of space
-
-       WriteByte(MSG_ENTITY, fl);
-       if(fl & 2)
-       {
-               if(self.state)
-                       WriteCoord(MSG_ENTITY, self.impulse);
-               else
-                       WriteCoord(MSG_ENTITY, 0); // off
-       }
-       if(fl & 4)
-       {
-               WriteCoord(MSG_ENTITY, self.origin.x);
-               WriteCoord(MSG_ENTITY, self.origin.y);
-               WriteCoord(MSG_ENTITY, self.origin.z);
-       }
-       if(fl & 1)
-       {
-               if(self.model != "null")
-               {
-                       WriteShort(MSG_ENTITY, self.modelindex);
-                       if(fl & 0x80)
-                       {
-                               WriteCoord(MSG_ENTITY, self.mins.x);
-                               WriteCoord(MSG_ENTITY, self.mins.y);
-                               WriteCoord(MSG_ENTITY, self.mins.z);
-                               WriteCoord(MSG_ENTITY, self.maxs.x);
-                               WriteCoord(MSG_ENTITY, self.maxs.y);
-                               WriteCoord(MSG_ENTITY, self.maxs.z);
-                       }
-               }
-               else
-               {
-                       WriteShort(MSG_ENTITY, 0);
-                       if(fl & 0x80)
-                       {
-                               WriteCoord(MSG_ENTITY, self.maxs.x);
-                               WriteCoord(MSG_ENTITY, self.maxs.y);
-                               WriteCoord(MSG_ENTITY, self.maxs.z);
-                       }
-               }
-               WriteShort(MSG_ENTITY, self.cnt);
-               if(fl & 0x20)
-               {
-                       WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
-                       WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
-               }
-               if(fl & 0x40)
-               {
-                       WriteShort(MSG_ENTITY, self.waterlevel * 16.0);
-                       WriteByte(MSG_ENTITY, self.count * 16.0);
-               }
-               WriteString(MSG_ENTITY, self.noise);
-               if(self.noise != "")
-               {
-                       WriteByte(MSG_ENTITY, floor(self.atten * 64));
-                       WriteByte(MSG_ENTITY, floor(self.volume * 255));
-               }
-               WriteString(MSG_ENTITY, self.bgmscript);
-               if(self.bgmscript != "")
-               {
-                       WriteByte(MSG_ENTITY, floor(self.bgmscriptattack * 64));
-                       WriteByte(MSG_ENTITY, floor(self.bgmscriptdecay * 64));
-                       WriteByte(MSG_ENTITY, floor(self.bgmscriptsustain * 255));
-                       WriteByte(MSG_ENTITY, floor(self.bgmscriptrelease * 64));
-               }
-       }
-       return 1;
-}
-
-void pointparticles_use()
-{
-       self.state = !self.state;
-       self.SendFlags |= 2;
-}
-
-void pointparticles_think()
-{
-       if(self.origin != self.oldorigin)
-       {
-               self.SendFlags |= 4;
-               self.oldorigin = self.origin;
-       }
-       self.nextthink = time;
-}
-
-void pointparticles_reset()
-{
-       if(self.spawnflags & 1)
-               self.state = 1;
-       else
-               self.state = 0;
-}
-
-void spawnfunc_func_pointparticles()
-{
-       if(self.model != "")
-               setmodel(self, self.model);
-       if(self.noise != "")
-               precache_sound (self.noise);
-
-       if(!self.bgmscriptsustain)
-               self.bgmscriptsustain = 1;
-       else if(self.bgmscriptsustain < 0)
-               self.bgmscriptsustain = 0;
-
-       if(!self.atten)
-               self.atten = ATTEN_NORM;
-       else if(self.atten < 0)
-               self.atten = 0;
-       if(!self.volume)
-               self.volume = 1;
-       if(!self.count)
-               self.count = 1;
-       if(!self.impulse)
-               self.impulse = 1;
-
-       if(!self.modelindex)
-       {
-               setorigin(self, self.origin + self.mins);
-               setsize(self, '0 0 0', self.maxs - self.mins);
-       }
-       if(!self.cnt)
-               self.cnt = particleeffectnum(self.mdl);
-
-       Net_LinkEntity(self, (self.spawnflags & 4), 0, pointparticles_SendEntity);
-
-       IFTARGETED
-       {
-               self.use = pointparticles_use;
-               self.reset = pointparticles_reset;
-               self.reset();
-       }
-       else
-               self.state = 1;
-       self.think = pointparticles_think;
-       self.nextthink = time;
-}
-
-void spawnfunc_func_sparks()
-{
-       // self.cnt is the amount of sparks that one burst will spawn
-       if(self.cnt < 1) {
-               self.cnt = 25.0; // nice default value
-       }
-
-       // self.wait is the probability that a sparkthink will spawn a spark shower
-       // range: 0 - 1, but 0 makes little sense, so...
-       if(self.wait < 0.05) {
-               self.wait = 0.25; // nice default value
-       }
-
-       self.count = self.cnt;
-       self.mins = '0 0 0';
-       self.maxs = '0 0 0';
-       self.velocity = '0 0 -1';
-       self.mdl = "TE_SPARK";
-       self.impulse = 10 * self.wait; // by default 2.5/sec
-       self.wait = 0;
-       self.cnt = 0; // use mdl
-
-       spawnfunc_func_pointparticles();
-}
-
-float rainsnow_SendEntity(entity to, float sf)
-{
-       WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
-       WriteByte(MSG_ENTITY, self.state);
-       WriteCoord(MSG_ENTITY, self.origin.x + self.mins.x);
-       WriteCoord(MSG_ENTITY, self.origin.y + self.mins.y);
-       WriteCoord(MSG_ENTITY, self.origin.z + self.mins.z);
-       WriteCoord(MSG_ENTITY, self.maxs.x - self.mins.x);
-       WriteCoord(MSG_ENTITY, self.maxs.y - self.mins.y);
-       WriteCoord(MSG_ENTITY, self.maxs.z - self.mins.z);
-       WriteShort(MSG_ENTITY, compressShortVector(self.dest));
-       WriteShort(MSG_ENTITY, self.count);
-       WriteByte(MSG_ENTITY, self.cnt);
-       return 1;
-}
-
-/*QUAKED spawnfunc_func_rain (0 .5 .8) ?
-This is an invisible area like a trigger, which rain falls inside of.
-
-Keys:
-"velocity"
- falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
-"cnt"
- sets color of rain (default 12 - white)
-"count"
- adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
-*/
-void spawnfunc_func_rain()
-{
-       self.dest = self.velocity;
-       self.velocity = '0 0 0';
-       if (!self.dest)
-               self.dest = '0 0 -700';
-       self.angles = '0 0 0';
-       self.movetype = MOVETYPE_NONE;
-       self.solid = SOLID_NOT;
-       SetBrushEntityModel();
-       if (!self.cnt)
-               self.cnt = 12;
-       if (!self.count)
-               self.count = 2000;
-       self.count = 0.01 * self.count * (self.size.x / 1024) * (self.size.y / 1024);
-       if (self.count < 1)
-               self.count = 1;
-       if(self.count > 65535)
-               self.count = 65535;
-
-       self.state = 1; // 1 is rain, 0 is snow
-       self.Version = 1;
-
-       Net_LinkEntity(self, false, 0, rainsnow_SendEntity);
-}
-
-
-/*QUAKED spawnfunc_func_snow (0 .5 .8) ?
-This is an invisible area like a trigger, which snow falls inside of.
-
-Keys:
-"velocity"
- falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
-"cnt"
- sets color of rain (default 12 - white)
-"count"
- adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
-*/
-void spawnfunc_func_snow()
-{
-       self.dest = self.velocity;
-       self.velocity = '0 0 0';
-       if (!self.dest)
-               self.dest = '0 0 -300';
-       self.angles = '0 0 0';
-       self.movetype = MOVETYPE_NONE;
-       self.solid = SOLID_NOT;
-       SetBrushEntityModel();
-       if (!self.cnt)
-               self.cnt = 12;
-       if (!self.count)
-               self.count = 2000;
-       self.count = 0.01 * self.count * (self.size.x / 1024) * (self.size.y / 1024);
-       if (self.count < 1)
-               self.count = 1;
-       if(self.count > 65535)
-               self.count = 65535;
-
-       self.state = 0; // 1 is rain, 0 is snow
-       self.Version = 1;
-
-       Net_LinkEntity(self, false, 0, rainsnow_SendEntity);
-}
-
-void misc_laser_aim()
-{
-       vector a;
-       if(self.enemy)
-       {
-               if(self.spawnflags & 2)
-               {
-                       if(self.enemy.origin != self.mangle)
-                       {
-                               self.mangle = self.enemy.origin;
-                               self.SendFlags |= 2;
-                       }
-               }
-               else
-               {
-                       a = vectoangles(self.enemy.origin - self.origin);
-                       a.x = -a.x;
-                       if(a != self.mangle)
-                       {
-                               self.mangle = a;
-                               self.SendFlags |= 2;
-                       }
-               }
-       }
-       else
-       {
-               if(self.angles != self.mangle)
-               {
-                       self.mangle = self.angles;
-                       self.SendFlags |= 2;
-               }
-       }
-       if(self.origin != self.oldorigin)
-       {
-               self.SendFlags |= 1;
-               self.oldorigin = self.origin;
-       }
-}
-
-void misc_laser_init()
-{
-       if(self.target != "")
-               self.enemy = find(world, targetname, self.target);
-}
-
-void misc_laser_think()
-{
-       vector o;
-       entity oldself;
-       entity hitent;
-       vector hitloc;
-
-       self.nextthink = time;
-
-       if(!self.state)
-               return;
-
-       misc_laser_aim();
-
-       if(self.enemy)
-       {
-               o = self.enemy.origin;
-               if (!(self.spawnflags & 2))
-                       o = self.origin + normalize(o - self.origin) * 32768;
-       }
-       else
-       {
-               makevectors(self.mangle);
-               o = self.origin + v_forward * 32768;
-       }
-
-       if(self.dmg || self.enemy.target != "")
-       {
-               traceline(self.origin, o, MOVE_NORMAL, self);
-       }
-       hitent = trace_ent;
-       hitloc = trace_endpos;
-
-       if(self.enemy.target != "") // DETECTOR laser
-       {
-               if(trace_ent.iscreature)
-               {
-                       self.pusher = hitent;
-                       if(!self.count)
-                       {
-                               self.count = 1;
-
-                               oldself = self;
-                               self = self.enemy;
-                               activator = self.pusher;
-                               SUB_UseTargets();
-                               self = oldself;
-                       }
-               }
-               else
-               {
-                       if(self.count)
-                       {
-                               self.count = 0;
-
-                               oldself = self;
-                               self = self.enemy;
-                               activator = self.pusher;
-                               SUB_UseTargets();
-                               self = oldself;
-                       }
-               }
-       }
-
-       if(self.dmg)
-       {
-               if(self.team)
-                       if(((self.spawnflags & 8) == 0) == (self.team != hitent.team))
-                               return;
-               if(hitent.takedamage)
-                       Damage(hitent, self, self, ((self.dmg < 0) ? 100000 : (self.dmg * frametime)), DEATH_HURTTRIGGER, hitloc, '0 0 0');
-       }
-}
-
-float laser_SendEntity(entity to, float fl)
-{
-       WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
-       fl = fl - (fl & 0xF0); // use that bit to indicate finite length laser
-       if(self.spawnflags & 2)
-               fl |= 0x80;
-       if(self.alpha)
-               fl |= 0x40;
-       if(self.scale != 1 || self.modelscale != 1)
-               fl |= 0x20;
-       if(self.spawnflags & 4)
-               fl |= 0x10;
-       WriteByte(MSG_ENTITY, fl);
-       if(fl & 1)
-       {
-               WriteCoord(MSG_ENTITY, self.origin.x);
-               WriteCoord(MSG_ENTITY, self.origin.y);
-               WriteCoord(MSG_ENTITY, self.origin.z);
-       }
-       if(fl & 8)
-       {
-               WriteByte(MSG_ENTITY, self.colormod.x * 255.0);
-               WriteByte(MSG_ENTITY, self.colormod.y * 255.0);
-               WriteByte(MSG_ENTITY, self.colormod.z * 255.0);
-               if(fl & 0x40)
-                       WriteByte(MSG_ENTITY, self.alpha * 255.0);
-               if(fl & 0x20)
-               {
-                       WriteByte(MSG_ENTITY, bound(0, self.scale * 16.0, 255));
-                       WriteByte(MSG_ENTITY, bound(0, self.modelscale * 16.0, 255));
-               }
-               if((fl & 0x80) || !(fl & 0x10)) // effect doesn't need sending if the laser is infinite and has collision testing turned off
-                       WriteShort(MSG_ENTITY, self.cnt + 1);
-       }
-       if(fl & 2)
-       {
-               if(fl & 0x80)
-               {
-                       WriteCoord(MSG_ENTITY, self.enemy.origin.x);
-                       WriteCoord(MSG_ENTITY, self.enemy.origin.y);
-                       WriteCoord(MSG_ENTITY, self.enemy.origin.z);
-               }
-               else
-               {
-                       WriteAngle(MSG_ENTITY, self.mangle.x);
-                       WriteAngle(MSG_ENTITY, self.mangle.y);
-               }
-       }
-       if(fl & 4)
-               WriteByte(MSG_ENTITY, self.state);
-       return 1;
-}
-
-/*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
-Any object touching the beam will be hurt
-Keys:
-"target"
- spawnfunc_target_position where the laser ends
-"mdl"
- name of beam end effect to use
-"colormod"
- color of the beam (default: red)
-"dmg"
- damage per second (-1 for a laser that kills immediately)
-*/
-void laser_use()
-{
-       self.state = !self.state;
-       self.SendFlags |= 4;
-       misc_laser_aim();
-}
-
-void laser_reset()
-{
-       if(self.spawnflags & 1)
-               self.state = 1;
-       else
-               self.state = 0;
-}
-
-void spawnfunc_misc_laser()
-{
-       if(self.mdl)
-       {
-               if(self.mdl == "none")
-                       self.cnt = -1;
-               else
-               {
-                       self.cnt = particleeffectnum(self.mdl);
-                       if(self.cnt < 0)
-                               if(self.dmg)
-                                       self.cnt = particleeffectnum("laser_deadly");
-               }
-       }
-       else if(!self.cnt)
-       {
-               if(self.dmg)
-                       self.cnt = particleeffectnum("laser_deadly");
-               else
-                       self.cnt = -1;
-       }
-       if(self.cnt < 0)
-               self.cnt = -1;
-
-       if(self.colormod == '0 0 0')
-               if(!self.alpha)
-                       self.colormod = '1 0 0';
-       if(self.message == "")
-               self.message = "saw the light";
-       if (self.message2 == "")
-               self.message2 = "was pushed into a laser by";
-       if(!self.scale)
-               self.scale = 1;
-       if(!self.modelscale)
-               self.modelscale = 1;
-       else if(self.modelscale < 0)
-               self.modelscale = 0;
-       self.think = misc_laser_think;
-       self.nextthink = time;
-       InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
-
-       self.mangle = self.angles;
-
-       Net_LinkEntity(self, false, 0, laser_SendEntity);
-
-       IFTARGETED
-       {
-               self.reset = laser_reset;
-               laser_reset();
-               self.use = laser_use;
-       }
-       else
-               self.state = 1;
-}
-
-// tZorks trigger impulse / gravity
-
-// targeted (directional) mode
-void trigger_impulse_touch1()
-{
-       entity targ;
-    float pushdeltatime;
-    float str;
-
-       if (self.active != ACTIVE_ACTIVE)
-               return;
-
-       if (!isPushable(other))
-               return;
-
-       EXACTTRIGGER_TOUCH;
-
-    targ = find(world, targetname, self.target);
-    if(!targ)
-    {
-        objerror("trigger_force without a (valid) .target!\n");
-        remove(self);
-        return;
-    }
-
-    str = min(self.radius, vlen(self.origin - other.origin));
-
-    if(self.falloff == 1)
-        str = (str / self.radius) * self.strength;
-    else if(self.falloff == 2)
-        str = (1 - (str / self.radius)) * self.strength;
-    else
-        str = self.strength;
-
-    pushdeltatime = time - other.lastpushtime;
-    if (pushdeltatime > 0.15) pushdeltatime = 0;
-    other.lastpushtime = time;
-    if(!pushdeltatime) return;
-
-    other.velocity = other.velocity + normalize(targ.origin - self.origin) * str * pushdeltatime;
-    other.flags &= ~FL_ONGROUND;
-    UpdateCSQCProjectile(other);
-}
-
-// Directionless (accelerator/decelerator) mode
-void trigger_impulse_touch2()
-{
-    float pushdeltatime;
-
-       if (self.active != ACTIVE_ACTIVE)
-               return;
-
-       if (!isPushable(other))
-               return;
-
-       EXACTTRIGGER_TOUCH;
-
-    pushdeltatime = time - other.lastpushtime;
-    if (pushdeltatime > 0.15) pushdeltatime = 0;
-    other.lastpushtime = time;
-    if(!pushdeltatime) return;
-
-    // div0: ticrate independent, 1 = identity (not 20)
-    other.velocity = other.velocity * pow(self.strength, pushdeltatime);
-    UpdateCSQCProjectile(other);
-}
-
-// Spherical (gravity/repulsor) mode
-void trigger_impulse_touch3()
-{
-    float pushdeltatime;
-    float str;
-
-       if (self.active != ACTIVE_ACTIVE)
-               return;
-
-       if (!isPushable(other))
-               return;
-
-       EXACTTRIGGER_TOUCH;
-
-    pushdeltatime = time - other.lastpushtime;
-    if (pushdeltatime > 0.15) pushdeltatime = 0;
-    other.lastpushtime = time;
-    if(!pushdeltatime) return;
-
-    setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
-
-       str = min(self.radius, vlen(self.origin - other.origin));
-
-    if(self.falloff == 1)
-        str = (1 - str / self.radius) * self.strength; // 1 in the inside
-    else if(self.falloff == 2)
-        str = (str / self.radius) * self.strength; // 0 in the inside
-    else
-        str = self.strength;
-
-    other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
-    UpdateCSQCProjectile(other);
-}
-
-/*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
--------- KEYS --------
-target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
-         If not, this trigger acts like a damper/accelerator field.
-
-strength : This is how mutch force to add in the direction of .target each second
-           when .target is set. If not, this is hoe mutch to slow down/accelerate
-           someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
-
-radius   : If set, act as a spherical device rather then a liniar one.
-
-falloff : 0 = none, 1 = liniar, 2 = inverted liniar
-
--------- NOTES --------
-Use a brush textured with common/origin in the trigger entity to determine the origin of the force
-in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
-*/
-
-void spawnfunc_trigger_impulse()
-{
-       self.active = ACTIVE_ACTIVE;
-
-       EXACTTRIGGER_INIT;
-    if(self.radius)
-    {
-        if(!self.strength) self.strength = 2000 * autocvar_g_triggerimpulse_radial_multiplier;
-        setorigin(self, self.origin);
-        setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
-        self.touch = trigger_impulse_touch3;
-    }
-    else
-    {
-        if(self.target)
-        {
-            if(!self.strength) self.strength = 950 * autocvar_g_triggerimpulse_directional_multiplier;
-            self.touch = trigger_impulse_touch1;
-        }
-        else
-        {
-            if(!self.strength) self.strength = 0.9;
-                       self.strength = pow(self.strength, autocvar_g_triggerimpulse_accel_power) * autocvar_g_triggerimpulse_accel_multiplier;
-            self.touch = trigger_impulse_touch2;
-        }
-    }
-}
-
-/*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
-"Flip-flop" trigger gate... lets only every second trigger event through
-*/
-void flipflop_use()
-{
-       self.state = !self.state;
-       if(self.state)
-               SUB_UseTargets();
-}
-
-void spawnfunc_trigger_flipflop()
-{
-       if(self.spawnflags & 1)
-               self.state = 1;
-       self.use = flipflop_use;
-       self.reset = spawnfunc_trigger_flipflop; // perfect resetter
-}
-
-/*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
-"Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
-*/
-void monoflop_use()
-{
-       self.nextthink = time + self.wait;
-       self.enemy = activator;
-       if(self.state)
-               return;
-       self.state = 1;
-       SUB_UseTargets();
-}
-void monoflop_fixed_use()
-{
-       if(self.state)
-               return;
-       self.nextthink = time + self.wait;
-       self.state = 1;
-       self.enemy = activator;
-       SUB_UseTargets();
-}
-
-void monoflop_think()
-{
-       self.state = 0;
-       activator = self.enemy;
-       SUB_UseTargets();
-}
-
-void monoflop_reset()
-{
-       self.state = 0;
-       self.nextthink = 0;
-}
-
-void spawnfunc_trigger_monoflop()
-{
-       if(!self.wait)
-               self.wait = 1;
-       if(self.spawnflags & 1)
-               self.use = monoflop_fixed_use;
-       else
-               self.use = monoflop_use;
-       self.think = monoflop_think;
-       self.state = 0;
-       self.reset = monoflop_reset;
-}
-
-void multivibrator_send()
-{
-       float newstate;
-       float cyclestart;
-
-       cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
-
-       newstate = (time < cyclestart + self.wait);
-
-       activator = self;
-       if(self.state != newstate)
-               SUB_UseTargets();
-       self.state = newstate;
-
-       if(self.state)
-               self.nextthink = cyclestart + self.wait + 0.01;
-       else
-               self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
-}
-
-void multivibrator_toggle()
-{
-       if(self.nextthink == 0)
-       {
-               multivibrator_send();
-       }
-       else
-       {
-               if(self.state)
-               {
-                       SUB_UseTargets();
-                       self.state = 0;
-               }
-               self.nextthink = 0;
-       }
-}
-
-void multivibrator_reset()
-{
-       if(!(self.spawnflags & 1))
-               self.nextthink = 0; // wait for a trigger event
-       else
-               self.nextthink = max(1, time);
-}
-
-/*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
-"Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
--------- KEYS --------
-target: trigger all entities with this targetname when it goes off
-targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
-phase: offset of the timing
-wait: "on" cycle time (default: 1)
-respawntime: "off" cycle time (default: same as wait)
--------- SPAWNFLAGS --------
-START_ON: assume it is already turned on (when targeted)
-*/
-void spawnfunc_trigger_multivibrator()
-{
-       if(!self.wait)
-               self.wait = 1;
-       if(!self.respawntime)
-               self.respawntime = self.wait;
-
-       self.state = 0;
-       self.use = multivibrator_toggle;
-       self.think = multivibrator_send;
-       self.nextthink = max(1, time);
-
-       IFTARGETED
-               multivibrator_reset();
-}
-
-
-void follow_init()
-{
-       entity src, dst;
-       src = world;
-       dst = world;
-       if(self.killtarget != "")
-               src = find(world, targetname, self.killtarget);
-       if(self.target != "")
-               dst = find(world, targetname, self.target);
-
-       if(!src && !dst)
-       {
-               objerror("follow: could not find target/killtarget");
-               return;
-       }
-
-       if(self.jointtype)
-       {
-               // already done :P entity must stay
-               self.aiment = src;
-               self.enemy = dst;
-       }
-       else if(!src || !dst)
-       {
-               objerror("follow: could not find target/killtarget");
-               return;
-       }
-       else if(self.spawnflags & 1)
-       {
-               // attach
-               if(self.spawnflags & 2)
-               {
-                       setattachment(dst, src, self.message);
-               }
-               else
-               {
-                       attach_sameorigin(dst, src, self.message);
-               }
-
-               dst.solid = SOLID_NOT; // solid doesn't work with attachment
-               remove(self);
-       }
-       else
-       {
-               if(self.spawnflags & 2)
-               {
-                       dst.movetype = MOVETYPE_FOLLOW;
-                       dst.aiment = src;
-                       // dst.punchangle = '0 0 0'; // keep unchanged
-                       dst.view_ofs = dst.origin;
-                       dst.v_angle = dst.angles;
-               }
-               else
-               {
-                       follow_sameorigin(dst, src);
-               }
-
-               remove(self);
-       }
-}
-
-void spawnfunc_misc_follow()
-{
-       InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
-}
-
-
-
-void gamestart_use() {
-       activator = self;
-       SUB_UseTargets();
-       remove(self);
-}
-
-void spawnfunc_trigger_gamestart() {
-       self.use = gamestart_use;
-       self.reset2 = spawnfunc_trigger_gamestart;
-
-       if(self.wait)
-       {
-               self.think = self.use;
-               self.nextthink = game_starttime + self.wait;
-       }
-       else
-               InitializeEntity(self, gamestart_use, INITPRIO_FINDTARGET);
-}
-
-
-
-
-void target_voicescript_clear(entity pl)
-{
-       pl.voicescript = world;
-}
-
-void target_voicescript_use()
-{
-       if(activator.voicescript != self)
-       {
-               activator.voicescript = self;
-               activator.voicescript_index = 0;
-               activator.voicescript_nextthink = time + self.delay;
-       }
-}
-
-void target_voicescript_next(entity pl)
-{
-       entity vs;
-       float i, n, dt;
-
-       vs = pl.voicescript;
-       if(!vs)
-               return;
-       if(vs.message == "")
-               return;
-       if (!IS_PLAYER(pl))
-               return;
-       if(gameover)
-               return;
-
-       if(time >= pl.voicescript_voiceend)
-       {
-               if(time >= pl.voicescript_nextthink)
-               {
-                       // get the next voice...
-                       n = tokenize_console(vs.message);
-
-                       if(pl.voicescript_index < vs.cnt)
-                               i = pl.voicescript_index * 2;
-                       else if(n > vs.cnt * 2)
-                               i = ((pl.voicescript_index - vs.cnt) % ((n - vs.cnt * 2 - 1) / 2)) * 2 + vs.cnt * 2 + 1;
-                       else
-                               i = -1;
-
-                       if(i >= 0)
-                       {
-                               play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
-                               dt = stof(argv(i + 1));
-                               if(dt >= 0)
-                               {
-                                       pl.voicescript_voiceend = time + dt;
-                                       pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
-                               }
-                               else
-                               {
-                                       pl.voicescript_voiceend = time - dt;
-                                       pl.voicescript_nextthink = pl.voicescript_voiceend;
-                               }
-
-                               pl.voicescript_index += 1;
-                       }
-                       else
-                       {
-                               pl.voicescript = world; // stop trying then
-                       }
-               }
-       }
-}
-
-void spawnfunc_target_voicescript()
-{
-       // netname: directory of the sound files
-       // message: list of "sound file" duration "sound file" duration, a *, and again a list
-       //          foo1 4.1 foo2 4.0 foo3 -3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
-       //          Here, a - in front of the duration means that no delay is to be
-       //          added after this message
-       // wait: average time between messages
-       // delay: initial delay before the first message
-
-       float i, n;
-       self.use = target_voicescript_use;
-
-       n = tokenize_console(self.message);
-       self.cnt = n / 2;
-       for(i = 0; i+1 < n; i += 2)
-       {
-               if(argv(i) == "*")
-               {
-                       self.cnt = i / 2;
-                       ++i;
-               }
-               precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
-       }
-}
-
-
-
-void trigger_relay_teamcheck_use()
-{
-       if(activator.team)
-       {
-               if(self.spawnflags & 2)
-               {
-                       if(activator.team != self.team)
-                               SUB_UseTargets();
-               }
-               else
-               {
-                       if(activator.team == self.team)
-                               SUB_UseTargets();
-               }
-       }
-       else
-       {
-               if(self.spawnflags & 1)
-                       SUB_UseTargets();
-       }
-}
-
-void trigger_relay_teamcheck_reset()
-{
-       self.team = self.team_saved;
-}
-
-void spawnfunc_trigger_relay_teamcheck()
-{
-       self.team_saved = self.team;
-       self.use = trigger_relay_teamcheck_use;
-       self.reset = trigger_relay_teamcheck_reset;
-}
-
-
-
-void trigger_disablerelay_use()
-{
-       entity e;
-
-       float a, b;
-       a = b = 0;
-
-       for(e = world; (e = find(e, targetname, self.target)); )
-       {
-               if(e.use == SUB_UseTargets)
-               {
-                       e.use = SUB_DontUseTargets;
-                       ++a;
-               }
-               else if(e.use == SUB_DontUseTargets)
-               {
-                       e.use = SUB_UseTargets;
-                       ++b;
-               }
-       }
-
-       if((!a) == (!b))
-               print("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!\n");
-}
-
-void spawnfunc_trigger_disablerelay()
-{
-       self.use = trigger_disablerelay_use;
-}
-
-string trigger_magicear_processmessage(entity ear, entity source, float teamsay, entity privatesay, string msgin)
-{
-       float domatch, dotrigger, matchstart, l;
-       string s, msg;
-       entity oldself;
-       string savemessage;
-
-       magicear_matched = false;
-
-       dotrigger = ((IS_PLAYER(source)) && (source.deadflag == DEAD_NO) && ((ear.radius == 0) || (vlen(source.origin - ear.origin) <= ear.radius)));
-       domatch = ((ear.spawnflags & 32) || dotrigger);
-
-       if (!domatch)
-               return msgin;
-
-       if (!msgin)
-       {
-               // we are in TUBA mode!
-               if (!(ear.spawnflags & 256))
-                       return msgin;
-
-               if(!W_Tuba_HasPlayed(source, ear.message, ear.movedir.x, !(ear.spawnflags & 512), ear.movedir.y, ear.movedir.z))
-                       return msgin;
-
-               magicear_matched = true;
-
-               if(dotrigger)
-               {
-                       oldself = self;
-                       activator = source;
-                       self = ear;
-                       savemessage = self.message;
-                       self.message = string_null;
-                       SUB_UseTargets();
-                       self.message = savemessage;
-                       self = oldself;
-               }
-
-               if(ear.netname != "")
-                       return ear.netname;
-
-               return msgin;
-       }
-
-       if(ear.spawnflags & 256) // ENOTUBA
-               return msgin;
-
-       if(privatesay)
-       {
-               if(ear.spawnflags & 4)
-                       return msgin;
-       }
-       else
-       {
-               if(!teamsay)
-                       if(ear.spawnflags & 1)
-                               return msgin;
-               if(teamsay > 0)
-                       if(ear.spawnflags & 2)
-                               return msgin;
-               if(teamsay < 0)
-                       if(ear.spawnflags & 8)
-                               return msgin;
-       }
-
-       matchstart = -1;
-       l = strlen(ear.message);
-
-       if(ear.spawnflags & 128)
-               msg = msgin;
-       else
-               msg = strdecolorize(msgin);
-
-       if(substring(ear.message, 0, 1) == "*")
-       {
-               if(substring(ear.message, -1, 1) == "*")
-               {
-                       // two wildcards
-                       // as we need multi-replacement here...
-                       s = substring(ear.message, 1, -2);
-                       l -= 2;
-                       if(strstrofs(msg, s, 0) >= 0)
-                               matchstart = -2; // we use strreplace on s
-               }
-               else
-               {
-                       // match at start
-                       s = substring(ear.message, 1, -1);
-                       l -= 1;
-                       if(substring(msg, -l, l) == s)
-                               matchstart = strlen(msg) - l;
-               }
-       }
-       else
-       {
-               if(substring(ear.message, -1, 1) == "*")
-               {
-                       // match at end
-                       s = substring(ear.message, 0, -2);
-                       l -= 1;
-                       if(substring(msg, 0, l) == s)
-                               matchstart = 0;
-               }
-               else
-               {
-                       // full match
-                       s = ear.message;
-                       if(msg == ear.message)
-                               matchstart = 0;
-               }
-       }
-
-       if(matchstart == -1) // no match
-               return msgin;
-
-       magicear_matched = true;
-
-       if(dotrigger)
-       {
-               oldself = self;
-               activator = source;
-               self = ear;
-               savemessage = self.message;
-               self.message = string_null;
-               SUB_UseTargets();
-               self.message = savemessage;
-               self = oldself;
-       }
-
-       if(ear.spawnflags & 16)
-       {
-               return ear.netname;
-       }
-       else if(ear.netname != "")
-       {
-               if(matchstart < 0)
-                       return strreplace(s, ear.netname, msg);
-               else
-                       return strcat(
-                               substring(msg, 0, matchstart),
-                               ear.netname,
-                               substring(msg, matchstart + l, -1)
-                       );
-       }
-       else
-               return msgin;
-}
-
-string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin)
-{
-       entity ear;
-       string msgout;
-       for(ear = magicears; ear; ear = ear.enemy)
-       {
-               msgout = trigger_magicear_processmessage(ear, source, teamsay, privatesay, msgin);
-               if(!(ear.spawnflags & 64))
-               if(magicear_matched)
-                       return msgout;
-               msgin = msgout;
-       }
-       return msgin;
-}
-
-void spawnfunc_trigger_magicear()
-{
-       self.enemy = magicears;
-       magicears = self;
-
-       // actually handled in "say" processing
-       // spawnflags:
-       //    1 = ignore say
-       //    2 = ignore teamsay
-       //    4 = ignore tell
-       //    8 = ignore tell to unknown player
-       //   16 = let netname replace the whole message (otherwise, netname is a word replacement if set)
-       //   32 = perform the replacement even if outside the radius or dead
-       //   64 = continue replacing/triggering even if this one matched
-       //  128 = don't decolorize message before matching
-       //  256 = message is a tuba note sequence (pitch.duration pitch.duration ...)
-       //  512 = tuba notes must be exact right pitch, no transposing
-       // message: either
-       //   *pattern*
-       // or
-       //   *pattern
-       // or
-       //   pattern*
-       // or
-       //   pattern
-       // netname:
-       //   if set, replacement for the matched text
-       // radius:
-       //   "hearing distance"
-       // target:
-       //   what to trigger
-       // movedir:
-       //   for spawnflags 256, defines 'instrument+1 mintempo maxtempo' (zero component doesn't matter)
-
-       self.movedir_x -= 1; // map to tuba instrument numbers
-}
-
-void relay_activators_use()
-{
-       entity trg, os;
-
-       os = self;
-
-       for(trg = world; (trg = find(trg, targetname, os.target)); )
-       {
-               self = trg;
-               if (trg.setactive)
-                       trg.setactive(os.cnt);
-               else
-               {
-                       //bprint("Not using setactive\n");
-                       if(os.cnt == ACTIVE_TOGGLE)
-                               if(trg.active == ACTIVE_ACTIVE)
-                                       trg.active = ACTIVE_NOT;
-                               else
-                                       trg.active = ACTIVE_ACTIVE;
-                       else
-                               trg.active = os.cnt;
-               }
-       }
-       self = os;
-}
-
-void spawnfunc_relay_activate()
-{
-       self.cnt = ACTIVE_ACTIVE;
-       self.use = relay_activators_use;
-}
-
-void spawnfunc_relay_deactivate()
-{
-       self.cnt = ACTIVE_NOT;
-       self.use = relay_activators_use;
-}
-
-void spawnfunc_relay_activatetoggle()
-{
-       self.cnt = ACTIVE_TOGGLE;
-       self.use = relay_activators_use;
-}
-
-void spawnfunc_target_changelevel_use()
-{
-       if(self.gametype != "")
-               MapInfo_SwitchGameType(MapInfo_Type_FromString(self.gametype));
-
-       if (self.chmap == "")
-               localcmd("endmatch\n");
-       else
-               localcmd(strcat("changelevel ", self.chmap, "\n"));
-}
-
-void spawnfunc_target_changelevel()
-{
-       self.use = spawnfunc_target_changelevel_use;
-}
diff --git a/qcsrc/server/g_triggers.qh b/qcsrc/server/g_triggers.qh
deleted file mode 100644 (file)
index 35c39f2..0000000
+++ /dev/null
@@ -1,388 +0,0 @@
-#ifndef G_TRIGGERS_H
-#define G_TRIGGERS_H
-
-void SUB_DontUseTargets();
-
-
-void() SUB_UseTargets;
-
-void DelayThink();
-
-/*
-==============================
-SUB_UseTargets
-
-the global "activator" should be set to the entity that initiated the firing.
-
-If self.delay is set, a DelayedUse entity will be created that will actually
-do the SUB_UseTargets after that many seconds have passed.
-
-Centerprints any self.message to the activator.
-
-Removes all entities with a targetname that match self.killtarget,
-and removes them, so some events can remove other triggers.
-
-Search for (string)targetname in all entities that
-match (string)self.target and call their .use function
-
-==============================
-*/
-void SUB_UseTargets();
-
-
-//=============================================================================
-
-const float    SPAWNFLAG_NOMESSAGE = 1;
-const float    SPAWNFLAG_NOTOUCH = 1;
-
-// the wait time has passed, so set back up for another activation
-void multi_wait();
-
-
-// the trigger was just touched/killed/used
-// self.enemy should be set to the activator so it can be held through a delay
-// so wait for the delay time before firing
-void multi_trigger();
-
-void multi_use();
-
-void multi_touch();
-
-void multi_eventdamage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force);
-
-void multi_reset();
-
-/*QUAKED spawnfunc_trigger_multiple (.5 .5 .5) ? notouch
-Variable sized repeatable trigger.  Must be targeted at one or more entities.  If "health" is set, the trigger must be killed to activate each time.
-If "delay" is set, the trigger waits some time after activating before firing.
-"wait" : Seconds between triggerings. (.2 default)
-If notouch is set, the trigger is only fired by other entities, not by touching.
-NOTOUCH has been obsoleted by spawnfunc_trigger_relay!
-sounds
-1)     secret
-2)     beep beep
-3)     large switch
-4)
-set "message" to text string
-*/
-void spawnfunc_trigger_multiple();
-
-
-/*QUAKED spawnfunc_trigger_once (.5 .5 .5) ? notouch
-Variable sized trigger. Triggers once, then removes itself.  You must set the key "target" to the name of another object in the level that has a matching
-"targetname".  If "health" is set, the trigger must be killed to activate.
-If notouch is set, the trigger is only fired by other entities, not by touching.
-if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired.
-if "angle" is set, the trigger will only fire when someone is facing the direction of the angle.  Use "360" for an angle of 0.
-sounds
-1)     secret
-2)     beep beep
-3)     large switch
-4)
-set "message" to text string
-*/
-void spawnfunc_trigger_once();
-
-//=============================================================================
-
-/*QUAKED spawnfunc_trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
-This fixed size trigger cannot be touched, it can only be fired by other events.  It can contain killtargets, targets, delays, and messages.
-*/
-void spawnfunc_trigger_relay();
-
-void delay_use();
-
-void delay_reset();
-
-void spawnfunc_trigger_delay();
-
-//=============================================================================
-
-
-void counter_use();
-
-void counter_reset();
-
-/*QUAKED spawnfunc_trigger_counter (.5 .5 .5) ? nomessage
-Acts as an intermediary for an action that takes multiple inputs.
-
-If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
-
-After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
-*/
-void spawnfunc_trigger_counter();
-
-void trigger_hurt_use();
-
-.float triggerhurttime;
-void trigger_hurt_touch();
-
-/*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ?
-Any object touching this will be hurt
-set dmg to damage amount
-defalt dmg = 5
-*/
-.entity trigger_hurt_next;
-entity trigger_hurt_last;
-entity trigger_hurt_first;
-void spawnfunc_trigger_hurt();
-
-float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end);
-
-//////////////////////////////////////////////////////////////
-//
-//
-//
-//Trigger heal --a04191b92fbd93aa67214ef7e72d6d2e
-//
-//////////////////////////////////////////////////////////////
-
-.float triggerhealtime;
-void trigger_heal_touch();
-
-void spawnfunc_trigger_heal();
-
-
-//////////////////////////////////////////////////////////////
-//
-//
-//
-//End trigger_heal
-//
-//////////////////////////////////////////////////////////////
-
-.entity trigger_gravity_check;
-void trigger_gravity_remove(entity own);
-void trigger_gravity_check_think();
-
-void trigger_gravity_use();
-
-void trigger_gravity_touch();
-
-void spawnfunc_trigger_gravity();
-
-//=============================================================================
-
-// TODO add a way to do looped sounds with sound(); then complete this entity
-.float volume, atten;
-void target_speaker_use_off();
-void target_speaker_use_activator();
-void target_speaker_use_on();
-void target_speaker_use_off();
-void target_speaker_reset();
-
-void spawnfunc_target_speaker();
-
-
-void spawnfunc_func_stardust();
-
-.string bgmscript;
-.float bgmscriptattack;
-.float bgmscriptdecay;
-.float bgmscriptsustain;
-.float bgmscriptrelease;
-float pointparticles_SendEntity(entity to, float fl);
-
-void pointparticles_use();
-
-void pointparticles_think();
-
-void pointparticles_reset();
-
-void spawnfunc_func_pointparticles();
-
-void spawnfunc_func_sparks();
-
-float rainsnow_SendEntity(entity to, float sf);
-
-/*QUAKED spawnfunc_func_rain (0 .5 .8) ?
-This is an invisible area like a trigger, which rain falls inside of.
-
-Keys:
-"velocity"
- falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
-"cnt"
- sets color of rain (default 12 - white)
-"count"
- adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
-*/
-void spawnfunc_func_rain();
-
-
-/*QUAKED spawnfunc_func_snow (0 .5 .8) ?
-This is an invisible area like a trigger, which snow falls inside of.
-
-Keys:
-"velocity"
- falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
-"cnt"
- sets color of rain (default 12 - white)
-"count"
- adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
-*/
-void spawnfunc_func_snow();
-
-.float modelscale;
-void misc_laser_aim();
-
-void misc_laser_init();
-
-.entity pusher;
-void misc_laser_think();
-
-float laser_SendEntity(entity to, float fl);
-
-/*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
-Any object touching the beam will be hurt
-Keys:
-"target"
- spawnfunc_target_position where the laser ends
-"mdl"
- name of beam end effect to use
-"colormod"
- color of the beam (default: red)
-"dmg"
- damage per second (-1 for a laser that kills immediately)
-*/
-void laser_use();
-
-void laser_reset();
-
-void spawnfunc_misc_laser();
-
-// tZorks trigger impulse / gravity
-.float radius;
-.float falloff;
-.float strength;
-.float lastpushtime;
-
-// targeted (directional) mode
-void trigger_impulse_touch1();
-
-// Directionless (accelerator/decelerator) mode
-void trigger_impulse_touch2();
-
-// Spherical (gravity/repulsor) mode
-void trigger_impulse_touch3();
-
-/*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
--------- KEYS --------
-target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
-         If not, this trigger acts like a damper/accelerator field.
-
-strength : This is how mutch force to add in the direction of .target each second
-           when .target is set. If not, this is hoe mutch to slow down/accelerate
-           someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
-
-radius   : If set, act as a spherical device rather then a liniar one.
-
-falloff : 0 = none, 1 = liniar, 2 = inverted liniar
-
--------- NOTES --------
-Use a brush textured with common/origin in the trigger entity to determine the origin of the force
-in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
-*/
-
-void spawnfunc_trigger_impulse();
-
-/*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
-"Flip-flop" trigger gate... lets only every second trigger event through
-*/
-void flipflop_use();
-
-void spawnfunc_trigger_flipflop();
-
-/*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
-"Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
-*/
-void monoflop_use();
-void monoflop_fixed_use();
-
-void monoflop_think();
-
-void monoflop_reset();
-
-void spawnfunc_trigger_monoflop();
-
-void multivibrator_send();
-
-void multivibrator_toggle();
-
-void multivibrator_reset();
-
-/*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
-"Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
--------- KEYS --------
-target: trigger all entities with this targetname when it goes off
-targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
-phase: offset of the timing
-wait: "on" cycle time (default: 1)
-respawntime: "off" cycle time (default: same as wait)
--------- SPAWNFLAGS --------
-START_ON: assume it is already turned on (when targeted)
-*/
-void spawnfunc_trigger_multivibrator();
-
-
-void follow_init();
-
-void spawnfunc_misc_follow();
-
-
-
-void gamestart_use();
-
-void spawnfunc_trigger_gamestart();
-
-
-
-
-.entity voicescript; // attached voice script
-.float voicescript_index; // index of next voice, or -1 to use the randomized ones
-.float voicescript_nextthink; // time to play next voice
-.float voicescript_voiceend; // time when this voice ends
-
-void target_voicescript_clear(entity pl);
-
-void target_voicescript_use();
-
-void target_voicescript_next(entity pl);
-
-void spawnfunc_target_voicescript();
-
-
-
-void trigger_relay_teamcheck_use();
-
-void trigger_relay_teamcheck_reset();
-
-void spawnfunc_trigger_relay_teamcheck();
-
-
-
-void trigger_disablerelay_use();
-
-void spawnfunc_trigger_disablerelay();
-
-float magicear_matched;
-float W_Tuba_HasPlayed(entity pl, string melody, float instrument, float ignorepitch, float mintempo, float maxtempo);
-string trigger_magicear_processmessage(entity ear, entity source, float teamsay, entity privatesay, string msgin);
-
-entity magicears;
-string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin);
-
-void spawnfunc_trigger_magicear();
-
-void relay_activators_use();
-
-void spawnfunc_relay_activate();
-
-void spawnfunc_relay_deactivate();
-
-void spawnfunc_relay_activatetoggle();
-
-.string chmap, gametype;
-void spawnfunc_target_changelevel_use();
-
-void spawnfunc_target_changelevel();
-#endif
index 04f8ff7b38afbf7551a2a162bf1153acc043c6cd..007644084a0ea27fe88c7b0f676b8f3b3bad6580 100644 (file)
@@ -32,7 +32,6 @@
     #include "ipban.qh"
     #include "race.qh"
     #include "antilag.qh"
-    #include "secret.qh"
 #endif
 
 const float LATENCY_THINKRATE = 10;
@@ -574,6 +573,7 @@ void Nagger_Init();
 void ClientInit_Spawn();
 void WeaponStats_Init();
 void WeaponStats_Shutdown();
+void Physics_AddStats();
 void spawnfunc_worldspawn (void)
 {
        float fd, l, i, j, n;
@@ -814,11 +814,8 @@ void spawnfunc_worldspawn (void)
        addstat(STAT_FROZEN, AS_INT, frozen);
        addstat(STAT_REVIVE_PROGRESS, AS_FLOAT, revive_progress);
 
-       // g_movementspeed hack
-       addstat(STAT_MOVEVARS_AIRSPEEDLIMIT_NONQW, AS_FLOAT, stat_sv_airspeedlimit_nonqw);
-       addstat(STAT_MOVEVARS_MAXSPEED, AS_FLOAT, stat_sv_maxspeed);
-       addstat(STAT_MOVEVARS_AIRACCEL_QW, AS_FLOAT, stat_sv_airaccel_qw);
-       addstat(STAT_MOVEVARS_AIRSTRAFEACCEL_QW, AS_FLOAT, stat_sv_airstrafeaccel_qw);
+       // physics
+       Physics_AddStats();
 
        // secrets
        addstat(STAT_SECRETS_TOTAL, AS_FLOAT, stat_secrets_total);
index f4893094efb533c429902e0defbd2ad2ad82b1df..a130b826c2ce46f3a918e637b56d50bd780a350a 100644 (file)
@@ -6,6 +6,7 @@
     #include "../warpzonelib/util_server.qh"
     #include "../common/util.qh"
     #include "../common/monsters/monsters.qh"
+       #include "../common/triggers/subs.qh"
     #include "defs.qh"
     #include "../common/notifications.qh"
     #include "item_key.qh"
@@ -20,7 +21,8 @@ TODO:
 - should keys have a trigger?
 */
 
-bool item_keys_usekey(entity l, entity p) {
+bool item_keys_usekey(entity l, entity p)
+{
        float valid = l.itemkeys & p.itemkeys;
 
        if (!valid) {
@@ -281,156 +283,3 @@ void spawnfunc_item_key2(void) {
        self.itemkeys = ITEM_KEY_BIT(0);
        spawnfunc_item_key();
 };
-
-
-/*
-================================
-trigger_keylock
-================================
-*/
-
-/**
- * trigger givent targets
- */
-void trigger_keylock_trigger(string s) {
-       entity stemp = self;
-       entity otemp = other;
-       entity atemp = activator;
-
-       entity t;
-       for(t = world; (t = find(t, targetname, s)); )
-               if (t.use) {
-                       self = t;
-                       other = stemp;
-                       activator = atemp;
-                       self.use();
-               }
-
-       self = stemp;
-       other = otemp;
-       activator = atemp;
-};
-
-/**
- * kill killtarget of trigger keylock.
- */
-void trigger_keylock_kill(string s) {
-       entity t;
-       for(t = world; (t = find(t, targetname, s)); )
-               remove(t);
-};
-
-void trigger_keylock_touch(void) {
-       bool key_used = false;
-       bool started_delay = false;
-
-       // only player may trigger the lock
-       if (!IS_PLAYER(other))
-               return;
-
-
-       // check silver key
-       if (self.itemkeys)
-               key_used = item_keys_usekey(self, other);
-
-       activator = other;
-
-       if (self.itemkeys) {
-               // at least one of the keys is missing
-               if (key_used) {
-                       // one or more keys were given, but others are still missing!
-                       play2(other, self.noise1);
-                       Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_LOCKED_ALSONEED, item_keys_keylist(self.itemkeys));
-                       other.key_door_messagetime = time + 2;
-               } else if (other.key_door_messagetime <= time) {
-                       // no keys were given
-                       play2(other, self.noise2);
-                       Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_LOCKED_NEED, item_keys_keylist(self.itemkeys));
-                       other.key_door_messagetime = time + 2;
-               }
-
-               // trigger target2
-               if (self.delay <= time || started_delay == true)
-               if (self.target2) {
-                       trigger_keylock_trigger(self.target2);
-                       started_delay = true;
-                       self.delay = time + self.wait;
-               }
-       } else {
-               // all keys were given!
-               play2(other, self.noise);
-               centerprint(other, self.message);
-
-               if (self.target)
-                       trigger_keylock_trigger(self.target);
-
-               if (self.killtarget)
-                       trigger_keylock_kill(self.killtarget);
-
-               remove(self);
-       }
-
-};
-
-/*QUAKED trigger_keylock (.0 .5 .8) ?
-Keylock trigger.  Must target other entities.
-This trigger will trigger target entities when all required keys are provided.
--------- KEYS --------
-itemkeys: A bit field with key IDs that are needed to open this lock.
-sounds: 1 to play misc/secret.wav, 2 to play misc/talk.wav, 3 to play misc/trigger1.wav (3 is default)
-target: trigger all entities with this targetname when triggered and all keys have been given to it, then remove this trigger
-target2: trigger all entities with this targetname when triggered without giving it all the required keys.
-killtarget: remove all entities with this targetname when triggered with all the needed keys.
-message: print this message to the player who activated the trigger when all needed keys have been given.
-message2: print this message to the player who activated the trigger when not all of the needed keys have been given.
-noise: sound to play when lock gets unlocked (default: see sounds)
-noise1: sound to play when only some of the needed key were used but not all (default: misc/decreasevalue.wav)
-noise2: sound to play when a key is missing (default: misc/talk.wav)
-wait: prevent triggering again for this amount of time (default: 5) - applies to target2, target3, target4.
----------NOTES----------
-If spawned without any key specified in itemkeys, this trigger will display an error and remove itself.
-message2 and noise2 will be resent to the player every 2 seconds while he is in the trigger zone.
-*/
-void spawnfunc_trigger_keylock(void) {
-       if (!self.itemkeys) {
-               remove(self);
-               return;
-       }
-
-       // set unlocked message
-       if (self.message == "")
-               self.message = "Unlocked!";
-
-       // set default unlock noise
-       if (self.noise == "") {
-               if (self.sounds == 1)
-                       self.noise = "misc/secret.wav";
-               else if (self.sounds == 2)
-                       self.noise = "misc/talk.wav";
-               else //if (self.sounds == 3) {
-                       self.noise = "misc/trigger1.wav";
-       }
-
-       // set default use key sound
-       if (self.noise1 == "")
-               self.noise1 = "misc/decreasevalue.wav";
-
-       // set closed sourd
-       if (self.noise2 == "")
-               self.noise2 = "misc/talk.wav";
-
-       // delay between triggering message2 and trigger2
-       if (!self.wait)
-               self.wait = 5;
-
-       // precache sounds
-       precache_sound(self.noise);
-       precache_sound(self.noise1);
-       precache_sound(self.noise2);
-
-       EXACTTRIGGER_INIT;
-
-       self.touch = trigger_keylock_touch;
-};
-
-
index 6ab56050368601b25496c20717744e5ff1ff9b29..4a2acd85b7d1c015d8303f3c4d120b4b25178292 100644 (file)
@@ -11,6 +11,7 @@
 /**
  * list of key names.
  */
+#ifdef SVQC
 string item_keys_names[ITEM_KEY_MAX];
 
 /**
@@ -24,3 +25,5 @@ float item_keys_usekey(entity l, entity p);
  */
 string item_keys_keylist(float keylist);
 #endif
+
+#endif
index d998ea7999b836763b21472eedd131204983e1b2..2ce05407baaf3b9249b4b4c5d79a79f70bbc2cb5 100644 (file)
@@ -22,6 +22,7 @@
     #include "defs.qh"
     #include "../common/notifications.qh"
     #include "../common/deathtypes.qh"
+    #include "../common/triggers/subs.qh"
     #include "mutators/mutators_include.qh"
     #include "tturrets/include/turrets_early.qh"
     #include "../common/mapinfo.qh"
@@ -226,19 +227,6 @@ entity findnearest(vector point, .string field, string value, vector axismod)
     return nearest_entity[0];
 }
 
-void spawnfunc_target_location()
-{
-    self.classname = "target_location";
-    // location name in netname
-    // eventually support: count, teamgame selectors, line of sight?
-}
-
-void spawnfunc_info_location()
-{
-    self.classname = "target_location";
-    self.message = self.netname;
-}
-
 string NearestLocation(vector p)
 {
     entity loc;
@@ -778,7 +766,7 @@ void readplayerstartcvars()
        warmup_start_ammo_fuel = max(0, warmup_start_ammo_fuel);
 }
 
-float sound_allowed(float _dest, entity e)
+float sound_allowed(float destin, entity e)
 {
     // sounds from world may always pass
     for (;;)
@@ -793,7 +781,7 @@ float sound_allowed(float _dest, entity e)
             break;
     }
     // sounds to self may always pass
-    if (_dest == MSG_ONE)
+    if (destin == MSG_ONE)
         if (e == msg_entity)
             return true;
     // sounds by players can be removed
@@ -805,14 +793,14 @@ float sound_allowed(float _dest, entity e)
 }
 
 #undef sound
-void sound(entity e, float chan, string samp, float vol, float _atten)
+void sound(entity e, float chan, string samp, float vol, float attenu)
 {
     if (!sound_allowed(MSG_BROADCAST, e))
         return;
-    sound7(e, chan, samp, vol, _atten, 0, 0);
+    sound7(e, chan, samp, vol, attenu, 0, 0);
 }
 
-void soundtoat(float _dest, entity e, vector o, float chan, string samp, float vol, float _atten)
+void soundtoat(float _dest, entity e, vector o, float chan, string samp, float vol, float attenu)
 {
     float entno, idx;
 
@@ -825,12 +813,12 @@ void soundtoat(float _dest, entity e, vector o, float chan, string samp, float v
     float sflags;
     sflags = 0;
 
-    _atten = floor(_atten * 64);
+    attenu = floor(attenu * 64);
     vol = floor(vol * 255);
 
     if (vol != 255)
         sflags |= SND_VOLUME;
-    if (_atten != 64)
+    if (attenu != 64)
         sflags |= SND_ATTENUATION;
     if (entno >= 8192 || chan < 0 || chan > 7)
         sflags |= SND_LARGEENTITY;
@@ -842,7 +830,7 @@ void soundtoat(float _dest, entity e, vector o, float chan, string samp, float v
     if (sflags & SND_VOLUME)
         WriteByte(_dest, vol);
     if (sflags & SND_ATTENUATION)
-        WriteByte(_dest, _atten);
+        WriteByte(_dest, attenu);
     if (sflags & SND_LARGEENTITY)
     {
         WriteShort(_dest, entno);
@@ -1274,7 +1262,6 @@ void SetCustomizer(entity e, float(void) customizer, void(void) uncustomizer)
     e.uncustomizeentityforclient_set = !!uncustomizer;
 }
 
-
 void Net_LinkEntity(entity e, float docull, float dt, float(entity, float) sendfunc)
 {
     vector mi, ma;
index f19f53fd0b3010d021308c2a29235f195097d1d1..fc2bf4be991cf6f6b28bc069e7a5573f59586fba 100644 (file)
@@ -99,15 +99,11 @@ void WarpZone_crosshair_trace(entity pl);
 void WarpZone_traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag);
 
 
-#define IFTARGETED if(!self.nottargeted && self.targetname != "")
-
 #define ITEM_TOUCH_NEEDKILL() (((trace_dpstartcontents | trace_dphitcontents) & DPCONTENTS_NODROP) || (trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY))
 #define ITEM_DAMAGE_NEEDKILL(dt) (((dt) == DEATH_HURTTRIGGER) || ((dt) == DEATH_SLIME) || ((dt) == DEATH_LAVA) || ((dt) == DEATH_SWAMP))
 
 #define PROJECTILE_TOUCH if(WarpZone_Projectile_Touch()) return
 
-#define move_out_of_solid(e) WarpZoneLib_MoveOutOfSolid(e)
-
 const string STR_PLAYER = "player";
 const string STR_SPECTATOR = "spectator";
 const string STR_OBSERVER = "observer";
index 49deac0e8f350c1f73cfea58694a21ac7927af5b..de0932263a1065f2d6ea0dbc2300bd747aaf60a8 100644 (file)
@@ -1,3 +1,5 @@
+#include "../../common/triggers/subs.qh"
+
 // random functions
 void assault_objective_use()
 {
index 330707e67c949bf097192658d2c0d57c8a2048bb..1266492ca091e4b99a05f3cc89ffa13822e853a5 100644 (file)
@@ -29,6 +29,5 @@ const float ST_ASSAULT_OBJECTIVES = 1;
 const float SP_ASSAULT_OBJECTIVES = 4;
 
 // predefined spawnfuncs
-void spawnfunc_func_breakable();
 void target_objective_decrease_activate();
 #endif
index 2e6eb4ff732533e927fc4359459ea5fec9f940f6..4c94e49070179adfcc7dc98d673d2eb20e9c8cfb 100644 (file)
@@ -1,7 +1,4 @@
-// ================================================================
-//  Official capture the flag game mode coding, reworked by Samual
-//  Last updated: September, 2012
-// ================================================================
+#include "../../common/movetypes/movetypes.qh"
 
 void ctf_FakeTimeLimit(entity e, float t)
 {
@@ -60,24 +57,24 @@ void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnra
        if(current_height) // make sure we can actually do this arcing path
        {
                targpos = (to + ('0 0 1' * current_height));
-               WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
+               WarpZone_TraceLine(flag.move_origin, targpos, MOVE_NOMONSTERS, flag);
                if(trace_fraction < 1)
                {
                        //print("normal arc line failed, trying to find new pos...");
                        WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag);
                        targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET);
-                       WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
+                       WarpZone_TraceLine(flag.move_origin, targpos, MOVE_NOMONSTERS, flag);
                        if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
                        /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
                }
        }
        else { targpos = to; }
 
-       //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
+       //flag.move_angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
 
        vector desired_direction = normalize(targpos - from);
-       if(turnrate) { flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); }
-       else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); }
+       if(turnrate) { flag.move_velocity = (normalize(normalize(flag.move_velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); }
+       else { flag.move_velocity = (desired_direction * autocvar_g_ctf_pass_velocity); }
 }
 
 float ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
@@ -210,9 +207,9 @@ void ctf_Handle_Drop(entity flag, entity player, float droptype)
        player = (player ? player : flag.pass_sender);
 
        // main
-       flag.movetype = MOVETYPE_TOSS;
+       flag.move_movetype = MOVETYPE_TOSS;
        flag.takedamage = DAMAGE_YES;
-       flag.angles = '0 0 0';
+       flag.move_angles = '0 0 0';
        flag.health = flag.max_flag_health;
        flag.ctf_droptime = time;
        flag.ctf_dropper = player;
@@ -260,18 +257,18 @@ void ctf_Handle_Retrieve(entity flag, entity player)
        if(player.vehicle)
        {
                setattachment(flag, player.vehicle, "");
-               setorigin(flag, VEHICLE_FLAG_OFFSET);
+               flag.move_origin = VEHICLE_FLAG_OFFSET;
                flag.scale = VEHICLE_FLAG_SCALE;
        }
        else
        {
                setattachment(flag, player, "");
-               setorigin(flag, FLAG_CARRY_OFFSET);
+               flag.move_origin = VEHICLE_FLAG_OFFSET;
        }
-       flag.movetype = MOVETYPE_NONE;
+       flag.move_movetype = MOVETYPE_NONE;
        flag.takedamage = DAMAGE_NO;
        flag.solid = SOLID_NOT;
-       flag.angles = '0 0 0';
+       flag.move_angles = '0 0 0';
        flag.ctf_status = FLAG_CARRY;
 
        // messages and sounds
@@ -311,14 +308,15 @@ void ctf_Handle_Throw(entity player, entity receiver, float droptype)
 
        // reset the flag
        setattachment(flag, world, "");
-       setorigin(flag, player.origin + FLAG_DROP_OFFSET);
+       flag.move_origin = player.origin + FLAG_DROP_OFFSET;
        flag.owner.flagcarried = world;
        flag.owner = world;
        flag.solid = SOLID_TRIGGER;
        flag.ctf_dropper = player;
        flag.ctf_droptime = time;
 
-       flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
+       flag.move_flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
+       flag.flags = flag.move_flags;
 
        switch(droptype)
        {
@@ -335,7 +333,7 @@ void ctf_Handle_Throw(entity player, entity receiver, float droptype)
                        ctf_CalculatePassVelocity(flag, targ_origin, player.origin, false);
 
                        // main
-                       flag.movetype = MOVETYPE_FLY;
+                       flag.move_movetype = MOVETYPE_FLY;
                        flag.takedamage = DAMAGE_NO;
                        flag.pass_sender = player;
                        flag.pass_target = receiver;
@@ -353,21 +351,21 @@ void ctf_Handle_Throw(entity player, entity receiver, float droptype)
                        makevectors((player.v_angle.y * '0 1 0') + (bound(autocvar_g_ctf_throw_angle_min, player.v_angle.x, autocvar_g_ctf_throw_angle_max) * '1 0 0'));
 
                        flag_velocity = (('0 0 1' * autocvar_g_ctf_throw_velocity_up) + ((v_forward * autocvar_g_ctf_throw_velocity_forward) * ((player.items & IT_STRENGTH) ? autocvar_g_ctf_throw_strengthmultiplier : 1)));
-                       flag.velocity = W_CalculateProjectileVelocity(player.velocity, flag_velocity, false);
+                       flag.move_velocity = W_CalculateProjectileVelocity(player.velocity, flag_velocity, false);
                        ctf_Handle_Drop(flag, player, droptype);
                        break;
                }
 
                case DROP_RESET:
                {
-                       flag.velocity = '0 0 0'; // do nothing
+                       flag.move_velocity = '0 0 0'; // do nothing
                        break;
                }
 
                default:
                case DROP_NORMAL:
                {
-                       flag.velocity = W_CalculateProjectileVelocity(player.velocity, (('0 0 1' * autocvar_g_ctf_drop_velocity_up) + ((('0 1 0' * crandom()) + ('1 0 0' * crandom())) * autocvar_g_ctf_drop_velocity_side)), false);
+                       flag.move_velocity = W_CalculateProjectileVelocity(player.velocity, (('0 0 1' * autocvar_g_ctf_drop_velocity_up) + ((('0 1 0' * crandom()) + ('1 0 0' * crandom())) * autocvar_g_ctf_drop_velocity_side)), false);
                        ctf_Handle_Drop(flag, player, droptype);
                        break;
                }
@@ -421,8 +419,8 @@ void ctf_Handle_Capture(entity flag, entity toucher, float capturetype)
                PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
 
        // effects
-       pointparticles(particleeffectnum(flag.capeffect), flag.origin, '0 0 0', 1);
-       //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
+       pointparticles(particleeffectnum(flag.capeffect), flag.move_origin, '0 0 0', 1);
+       //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.move_origin - '0 0 15', -0.8, 0, 1);
 
        // other
        if(capturetype == CAPTURE_NORMAL)
@@ -487,20 +485,20 @@ void ctf_Handle_Pickup(entity flag, entity player, float pickuptype)
        if(player.vehicle)
        {
                setattachment(flag, player.vehicle, "");
-               setorigin(flag, VEHICLE_FLAG_OFFSET);
+               flag.move_origin = VEHICLE_FLAG_OFFSET;
                flag.scale = VEHICLE_FLAG_SCALE;
        }
        else
        {
                setattachment(flag, player, "");
-               setorigin(flag, FLAG_CARRY_OFFSET);
+               flag.move_origin = FLAG_CARRY_OFFSET;
        }
 
        // flag setup
-       flag.movetype = MOVETYPE_NONE;
+       flag.move_movetype = MOVETYPE_NONE;
        flag.takedamage = DAMAGE_NO;
        flag.solid = SOLID_NOT;
-       flag.angles = '0 0 0';
+       flag.move_angles = '0 0 0';
        flag.ctf_status = FLAG_CARRY;
 
        switch(pickuptype)
@@ -647,6 +645,7 @@ void ctf_CheckStalemate(void)
 
 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
 {
+       self.move_velocity = self.velocity;
        if(ITEM_DAMAGE_NEEDKILL(deathtype))
        {
                // automatically kill the flag and return it
@@ -663,13 +662,11 @@ void ctf_FlagDamage(entity inflictor, entity attacker, float damage, float death
        }
 }
 
-void ctf_FlagThink()
+void ctf_FlagUpdate()
 {
        // declarations
        entity tmp_entity;
 
-       self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
-
        // captureshield
        if(self == ctf_worldflaglist) // only for the first flag
                FOR_EACH_CLIENT(tmp_entity)
@@ -678,7 +675,7 @@ void ctf_FlagThink()
        // sanity checks
        if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
                dprint("wtf the flag got squashed?\n");
-               tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
+               tracebox(self.move_origin, FLAG_MIN, FLAG_MAX, self.move_origin, MOVE_NOMONSTERS, self);
                if(!trace_startsolid || self.noalign) // can we resize it without getting stuck?
                        setsize(self, FLAG_MIN, FLAG_MAX); }
 
@@ -686,7 +683,7 @@ void ctf_FlagThink()
        {
                case FLAG_DROPPED:
                {
-                       self.angles = '0 0 0';
+                       self.move_angles = '0 0 0';
                        break;
                }
 
@@ -702,7 +699,7 @@ void ctf_FlagThink()
                        {
                                for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
                                        if(tmp_entity.ctf_status == FLAG_DROPPED)
-                                       if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius)
+                                       if(vlen(self.move_origin - tmp_entity.move_origin) < autocvar_g_ctf_dropped_capture_radius)
                                        if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
                                                ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED);
                        }
@@ -716,18 +713,18 @@ void ctf_FlagThink()
                                vector midpoint = ((self.absmin + self.absmax) * 0.5);
                                if(pointcontents(midpoint) == CONTENT_WATER)
                                {
-                                       self.velocity = self.velocity * 0.5;
+                                       self.move_velocity = self.move_velocity * 0.5;
 
                                        if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
-                                               { self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
+                                               { self.move_velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
                                        else
-                                               { self.movetype = MOVETYPE_FLY; }
+                                               { self.move_movetype = MOVETYPE_FLY; }
                                }
-                               else if(self.movetype == MOVETYPE_FLY) { self.movetype = MOVETYPE_TOSS; }
+                               else if(self.move_movetype == MOVETYPE_FLY) { self.move_movetype = MOVETYPE_TOSS; }
                        }
                        if(autocvar_g_ctf_flag_return_dropped)
                        {
-                               if((vlen(self.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1))
+                               if((vlen(self.move_origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1))
                                {
                                        self.health = 0;
                                        ctf_CheckFlagReturn(self, RETURN_DROPPED);
@@ -771,12 +768,12 @@ void ctf_FlagThink()
                {
                        vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5);
                        targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin); // origin of target as seen by the flag (us)
-                       WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self);
+                       WarpZone_TraceLine(self.move_origin, targ_origin, MOVE_NOMONSTERS, self);
 
                        if((self.pass_target == world)
                                || (self.pass_target.deadflag != DEAD_NO)
                                || (self.pass_target.flagcarried)
-                               || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
+                               || (vlen(self.move_origin - targ_origin) > autocvar_g_ctf_pass_radius)
                                || ((trace_fraction < 1) && (trace_ent != self.pass_target))
                                || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
                        {
@@ -786,7 +783,7 @@ void ctf_FlagThink()
                        else
                        {
                                // still a viable target, go for it
-                               ctf_CalculatePassVelocity(self, targ_origin, self.origin, true);
+                               ctf_CalculatePassVelocity(self, targ_origin, self.move_origin, true);
                        }
                        return;
                }
@@ -799,6 +796,20 @@ void ctf_FlagThink()
        }
 }
 
+void ctf_FlagThink()
+{
+       self.nextthink = time;
+
+       if(time >= self.ctf_thinkrate)
+       {
+               self.ctf_thinkrate = time + FLAG_THINKRATE;
+               ctf_FlagUpdate();
+       }
+
+       //Movetype_Physics_NoMatchServer();
+       Movetype_Physics_MatchTicrate(sys_frametime, 0);
+}
+
 void ctf_FlagTouch()
 {
        if(gameover) { return; }
@@ -833,7 +844,7 @@ void ctf_FlagTouch()
        {
                if(time > self.wait) // if we haven't in a while, play a sound/effect
                {
-                       pointparticles(particleeffectnum(self.toucheffect), self.origin, '0 0 0', 1);
+                       pointparticles(particleeffectnum(self.toucheffect), self.move_origin, '0 0 0', 1);
                        sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTEN_NORM);
                        self.wait = time + FLAG_TOUCHRATE;
                }
@@ -912,15 +923,16 @@ void ctf_RespawnFlag(entity flag)
 
        // reset the flag
        setattachment(flag, world, "");
-       setorigin(flag, flag.ctf_spawnorigin);
+       flag.move_origin = flag.ctf_spawnorigin;
 
-       flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
+       flag.move_movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
        flag.takedamage = DAMAGE_NO;
        flag.health = flag.max_flag_health;
        flag.solid = SOLID_TRIGGER;
-       flag.velocity = '0 0 0';
-       flag.angles = flag.mangle;
-       flag.flags = FL_ITEM | FL_NOTARGET;
+       flag.move_velocity = '0 0 0';
+       flag.move_angles = flag.mangle;
+       flag.move_flags = FL_ITEM | FL_NOTARGET;
+       flag.flags = flag.move_flags;
 
        flag.ctf_status = FLAG_BASE;
        flag.owner = world;
@@ -951,11 +963,16 @@ void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf
        self.bot_basewaypoint = self.nearestwaypoint;
 
        // waypointsprites
+       // move_origin isnt accessible just yet
        WaypointSprite_SpawnFixed(((self.team == NUM_TEAM_1) ? "redbase" : "bluebase"), self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, false));
        WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, false));
 
        // captureshield setup
        ctf_CaptureShield_Spawn(self);
+
+       self.move_origin = self.origin;
+       self.move_angles = self.angles;
+       self.move_velocity = self.velocity;
 }
 
 void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
@@ -975,7 +992,8 @@ void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag
        flag.items = ((teamnumber) ? IT_KEY2 : IT_KEY1); // IT_KEY2: gold key (redish enough) - IT_KEY1: silver key (bluish enough)
        flag.classname = "item_flag_team";
        flag.target = "###item###"; // wut?
-       flag.flags = FL_ITEM | FL_NOTARGET;
+       flag.move_flags = FL_ITEM | FL_NOTARGET;
+       flag.flags = flag.move_flags;
        flag.solid = SOLID_TRIGGER;
        flag.takedamage = DAMAGE_NO;
        flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
@@ -986,13 +1004,15 @@ void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag
        flag.teleportable = TELEPORT_NORMAL;
        flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
        flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
-       flag.velocity = '0 0 0';
+       flag.move_velocity = '0 0 0';
        flag.mangle = flag.angles;
        flag.reset = ctf_Reset;
        flag.touch = ctf_FlagTouch;
+       flag.move_touch = flag.touch;
        flag.think = ctf_FlagThink;
        flag.nextthink = time + FLAG_THINKRATE;
        flag.ctf_status = FLAG_BASE;
+       flag.move_time = time;
 
        // appearence
        if(flag.model == "")       { flag.model = ((teamnumber) ? autocvar_g_ctf_flag_red_model : autocvar_g_ctf_flag_blue_model); }
@@ -1044,14 +1064,14 @@ void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag
        {
                flag.dropped_origin = flag.origin;
                flag.noalign = true;
-               flag.movetype = MOVETYPE_NONE;
+               flag.move_movetype = MOVETYPE_NONE;
        }
        else // drop to floor, automatically find a platform and set that as spawn origin
        {
                flag.noalign = false;
                self = flag;
                droptofloor();
-               flag.movetype = MOVETYPE_TOSS;
+               flag.move_movetype = MOVETYPE_TOSS;
        }
 
        InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
@@ -1074,7 +1094,7 @@ void havocbot_calculate_middlepoint()
        f = ctf_worldflaglist;
        while (f)
        {
-               fo = f.origin;
+               fo = f.move_origin;
                s = s + fo;
                f = f.ctf_worldflagnext;
        }
@@ -1218,7 +1238,7 @@ void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float d
                {
                        if(df_radius)
                        {
-                               if(vlen(org-head.origin)<df_radius)
+                               if(vlen(org-head.move_origin)<df_radius)
                                        navigation_routerating(head, ratingscale, 10000);
                        }
                        else
@@ -1382,7 +1402,7 @@ void havocbot_role_ctf_escort()
        // If the flag carrier reached the base switch to defense
        mf = havocbot_ctf_find_flag(self);
        if(mf.ctf_status!=FLAG_BASE)
-       if(vlen(ef.origin - mf.dropped_origin) < 300)
+       if(vlen(ef.move_origin - mf.dropped_origin) < 300)
        {
                havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
                return;
@@ -1441,7 +1461,7 @@ void havocbot_role_ctf_offense()
                if(mf.tag_entity)
                        pos = mf.tag_entity.origin;
                else
-                       pos = mf.origin;
+                       pos = mf.move_origin;
 
                // Try to get it if closer than the enemy base
                if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
@@ -1457,7 +1477,7 @@ void havocbot_role_ctf_offense()
                if(ef.tag_entity)
                        pos = ef.tag_entity.origin;
                else
-                       pos = ef.origin;
+                       pos = ef.move_origin;
 
                if(vlen(pos-mf.dropped_origin)>700)
                {
@@ -1954,9 +1974,9 @@ MUTATOR_HOOKFUNCTION(ctf_VehicleEnter)
                else
                {
                        setattachment(vh_player.flagcarried, vh_vehicle, "");
-                       setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
+                       vh_player.flagcarried.move_origin = VEHICLE_FLAG_OFFSET;
                        vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
-                       //vh_player.flagcarried.angles = '0 0 0';
+                       //vh_player.flagcarried.move_angles = '0 0 0';
                }
                return true;
        }
@@ -1969,9 +1989,9 @@ MUTATOR_HOOKFUNCTION(ctf_VehicleExit)
        if(vh_player.flagcarried)
        {
                setattachment(vh_player.flagcarried, vh_player, "");
-               setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET);
+               vh_player.flagcarried.move_origin = VEHICLE_FLAG_OFFSET;
                vh_player.flagcarried.scale = FLAG_SCALE;
-               vh_player.flagcarried.angles = '0 0 0';
+               vh_player.flagcarried.move_angles = '0 0 0';
                return true;
        }
 
@@ -2002,7 +2022,7 @@ MUTATOR_HOOKFUNCTION(ctf_MatchEnd)
                        case FLAG_PASSING:
                        {
                                // lock the flag, game is over
-                               flag.movetype = MOVETYPE_NONE;
+                               flag.move_movetype = MOVETYPE_NONE;
                                flag.takedamage = DAMAGE_NO;
                                flag.solid = SOLID_NOT;
                                flag.nextthink = false; // stop thinking
index 0e5930db2cbe83c95b439e8f75461cdce64eda7f..5d57dced798d7757ed8b3a086d2eec78e8842ab0 100644 (file)
@@ -103,6 +103,7 @@ float ctf_captimerecord; // record time for capturing the flag
 .entity ctf_dropper; // don't allow spam of dropping the flag
 .float max_flag_health;
 .float next_take_time;
+.float ctf_thinkrate;
 
 // passing/throwing properties
 .float pass_distance;
index dd6e8b2109be3b97ceab791a5874a8ec94edf880..314221a19a53ab34f0e7ef022122ee762f0639ee 100644 (file)
@@ -1,3 +1,5 @@
+#include "../../common/triggers/subs.qh"
+
 void dom_EventLog(string mode, float team_before, entity actor) // use an alias for easy changing and quick editing later
 {
        if(autocvar_sv_eventlog)
index d1f20f7ee950e5e3af577fe9dcbc154302cda51a..93ed9f7d8afdd9c4607e01cc2dbb730d35effb02 100644 (file)
@@ -1,3 +1,5 @@
+#include "../../common/triggers/subs.qh"
+
 float autocvar_g_onslaught_spawn_at_controlpoints;
 float autocvar_g_onslaught_spawn_at_generator;
 float autocvar_g_onslaught_cp_proxydecap;
index b2f800a27e06c1228d541cbabc47ef221c04ae9d..942c96513b22190b24a14b97f008f3832f4f25ee 100644 (file)
@@ -1,3 +1,5 @@
+#include "../../common/triggers/target/music.qh"
+
 float buffs_BuffModel_Customize()
 {
        entity player, myowner;
index ad52d3e2919f29a3563927a4c900ca401f35ee40..39ecc2fdeef4fce5238335a2b80da13d5c9d9da2 100644 (file)
@@ -1,6 +1,57 @@
+#ifdef CSQC
+       #define PHYS_DODGING_FRAMETIME                          (1 / (frametime <= 0 ? 60 : frametime))
+       #define PHYS_DODGING                                            getstati(STAT_DODGING)
+       #define PHYS_DODGING_DELAY                                      getstatf(STAT_DODGING_DELAY)
+       #define PHYS_DODGING_TIMEOUT(s)                         getstatf(STAT_DODGING_TIMEOUT)
+       #define PHYS_DODGING_HORIZ_SPEED_FROZEN         getstatf(STAT_DODGING_HORIZ_SPEED_FROZEN)
+       #define PHYS_DODGING_FROZEN_NODOUBLETAP         getstati(STAT_DODGING_FROZEN_NO_DOUBLETAP)
+       #define PHYS_DODGING_HORIZ_SPEED                        getstatf(STAT_DODGING_HORIZ_SPEED)
+       #define PHYS_DODGING_PRESSED_KEYS(s)            s.pressedkeys
+       #define PHYS_DODGING_HEIGHT_THRESHOLD           getstatf(STAT_DODGING_HEIGHT_THRESHOLD)
+       #define PHYS_DODGING_DISTANCE_THRESHOLD         getstatf(STAT_DODGING_DISTANCE_THRESHOLD)
+       #define PHYS_DODGING_RAMP_TIME                          getstatf(STAT_DODGING_RAMP_TIME)
+       #define PHYS_DODGING_UP_SPEED                           getstatf(STAT_DODGING_UP_SPEED)
+       #define PHYS_DODGING_WALL                                       getstatf(STAT_DODGING_WALL)
+#elif defined(SVQC)
+       #define PHYS_DODGING_FRAMETIME                          sys_frametime
+       #define PHYS_DODGING                                            g_dodging
+       #define PHYS_DODGING_DELAY                                      autocvar_sv_dodging_delay
+       #define PHYS_DODGING_TIMEOUT(s)                         s.cvar_cl_dodging_timeout
+       #define PHYS_DODGING_HORIZ_SPEED_FROZEN         autocvar_sv_dodging_horiz_speed_frozen
+       #define PHYS_DODGING_FROZEN_NODOUBLETAP         autocvar_sv_dodging_frozen_doubletap
+       #define PHYS_DODGING_HORIZ_SPEED                        autocvar_sv_dodging_horiz_speed
+       #define PHYS_DODGING_PRESSED_KEYS(s)            s.pressedkeys
+       #define PHYS_DODGING_HEIGHT_THRESHOLD           autocvar_sv_dodging_height_threshold
+       #define PHYS_DODGING_DISTANCE_THRESHOLD         autocvar_sv_dodging_wall_distance_threshold
+       #define PHYS_DODGING_RAMP_TIME                          autocvar_sv_dodging_ramp_time
+       #define PHYS_DODGING_UP_SPEED                           autocvar_sv_dodging_up_speed
+       #define PHYS_DODGING_WALL                                       autocvar_sv_dodging_wall_dodging
+#endif
+
+#ifdef SVQC
 
 .float cvar_cl_dodging_timeout;
 
+.float stat_dodging;
+.float stat_dodging_delay;
+.float stat_dodging_horiz_speed_frozen;
+.float stat_dodging_frozen_nodoubletap;
+.float stat_dodging_frozen;
+.float stat_dodging_horiz_speed;
+.float stat_dodging_height_threshold;
+.float stat_dodging_distance_threshold;
+.float stat_dodging_ramp_time;
+.float stat_dodging_up_speed;
+.float stat_dodging_wall;
+
+#endif
+
+// set to 1 to indicate dodging has started.. reset by physics hook after dodge has been done..
+.float dodging_action;
+
+// the jump part of the dodge cannot be ramped
+.float dodging_single_action;
+
 
 // these are used to store the last key press time for each of the keys..
 .float last_FORWARD_KEY_time;
 // until it's 0.
 .float dodging_velocity_gain;
 
-MUTATOR_HOOKFUNCTION(dodging_GetCvars) {
-       GetCvars_handleFloat(get_cvars_s, get_cvars_f, cvar_cl_dodging_timeout, "cl_dodging_timeout");
-       return 0;
+#ifdef CSQC
+.float pressedkeys;
+
+#elif defined(SVQC)
+
+void dodging_UpdateStats()
+{
+       self.stat_dodging = PHYS_DODGING;
+       self.stat_dodging_delay = PHYS_DODGING_DELAY;
+       self.stat_dodging_horiz_speed_frozen = PHYS_DODGING_HORIZ_SPEED_FROZEN;
+       self.stat_dodging_frozen = PHYS_DODGING_FROZEN;
+       self.stat_dodging_frozen_nodoubletap = PHYS_DODGING_FROZEN_NODOUBLETAP;
+       self.stat_dodging_height_threshold = PHYS_DODGING_HEIGHT_THRESHOLD;
+       self.stat_dodging_distance_threshold = PHYS_DODGING_DISTANCE_THRESHOLD;
+       self.stat_dodging_ramp_time = PHYS_DODGING_RAMP_TIME;
+       self.stat_dodging_up_speed = PHYS_DODGING_UP_SPEED;
+       self.stat_dodging_wall = PHYS_DODGING_WALL;
 }
 
-MUTATOR_HOOKFUNCTION(dodging_PlayerPhysics) {
-       // print("dodging_PlayerPhysics\n");
+void dodging_Initialize()
+{
+       addstat(STAT_DODGING, AS_INT, stat_dodging);
+       addstat(STAT_DODGING_DELAY, AS_FLOAT, stat_dodging_delay);
+       addstat(STAT_DODGING_TIMEOUT, AS_FLOAT, cvar_cl_dodging_timeout); // we stat this, so it is updated on the client when updated on server (otherwise, chaos)
+       addstat(STAT_DODGING_FROZEN_NO_DOUBLETAP, AS_INT, stat_dodging_frozen_nodoubletap);
+       addstat(STAT_DODGING_HORIZ_SPEED_FROZEN, AS_FLOAT, stat_dodging_horiz_speed_frozen);
+       addstat(STAT_DODGING_FROZEN, AS_INT, stat_dodging_frozen);
+       addstat(STAT_DODGING_HORIZ_SPEED, AS_FLOAT, stat_dodging_horiz_speed);
+       addstat(STAT_DODGING_HEIGHT_THRESHOLD, AS_FLOAT, stat_dodging_height_threshold);
+       addstat(STAT_DODGING_DISTANCE_THRESHOLD, AS_FLOAT, stat_dodging_distance_threshold);
+       addstat(STAT_DODGING_RAMP_TIME, AS_FLOAT, stat_dodging_ramp_time);
+       addstat(STAT_DODGING_UP_SPEED, AS_FLOAT, stat_dodging_up_speed);
+       addstat(STAT_DODGING_WALL, AS_FLOAT, stat_dodging_wall);
+}
+
+#endif
+
+// returns 1 if the player is close to a wall
+float check_close_to_wall(float threshold)
+{
+       if (PHYS_DODGING_WALL == 0) { return false; }
+
+       #define X(OFFSET)                                                                                                                               \
+       tracebox(self.origin, self.mins, self.maxs, self.origin + OFFSET, true, self);  \
+       if (trace_fraction < 1 && vlen (self.origin - trace_endpos) < threshold)                \
+               return true;
+       X(1000*v_right);
+       X(-1000*v_right);
+       X(1000*v_forward);
+       X(-1000*v_forward);
+       #undef X
+
+       return false;
+}
+
+float check_close_to_ground(float threshold)
+{
+       return IS_ONGROUND(self) ? true : false;
+}
+
+float PM_dodging_checkpressedkeys()
+{
+       if(!PHYS_DODGING)
+               return false;
 
-       float common_factor;
-       float new_velocity_gain;
-       float velocity_difference;
-       float clean_up_and_do_nothing;
-       float horiz_speed = autocvar_sv_dodging_horiz_speed;
+       float frozen_dodging = (PHYS_FROZEN(self) && PHYS_DODGING_FROZEN);
+       float frozen_no_doubletap = (frozen_dodging && !PHYS_DODGING_FROZEN_NODOUBLETAP);
 
-       if(self.frozen)
-               horiz_speed = autocvar_sv_dodging_horiz_speed_frozen;
+       // first check if the last dodge is far enough back in time so we can dodge again
+       if ((time - self.last_dodging_time) < PHYS_DODGING_DELAY)
+               return false;
+
+       makevectors(PHYS_WORLD_ANGLES(self));
+
+       if (check_close_to_ground(PHYS_DODGING_HEIGHT_THRESHOLD) != 1
+               && check_close_to_wall(PHYS_DODGING_DISTANCE_THRESHOLD) != 1)
+               return true;
+
+       float tap_direction_x = 0;
+       float tap_direction_y = 0;
+       float dodge_detected = 0;
+
+       #define X(COND,BTN,RESULT)                                                                                                                      \
+       if (PHYS_INPUT_MOVEVALUES(self)_##COND)                                                                                         \
+               /* is this a state change? */                                                                                                   \
+               if(!(PHYS_DODGING_PRESSED_KEYS(self) & KEY_##BTN) || frozen_no_doubletap) {             \
+                               tap_direction_##RESULT;                                                                                                 \
+                               if ((time - self.last_##BTN##_KEY_time) < PHYS_DODGING_TIMEOUT(self))   \
+                                       dodge_detected = 1;                                                                                                     \
+                               self.last_##BTN##_KEY_time = time;                                                                              \
+               }
+       X(x < 0, BACKWARD,      x--);
+       X(x > 0, FORWARD,       x++);
+       X(y < 0, LEFT,          y--);
+       X(y > 0, RIGHT,         y++);
+       #undef X
+
+       if (dodge_detected == 1)
+       {
+               self.last_dodging_time = time;
+
+               self.dodging_action = 1;
+               self.dodging_single_action = 1;
 
-    if (self.deadflag != DEAD_NO)
-        return 0;
+               self.dodging_velocity_gain = PHYS_DODGING_HORIZ_SPEED;
 
-       new_velocity_gain = 0;
-       clean_up_and_do_nothing = 0;
+               self.dodging_direction_x = tap_direction_x;
+               self.dodging_direction_y = tap_direction_y;
 
-       if (g_dodging == 0)
-               clean_up_and_do_nothing = 1;
+               // normalize the dodging_direction vector.. (unlike UT99) XD
+               float length = self.dodging_direction_x * self.dodging_direction_x
+                                       + self.dodging_direction_y * self.dodging_direction_y;
+               length = sqrt(length);
+
+               self.dodging_direction_x = self.dodging_direction_x * 1.0 / length;
+               self.dodging_direction_y = self.dodging_direction_y * 1.0 / length;
+               return true;
+       }
+       return false;
+}
+
+void PM_dodging()
+{
+       if (!PHYS_DODGING)
+               return;
+
+#ifdef SVQC
+       dodging_UpdateStats();
+#endif
+
+    if (PHYS_DEAD(self))
+        return;
 
        // when swimming, no dodging allowed..
        if (self.waterlevel >= WATERLEVEL_SWIMMING)
-               clean_up_and_do_nothing = 1;
-
-       if (clean_up_and_do_nothing != 0) {
+       {
                self.dodging_action = 0;
                self.dodging_direction_x = 0;
                self.dodging_direction_y = 0;
-               return 0;
+               return;
        }
 
        // make sure v_up, v_right and v_forward are sane
-       makevectors(self.angles);
+       makevectors(PHYS_WORLD_ANGLES(self));
 
        // if we have e.g. 0.5 sec ramptime and a frametime of 0.25, then the ramp code
        // will be called ramp_time/frametime times = 2 times. so, we need to
        // add 0.5 * the total speed each frame until the dodge action is done..
-       common_factor = sys_frametime / autocvar_sv_dodging_ramp_time;
+       float common_factor = PHYS_DODGING_FRAMETIME / PHYS_DODGING_RAMP_TIME;
 
        // if ramp time is smaller than frametime we get problems ;D
-       if (common_factor > 1)
-               common_factor = 1;
+       common_factor = min(common_factor, 1);
 
-       new_velocity_gain = self.dodging_velocity_gain - (common_factor * horiz_speed);
-       if (new_velocity_gain < 0)
-               new_velocity_gain = 0;
+       float horiz_speed = PHYS_FROZEN(self) ? PHYS_DODGING_HORIZ_SPEED_FROZEN : PHYS_DODGING_HORIZ_SPEED;
+       float new_velocity_gain = self.dodging_velocity_gain - (common_factor * horiz_speed);
+       new_velocity_gain = max(0, new_velocity_gain);
 
-       velocity_difference = self.dodging_velocity_gain - new_velocity_gain;
+       float velocity_difference = self.dodging_velocity_gain - new_velocity_gain;
 
        // ramp up dodging speed by adding some velocity each frame.. TODO: do it! :D
-       if (self.dodging_action == 1) {
+       if (self.dodging_action == 1)
+       {
                //disable jump key during dodge accel phase
-               if (self.movement.z > 0) self.movement_z = 0;
+               if(PHYS_INPUT_MOVEVALUES(self)_z > 0) { PHYS_INPUT_MOVEVALUES(self)_z = 0; }
 
-               self.velocity =
-                         self.velocity
-                       + ((self.dodging_direction.y * velocity_difference) * v_right)
-                       + ((self.dodging_direction.x * velocity_difference) * v_forward);
+               self.velocity += ((self.dodging_direction_y * velocity_difference) * v_right)
+                                       + ((self.dodging_direction_x * velocity_difference) * v_forward);
 
                self.dodging_velocity_gain = self.dodging_velocity_gain - velocity_difference;
        }
 
        // the up part of the dodge is a single shot action
-       if (self.dodging_single_action == 1) {
-               self.flags &= ~FL_ONGROUND;
+       if (self.dodging_single_action == 1)
+       {
+               UNSET_ONGROUND(self);
 
-               self.velocity =
-                         self.velocity
-                       + (autocvar_sv_dodging_up_speed * v_up);
+               self.velocity += PHYS_DODGING_UP_SPEED * v_up;
 
+#ifdef SVQC
                if (autocvar_sv_dodging_sound == 1)
                        PlayerSound(playersound_jump, CH_PLAYER, VOICETYPE_PLAYERSOUND);
 
                animdecide_setaction(self, ANIMACTION_JUMP, true);
+#endif
 
                self.dodging_single_action = 0;
        }
 
        // are we done with the dodging ramp yet?
-       if((self.dodging_action == 1) && ((time - self.last_dodging_time) > autocvar_sv_dodging_ramp_time))
+       if((self.dodging_action == 1) && ((time - self.last_dodging_time) > PHYS_DODGING_RAMP_TIME))
        {
                // reset state so next dodge can be done correctly
                self.dodging_action = 0;
                self.dodging_direction_x = 0;
                self.dodging_direction_y = 0;
        }
-
-       return 0;
-}
-
-
-// returns 1 if the player is close to a wall
-float check_close_to_wall(float threshold) {
-       if (autocvar_sv_dodging_wall_dodging == 0)
-               return 0;
-
-       vector trace_start;
-       vector trace_end;
-
-       trace_start = self.origin;
-
-       trace_end = self.origin + (1000*v_right);
-       tracebox(trace_start, self.mins, self.maxs, trace_end, true, self);
-       if (trace_fraction < 1 && vlen (self.origin - trace_endpos) < threshold)
-               return 1;
-
-       trace_end = self.origin - (1000*v_right);
-       tracebox(trace_start, self.mins, self.maxs, trace_end, true, self);
-       if (trace_fraction < 1 && vlen (self.origin - trace_endpos) < threshold)
-               return 1;
-
-       trace_end = self.origin + (1000*v_forward);
-       tracebox(trace_start, self.mins, self.maxs, trace_end, true, self);
-       if (trace_fraction < 1 && vlen (self.origin - trace_endpos) < threshold)
-               return 1;
-
-       trace_end = self.origin - (1000*v_forward);
-       tracebox(trace_start, self.mins, self.maxs, trace_end, true, self);
-       if (trace_fraction < 1 && vlen (self.origin - trace_endpos) < threshold)
-               return 1;
-
-       return 0;
 }
 
-float check_close_to_ground(float threshold) {
-       if (self.flags & FL_ONGROUND)
-               return 1;
+#ifdef SVQC
 
-       return 0;
+MUTATOR_HOOKFUNCTION(dodging_GetCvars)
+{
+       GetCvars_handleFloat(get_cvars_s, get_cvars_f, cvar_cl_dodging_timeout, "cl_dodging_timeout");
+       return false;
 }
 
-
-MUTATOR_HOOKFUNCTION(dodging_GetPressedKeys) {
+MUTATOR_HOOKFUNCTION(dodging_PlayerPhysics)
+{
        // print("dodging_PlayerPhysics\n");
+       PM_dodging();
 
-       float length;
-       vector tap_direction = '0 0 0';
-
-       float frozen_dodging, frozen_no_doubletap;
-       frozen_dodging = (self.frozen && autocvar_sv_dodging_frozen);
-       frozen_no_doubletap = (frozen_dodging && !autocvar_sv_dodging_frozen_doubletap);
-
-       float dodge_detected;
-       if (g_dodging == 0)
-               return 0;
-
-       dodge_detected = 0;
-
-       // first check if the last dodge is far enough back in time so we can dodge again
-       if ((time - self.last_dodging_time) < autocvar_sv_dodging_delay)
-               return 0;
-
-       if (check_close_to_ground(autocvar_sv_dodging_height_threshold) != 1
-               && check_close_to_wall(autocvar_sv_dodging_wall_distance_threshold) != 1)
-               return 0;
-
-       if (self.movement.x > 0) {
-               // is this a state change?
-               if (!(self.pressedkeys & KEY_FORWARD) || frozen_no_doubletap) {
-                       if ((time - self.last_FORWARD_KEY_time) < self.cvar_cl_dodging_timeout) {
-                               tap_direction.x = 1.0;
-                               dodge_detected = 1;
-                       }
-                       self.last_FORWARD_KEY_time = time;
-               }
-       }
-
-       if (self.movement.x < 0) {
-               // is this a state change?
-               if (!(self.pressedkeys & KEY_BACKWARD) || frozen_no_doubletap) {
-                       tap_direction.x = -1.0;
-                       if ((time - self.last_BACKWARD_KEY_time) < self.cvar_cl_dodging_timeout)        {
-                               dodge_detected = 1;
-                       }
-                       self.last_BACKWARD_KEY_time = time;
-               }
-       }
-
-       if (self.movement.y > 0) {
-               // is this a state change?
-               if (!(self.pressedkeys & KEY_RIGHT) || frozen_no_doubletap) {
-                       tap_direction.y = 1.0;
-                       if ((time - self.last_RIGHT_KEY_time) < self.cvar_cl_dodging_timeout)   {
-                               dodge_detected = 1;
-                       }
-                       self.last_RIGHT_KEY_time = time;
-               }
-       }
-
-       if (self.movement.y < 0) {
-               // is this a state change?
-               if (!(self.pressedkeys & KEY_LEFT) || frozen_no_doubletap) {
-                       tap_direction.y = -1.0;
-                       if ((time - self.last_LEFT_KEY_time) < self.cvar_cl_dodging_timeout)    {
-                               dodge_detected = 1;
-                       }
-                       self.last_LEFT_KEY_time = time;
-               }
-       }
-
-       if (dodge_detected == 1) {
-               self.last_dodging_time = time;
-
-               self.dodging_action = 1;
-               self.dodging_single_action = 1;
-
-               self.dodging_velocity_gain = autocvar_sv_dodging_horiz_speed;
-
-               self.dodging_direction_x = tap_direction.x;
-               self.dodging_direction_y = tap_direction.y;
-
-               // normalize the dodging_direction vector.. (unlike UT99) XD
-               length =          self.dodging_direction.x * self.dodging_direction.x;
-               length = length + self.dodging_direction.y * self.dodging_direction.y;
-               length = sqrt(length);
+       return false;
+}
 
-               self.dodging_direction_x = self.dodging_direction.x * 1.0/length;
-               self.dodging_direction_y = self.dodging_direction.y * 1.0/length;
-       }
+MUTATOR_HOOKFUNCTION(dodging_GetPressedKeys)
+{
+       PM_dodging_checkpressedkeys();
 
-       return 0;
+       return false;
 }
 
 MUTATOR_DEFINITION(mutator_dodging)
@@ -264,7 +307,8 @@ MUTATOR_DEFINITION(mutator_dodging)
        // this just turns on the cvar.
        MUTATOR_ONADD
        {
-               g_dodging = 1;
+               g_dodging = cvar("g_dodging");
+               dodging_Initialize();
        }
 
        // this just turns off the cvar.
@@ -273,9 +317,6 @@ MUTATOR_DEFINITION(mutator_dodging)
                g_dodging = 0;
        }
 
-       MUTATOR_ONREMOVE
-       {
-       }
-
-       return 0;
+       return false;
 }
+#endif
index d36f3ecbe82e566a030968198c11564cbefb7a2c..f78e6e044ef71cc2a44cc3a06b2f6f8c62861c0e 100644 (file)
@@ -1,28 +1,65 @@
 .float multijump_count;
 .float multijump_ready;
 
-MUTATOR_HOOKFUNCTION(multijump_PlayerPhysics)
+#ifdef CSQC
+
+#define PHYS_MULTIJUMP                                 getstati(STAT_MULTIJUMP)
+#define PHYS_MULTIJUMP_SPEED           getstatf(STAT_MULTIJUMP_SPEED)
+#define PHYS_MULTIJUMP_ADD                     getstati(STAT_MULTIJUMP_ADD)
+
+#elif defined(SVQC)
+
+#define PHYS_MULTIJUMP                                 autocvar_g_multijump
+#define PHYS_MULTIJUMP_SPEED           autocvar_g_multijump_speed
+#define PHYS_MULTIJUMP_ADD                     autocvar_g_multijump_add
+
+
+.float stat_multijump;
+.float stat_multijump_speed;
+.float stat_multijump_add;
+
+void multijump_UpdateStats()
 {
-       if(self.flags & FL_ONGROUND)
-               self.multijump_count = 0;
+       self.stat_multijump = PHYS_MULTIJUMP;
+       self.stat_multijump_speed = PHYS_MULTIJUMP_SPEED;
+       self.stat_multijump_add = PHYS_MULTIJUMP_ADD;
+}
 
-       return false;
+void multijump_AddStats()
+{
+       addstat(STAT_MULTIJUMP, AS_INT, stat_multijump);
+       addstat(STAT_MULTIJUMP_SPEED, AS_FLOAT, stat_multijump_speed);
+       addstat(STAT_MULTIJUMP_ADD, AS_INT, stat_multijump_add);
 }
 
-MUTATOR_HOOKFUNCTION(multijump_PlayerJump)
+#endif
+
+void PM_multijump()
 {
-       if (self.flags & FL_JUMPRELEASED && !(self.flags & FL_ONGROUND)) // jump button pressed this frame and we are in midair
+       if(!PHYS_MULTIJUMP) { return; }
+
+       if(IS_ONGROUND(self))
+       {
+               self.multijump_count = 0;
+       }
+}
+
+float PM_multijump_checkjump()
+{
+       if(!PHYS_MULTIJUMP) { return false; }
+
+       if (!IS_JUMP_HELD(self) && !IS_ONGROUND(self)) // jump button pressed this frame and we are in midair
                self.multijump_ready = true;  // this is necessary to check that we released the jump button and pressed it again
        else
                self.multijump_ready = false;
 
-       if(!player_multijump && self.multijump_ready && (autocvar_g_multijump == -1 || self.multijump_count < autocvar_g_multijump) && self.velocity.z > autocvar_g_multijump_speed)
+       if(!player_multijump && self.multijump_ready && (self.multijump_count < PHYS_MULTIJUMP || PHYS_MULTIJUMP == -1) && self.velocity_z > PHYS_MULTIJUMP_SPEED)
        {
-               if (autocvar_g_multijump)
+               if (PHYS_MULTIJUMP)
                {
-                       if (autocvar_g_multijump_add == 0) // in this case we make the z velocity == jumpvelocity
+                       if (!PHYS_MULTIJUMP_ADD) // in this case we make the z velocity == jumpvelocity
                        {
-                               if (self.velocity.z < autocvar_sv_jumpvelocity)
+                               if (self.velocity_z < PHYS_JUMPVELOCITY)
                                {
                                        player_multijump = true;
                                        self.velocity_z = 0;
@@ -33,21 +70,26 @@ MUTATOR_HOOKFUNCTION(multijump_PlayerJump)
 
                        if(player_multijump)
                        {
-                               if(self.movement.x != 0 || self.movement.y != 0) // don't remove all speed if player isnt pressing any movement keys
+                               if(PHYS_INPUT_MOVEVALUES(self)_x != 0 || PHYS_INPUT_MOVEVALUES(self)_y != 0) // don't remove all speed if player isnt pressing any movement keys
                                {
                                        float curspeed;
                                        vector wishvel, wishdir;
 
+#ifdef SVQC
                                        curspeed = max(
                                                vlen(vec2(self.velocity)), // current xy speed
                                                vlen(vec2(antilag_takebackavgvelocity(self, max(self.lastteleporttime + sys_frametime, time - 0.25), time))) // average xy topspeed over the last 0.25 secs
                                        );
-                                       makevectors(self.v_angle.y * '0 1 0');
-                                       wishvel = v_forward * self.movement.x + v_right * self.movement.y;
+#elif defined(CSQC)
+                                       curspeed = vlen(vec2(self.velocity));
+#endif
+
+                                       makevectors(PHYS_INPUT_ANGLES(self)_y * '0 1 0');
+                                       wishvel = v_forward * PHYS_INPUT_MOVEVALUES(self)_x + v_right * PHYS_INPUT_MOVEVALUES(self)_y;
                                        wishdir = normalize(wishvel);
 
-                                       self.velocity_x = wishdir.x * curspeed; // allow "dodging" at a multijump
-                                       self.velocity_y = wishdir.y * curspeed;
+                                       self.velocity_x = wishdir_x * curspeed; // allow "dodging" at a multijump
+                                       self.velocity_y = wishdir_y * curspeed;
                                        // keep velocity_z unchanged!
                                }
                                self.multijump_count += 1;
@@ -59,6 +101,20 @@ MUTATOR_HOOKFUNCTION(multijump_PlayerJump)
        return false;
 }
 
+#ifdef SVQC
+MUTATOR_HOOKFUNCTION(multijump_PlayerPhysics)
+{
+       multijump_UpdateStats();
+       PM_multijump();
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(multijump_PlayerJump)
+{
+       return PM_multijump_checkjump();
+}
+
 MUTATOR_HOOKFUNCTION(multijump_BuildMutatorsString)
 {
        ret_string = strcat(ret_string, ":multijump");
@@ -78,5 +134,11 @@ MUTATOR_DEFINITION(mutator_multijump)
        MUTATOR_HOOK(BuildMutatorsString, multijump_BuildMutatorsString, CBC_ORDER_ANY);
        MUTATOR_HOOK(BuildMutatorsPrettyString, multijump_BuildMutatorsPrettyString, CBC_ORDER_ANY);
 
+       MUTATOR_ONADD
+       {
+               multijump_AddStats();
+       }
+
        return false;
 }
+#endif
index 3932ce407d885c2ccb01d7c5f0a236aceac775a2..dc1b6bc3bf0c31ca30d74d826ec44abee4d0bc70 100644 (file)
@@ -1,3 +1,5 @@
+#include "../../common/triggers/target/music.qh"
+
 .entity nade_spawnloc;
 
 void nade_timer_think()
index a0170e4803f04ae82fad8c363f97f3fa5f27d454..c3bdec50c9d8e96e2d0ffa691802793f42bcf7e8 100644 (file)
@@ -74,7 +74,6 @@
     #include "../playerdemo.qh"
     #include "../round_handler.qh"
     #include "../item_key.qh"
-    #include "../secret.qh"
     #include "../pathlib/pathlib.qh"
     #include "../tturrets/include/turrets.qh"
     #include "../vehicles/vehicles.qh"
index 85e75cda9e2fce18b2f2715b0bd1c10e8452b7bb..9cab150c33d127858d2276187ca29002b5067a5f 100644 (file)
@@ -13,20 +13,17 @@ campaign.qc
 cheats.qc
 cl_client.qc
 cl_impulse.qc
-cl_physics.qc
 cl_player.qc
 csqceffects.qc
 // ctf.qc
 // domination.qc
 ent_cs.qc
-func_breakable.qc
 g_casings.qc
 g_damage.qc
 g_hook.qc
 g_models.qc
 g_subs.qc
 g_tetris.qc
-g_triggers.qc
 g_violence.qc
 g_world.qc
 ipban.qc
@@ -43,21 +40,14 @@ round_handler.qc
 // runematch.qc
 scores.qc
 scores_rules.qc
-secret.qc
 spawnpoints.qc
 steerlib.qc
 sv_main.qc
-target_music.qc
-target_spawn.qc
 teamplay.qc
 t_halflife.qc
 t_items.qc
-t_jumppads.qc
-t_plats.qc
 t_quake3.qc
 t_quake.qc
-t_swamp.qc
-t_teleporters.qc
 waypointsprites.qc
 
 bot/bot.qc
@@ -95,11 +85,14 @@ weapons/weaponsystem.qc
 ../common/monsters/monsters.qc
 ../common/monsters/spawn.qc
 ../common/monsters/sv_monsters.qc
+../common/movetypes/include.qc
 ../common/nades.qc
 ../common/net_notice.qc
 ../common/notifications.qc
+../common/physics.qc
 ../common/playerstats.qc
 ../common/test.qc
+../common/triggers/include.qc
 ../common/urllib.qc
 ../common/util.qc
 ../common/weapons/config.qc
index bb7f0eccc2297ad547b06181fa225d3957ad318d..a42d24034404e06eda65045a8a517ae125a98fa7 100644 (file)
@@ -1,4 +1,5 @@
 #include "race.qh"
+#include "../common/triggers/subs.qh"
 
 void W_Porto_Fail(float failhard);
 
index bfdc9be4305b80f27265d4c526694a51cfd6ee1e..b51c1a9c749ed0ae05d596b8d0ae988ea5ccdedc 100644 (file)
@@ -29,4 +29,40 @@ float race_GetFractionalLapCount(entity e);
 float race_readTime(string map, float pos);
 string race_readUID(string map, float pos);
 string race_readName(string map, float pos);
+
+
+#ifdef SVQC
+float speedaward_speed;
+string speedaward_holder;
+string speedaward_uid;
+#endif
+void race_send_speedaward(float msg)
+{
+#ifdef SVQC
+       // send the best speed of the round
+       WriteByte(msg, SVC_TEMPENTITY);
+       WriteByte(msg, TE_CSQC_RACE);
+       WriteByte(msg, RACE_NET_SPEED_AWARD);
+       WriteInt24_t(msg, floor(speedaward_speed+0.5));
+       WriteString(msg, speedaward_holder);
+#endif
+}
+
+#ifdef SVQC
+float speedaward_alltimebest;
+string speedaward_alltimebest_holder;
+string speedaward_alltimebest_uid;
+#endif
+void race_send_speedaward_alltimebest(float msg)
+{
+#ifdef SVQC
+       // send the best speed
+       WriteByte(msg, SVC_TEMPENTITY);
+       WriteByte(msg, TE_CSQC_RACE);
+       WriteByte(msg, RACE_NET_SPEED_AWARD_BEST);
+       WriteInt24_t(msg, floor(speedaward_alltimebest+0.5));
+       WriteString(msg, speedaward_alltimebest_holder);
+#endif
+}
+
 #endif
diff --git a/qcsrc/server/secret.qc b/qcsrc/server/secret.qc
deleted file mode 100644 (file)
index 30fbedc..0000000
+++ /dev/null
@@ -1,83 +0,0 @@
-#if defined(CSQC)
-#elif defined(MENUQC)
-#elif defined(SVQC)
-       #include "../dpdefs/progsdefs.qh"
-    #include "../common/util.qh"
-    #include "defs.qh"
-    #include "secret.qh"
-#endif
-
-void secrets_setstatus() {
-       self.stat_secrets_total = secrets_total;
-       self.stat_secrets_found = secrets_found;
-}
-
-/**
- * A secret has been found (maybe :P)
- */
-void trigger_secret_touch() {
-       // only a player can trigger this
-       if (!IS_PLAYER(other))
-               return;
-
-       // update secrets found counter
-       secrets_found += 1;
-       //print("Secret found: ", ftos(secret_counter.cnt), "/");
-       //print(ftos(secret_counter.count), "\n");
-
-       // centerprint message (multi_touch() doesn't always call centerprint())
-       centerprint(other, self.message);
-       self.message = "";
-
-       // handle normal trigger features
-       multi_touch();
-       remove(self);
-}
-
-/*QUAKED trigger_secret (.5 .5 .5) ?
-Variable sized secret trigger. Can be targeted at one or more entities.
-Basically, it's a trigger_once (with restrictions, see notes) that additionally updates the number of secrets found.
--------- KEYS --------
-sounds: 1 to play misc/secret.wav, 2 to play misc/talk.wav, 3 to play misc/trigger1.wav (default: 1)
-noise: path to sound file, if you want to play something else
-target: trigger all entities with this targetname when triggered
-message: print this message to the player who activated the trigger instead of the standard 'You found a secret!'
-killtarget: remove all entities with this targetname when triggered
--------- NOTES --------
-You should create a common/trigger textured brush covering the entrance to a secret room/area.
-Trigger secret can only be trigger by a player's touch and can not be a target itself.
-*/
-void spawnfunc_trigger_secret() {
-       // FIXME: should it be disabled in most modes?
-
-       // update secrets count
-       secrets_total += 1;
-
-       // add default message
-       if (self.message == "")
-               self.message = "You found a secret!";
-
-       // set default sound
-       if (self.noise == "")
-       if (!self.sounds)
-               self.sounds = 1; // misc/secret.wav
-
-       // this entity can't be a target itself!!!!
-       self.targetname = "";
-
-       // you can't just shoot a room to find it, can you?
-       self.health = 0;
-
-       // a secret can not be delayed
-       self.delay = 0;
-
-       // convert this trigger to trigger_once
-       self.classname = "trigger_once";
-       spawnfunc_trigger_once();
-
-       // take over the touch() function, so we can mark secret as found
-       self.touch = trigger_secret_touch;
-       // ignore triggering;
-       self.use = func_null;
-}
-
diff --git a/qcsrc/server/secret.qh b/qcsrc/server/secret.qh
deleted file mode 100644 (file)
index 6e6ae0b..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-#ifndef SECRET_H
-#define SECRET_H
-
-/**
- * Total number of secrets on the map.
- */
-float secrets_total;
-
-/**
- * Total numbe of secrets found on the map.
- */
-float secrets_found;
-
-
-.float stat_secrets_total;
-.float stat_secrets_found;
-
-/**
- * update secrets status.
- */
-void secrets_setstatus();
-#endif
index 253c70fbe071eff1be2f03a54c64bd18a7d06b58..aa5b09e1ea1d6e10daf279ca3c93d01bf548c7af 100644 (file)
@@ -6,6 +6,7 @@
     #include "../warpzonelib/util_server.qh"
     #include "../common/constants.qh"
     #include "../common/teams.qh"
+       #include "../common/triggers/subs.qh"
     #include "../common/util.qh"
     #include "autocvars.qh"
     #include "constants.qh"
index 681c5dc7d3f0998788b485baf38bd495d49a9682..f041dd39659aebd31a8b375b6e14d1fd51820f1c 100644 (file)
@@ -1,12 +1,3 @@
-#if defined(CSQC)
-#elif defined(MENUQC)
-#elif defined(SVQC)
-       #include "../dpdefs/progsdefs.qh"
-    #include "../warpzonelib/util_server.qh"
-    #include "defs.qh"
-    #include "vehicles/vehicles_def.qh"
-#endif
-
 .float  roomtype;
 .float  radius;
 .float  pitch;
@@ -41,30 +32,3 @@ void spawnfunc_info_node() {}
 void spawnfunc_env_sound() {}
 void spawnfunc_light_spot() {}
 void spawnfunc_func_healthcharger() {}
-
-
-void func_ladder_touch()
-{
-       if (!other.iscreature)
-               return;
-       if (other.vehicle_flags & VHF_ISVEHICLE)
-               return;
-
-       EXACTTRIGGER_TOUCH;
-
-       other.ladder_time = time + 0.1;
-       other.ladder_entity = self;
-}
-
-void spawnfunc_func_ladder()
-{
-       EXACTTRIGGER_INIT;
-       self.touch = func_ladder_touch;
-}
-
-void spawnfunc_func_water()
-{
-       EXACTTRIGGER_INIT;
-       self.touch = func_ladder_touch;
-}
-
index 03af802e71c12d8b762c0e34df606ba8c096ee3d..4c454c70e0368fd9710d6a4dd9cfa4e678580632 100644 (file)
@@ -4,7 +4,7 @@
     #include "../common/buffs.qh"
     #include "../common/weapons/weapons.qh"
     #include "../client/autocvars.qh"
-    #include "../client/movetypes.qh"
+    #include "../common/movetypes/movetypes.qh"
     #include "../client/main.qh"
     #include "../csqcmodellib/common.qh"
     #include "../csqcmodellib/cl_model.qh"
@@ -17,6 +17,7 @@
     #include "../common/constants.qh"
     #include "../common/util.qh"
     #include "../common/monsters/monsters.qh"
+       #include "../common/triggers/subs.qh"
     #include "../common/weapons/weapons.qh"
     #include "weapons/weaponsystem.qh"
     #include "t_items.qh"
diff --git a/qcsrc/server/t_jumppads.qc b/qcsrc/server/t_jumppads.qc
deleted file mode 100644 (file)
index 92af1cd..0000000
+++ /dev/null
@@ -1,330 +0,0 @@
-#include "t_jumppads.qh"
-
-void trigger_push_use()
-{
-       if(teamplay)
-               self.team = activator.team;
-}
-
-/*
-       trigger_push_calculatevelocity
-
-       Arguments:
-         org - origin of the object which is to be pushed
-         tgt - target entity (can be either a point or a model entity; if it is
-               the latter, its midpoint is used)
-         ht  - jump height, measured from the higher one of org and tgt's midpoint
-
-       Returns: velocity for the jump
-       the global trigger_push_calculatevelocity_flighttime is set to the total
-       jump time
- */
-
-vector trigger_push_calculatevelocity(vector org, entity tgt, float ht)
-{
-       float grav, sdist, zdist, vs, vz, jumpheight;
-       vector sdir, torg;
-
-       torg = tgt.origin + (tgt.mins + tgt.maxs) * 0.5;
-
-       grav = autocvar_sv_gravity;
-       if(other.gravity)
-               grav *= other.gravity;
-
-       zdist = torg.z - org.z;
-       sdist = vlen(torg - org - zdist * '0 0 1');
-       sdir = normalize(torg - org - zdist * '0 0 1');
-
-       // how high do we need to push the player?
-       jumpheight = fabs(ht);
-       if(zdist > 0)
-               jumpheight = jumpheight + zdist;
-
-       /*
-               STOP.
-
-               You will not understand the following equations anyway...
-               But here is what I did to get them.
-
-               I used the functions
-
-                 s(t) = t * vs
-                 z(t) = t * vz - 1/2 grav t^2
-
-               and solved for:
-
-                 s(ti) = sdist
-                 z(ti) = zdist
-                 max(z, ti) = jumpheight
-
-               From these three equations, you will find the three parameters vs, vz
-               and ti.
-        */
-
-       // push him so high...
-       vz = sqrt(fabs(2 * grav * jumpheight)); // NOTE: sqrt(positive)!
-
-       // we start with downwards velocity only if it's a downjump and the jump apex should be outside the jump!
-       if(ht < 0)
-               if(zdist < 0)
-                       vz = -vz;
-
-       vector solution;
-       solution = solve_quadratic(0.5 * grav, -vz, zdist); // equation "z(ti) = zdist"
-       // ALWAYS solvable because jumpheight >= zdist
-       if(!solution.z)
-               solution.y = solution.x; // just in case it is not solvable due to roundoff errors, assume two equal solutions at their center (this is mainly for the usual case with ht == 0)
-       if(zdist == 0)
-               solution.x = solution.y; // solution_x is 0 in this case, so don't use it, but rather use solution_y (which will be sqrt(0.5 * jumpheight / grav), actually)
-
-       if(zdist < 0)
-       {
-               // down-jump
-               if(ht < 0)
-               {
-                       // almost straight line type
-                       // jump apex is before the jump
-                       // we must take the larger one
-                       trigger_push_calculatevelocity_flighttime = solution.y;
-               }
-               else
-               {
-                       // regular jump
-                       // jump apex is during the jump
-                       // we must take the larger one too
-                       trigger_push_calculatevelocity_flighttime = solution.y;
-               }
-       }
-       else
-       {
-               // up-jump
-               if(ht < 0)
-               {
-                       // almost straight line type
-                       // jump apex is after the jump
-                       // we must take the smaller one
-                       trigger_push_calculatevelocity_flighttime = solution.x;
-               }
-               else
-               {
-                       // regular jump
-                       // jump apex is during the jump
-                       // we must take the larger one
-                       trigger_push_calculatevelocity_flighttime = solution.y;
-               }
-       }
-       vs = sdist / trigger_push_calculatevelocity_flighttime;
-
-       // finally calculate the velocity
-       return sdir * vs + '0 0 1' * vz;
-}
-
-void trigger_push_touch()
-{
-       if (self.active == ACTIVE_NOT)
-               return;
-
-       if (!isPushable(other))
-               return;
-
-       if(self.team)
-               if(((self.spawnflags & 4) == 0) == (self.team != other.team))
-                       return;
-
-       EXACTTRIGGER_TOUCH;
-
-       if(self.enemy)
-       {
-               other.velocity = trigger_push_calculatevelocity(other.origin, self.enemy, self.height);
-       }
-       else if(self.target)
-       {
-               entity e;
-               RandomSelection_Init();
-               for(e = world; (e = find(e, targetname, self.target)); )
-               {
-                       if(e.cnt)
-                               RandomSelection_Add(e, 0, string_null, e.cnt, 1);
-                       else
-                               RandomSelection_Add(e, 0, string_null, 1, 1);
-               }
-               other.velocity = trigger_push_calculatevelocity(other.origin, RandomSelection_chosen_ent, self.height);
-       }
-       else
-       {
-               other.velocity = self.movedir;
-       }
-
-       other.flags &= ~FL_ONGROUND;
-
-       if (IS_PLAYER(other))
-       {
-               // reset tracking of oldvelocity for impact damage (sudden velocity changes)
-               other.oldvelocity = other.velocity;
-
-               if(self.pushltime < time)  // prevent "snorring" sound when a player hits the jumppad more than once
-               {
-                       // flash when activated
-                       pointparticles(particleeffectnum("jumppad_activate"), other.origin, other.velocity, 1);
-                       sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
-                       self.pushltime = time + 0.2;
-               }
-               if(IS_REAL_CLIENT(other) || IS_BOT_CLIENT(other))
-               {
-                       bool found = false;
-                       for(int i = 0; i < other.jumppadcount && i < NUM_JUMPPADSUSED; ++i)
-                               if(other.(jumppadsused[i]) == self)
-                                       found = true;
-                       if(!found)
-                       {
-                               other.(jumppadsused[other.jumppadcount % NUM_JUMPPADSUSED]) = self;
-                               other.jumppadcount = other.jumppadcount + 1;
-                       }
-
-                       if(IS_REAL_CLIENT(other))
-                       {
-                               if(self.message)
-                                       centerprint(other, self.message);
-                       }
-                       else
-                               other.lastteleporttime = time;
-
-                       if (other.deadflag == DEAD_NO)
-                               animdecide_setaction(other, ANIMACTION_JUMP, true);
-               }
-               else
-                       other.jumppadcount = true;
-
-               // reset tracking of who pushed you into a hazard (for kill credit)
-               other.pushltime = 0;
-               other.istypefrag = 0;
-       }
-
-       if(self.enemy.target)
-       {
-               entity oldself;
-               oldself = self;
-               activator = other;
-               self = self.enemy;
-               SUB_UseTargets();
-               self = oldself;
-       }
-
-       if (other.flags & FL_PROJECTILE)
-       {
-               other.angles = vectoangles (other.velocity);
-               switch(other.movetype)
-               {
-                       case MOVETYPE_FLY:
-                               other.movetype = MOVETYPE_TOSS;
-                               other.gravity = 1;
-                               break;
-                       case MOVETYPE_BOUNCEMISSILE:
-                               other.movetype = MOVETYPE_BOUNCE;
-                               other.gravity = 1;
-                               break;
-               }
-               UpdateCSQCProjectile(other);
-       }
-
-       if (self.spawnflags & PUSH_ONCE)
-       {
-               self.touch = func_null;
-               self.think = SUB_Remove;
-               self.nextthink = time;
-       }
-}
-
-void trigger_push_findtarget()
-{
-       entity e, t;
-       vector org;
-
-       // first calculate a typical start point for the jump
-       org = (self.absmin + self.absmax) * 0.5;
-       org.z = self.absmax.z - PL_MIN_z;
-
-       if (self.target)
-       {
-               float n;
-               n = 0;
-               for(t = world; (t = find(t, targetname, self.target)); )
-               {
-                       ++n;
-                       e = spawn();
-                       setorigin(e, org);
-                       setsize(e, PL_MIN, PL_MAX);
-                       e.velocity = trigger_push_calculatevelocity(org, t, self.height);
-                       tracetoss(e, e);
-                       if(e.movetype == MOVETYPE_NONE)
-                               waypoint_spawnforteleporter(self, trace_endpos, vlen(trace_endpos - org) / vlen(e.velocity));
-                       remove(e);
-               }
-
-               if(n == 0)
-               {
-                       // no dest!
-                       objerror ("Jumppad with nonexistant target");
-                       return;
-               }
-               else if(n == 1)
-               {
-                       // exactly one dest - bots love that
-                       self.enemy = find(world, targetname, self.target);
-               }
-               else
-               {
-                       // have to use random selection every single time
-                       self.enemy = world;
-               }
-       }
-       else
-       {
-               e = spawn();
-               setorigin(e, org);
-               setsize(e, PL_MIN, PL_MAX);
-               e.velocity = self.movedir;
-               tracetoss(e, e);
-               waypoint_spawnforteleporter(self, trace_endpos, vlen(trace_endpos - org) / vlen(e.velocity));
-               remove(e);
-       }
-}
-
-/*
- * ENTITY PARAMETERS:
- *
- *   target:  target of jump
- *   height:  the absolute value is the height of the highest point of the jump
- *            trajectory above the higher one of the player and the target.
- *            the sign indicates whether the highest point is INSIDE (positive)
- *            or OUTSIDE (negative) of the jump trajectory. General rule: use
- *            positive values for targets mounted on the floor, and use negative
- *            values to target a point on the ceiling.
- *   movedir: if target is not set, this * speed * 10 is the velocity to be reached.
- */
-void spawnfunc_trigger_push()
-{
-       SetMovedir ();
-
-       EXACTTRIGGER_INIT;
-
-       self.active = ACTIVE_ACTIVE;
-       self.use = trigger_push_use;
-       self.touch = trigger_push_touch;
-
-       // normal push setup
-       if (!self.speed)
-               self.speed = 1000;
-       self.movedir = self.movedir * self.speed * 10;
-
-       if (!self.noise)
-               self.noise = "misc/jumppad.wav";
-       precache_sound (self.noise);
-
-       // this must be called to spawn the teleport waypoints for bots
-       InitializeEntity(self, trigger_push_findtarget, INITPRIO_FINDTARGET);
-}
-
-void spawnfunc_target_push() {}
-void spawnfunc_info_notnull() {}
-void spawnfunc_target_position() {}
diff --git a/qcsrc/server/t_jumppads.qh b/qcsrc/server/t_jumppads.qh
deleted file mode 100644 (file)
index 9d66e98..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-#ifndef T_JUMPPADS_H
-#define T_JUMPPADS_H
-
-const float PUSH_ONCE          = 1;
-const float PUSH_SILENT                = 2;
-
-.float pushltime;
-.float istypefrag;
-.float height;
-
-void() SUB_UseTargets;
-
-float trigger_push_calculatevelocity_flighttime;
-
-void trigger_push_use();
-
-/*
-       trigger_push_calculatevelocity
-
-       Arguments:
-         org - origin of the object which is to be pushed
-         tgt - target entity (can be either a point or a model entity; if it is
-               the latter, its midpoint is used)
-         ht  - jump height, measured from the higher one of org and tgt's midpoint
-
-       Returns: velocity for the jump
-       the global trigger_push_calculatevelocity_flighttime is set to the total
-       jump time
- */
-
-vector trigger_push_calculatevelocity(vector org, entity tgt, float ht);
-
-void trigger_push_touch();
-
-.vector dest;
-void trigger_push_findtarget();
-
-/*
- * ENTITY PARAMETERS:
- *
- *   target:  target of jump
- *   height:  the absolute value is the height of the highest point of the jump
- *            trajectory above the higher one of the player and the target.
- *            the sign indicates whether the highest point is INSIDE (positive)
- *            or OUTSIDE (negative) of the jump trajectory. General rule: use
- *            positive values for targets mounted on the floor, and use negative
- *            values to target a point on the ceiling.
- *   movedir: if target is not set, this * speed * 10 is the velocity to be reached.
- */
-void spawnfunc_trigger_push();
-
-void spawnfunc_target_push();
-void spawnfunc_info_notnull();
-void spawnfunc_target_position();
-#endif
diff --git a/qcsrc/server/t_plats.qc b/qcsrc/server/t_plats.qc
deleted file mode 100644 (file)
index 5b9433a..0000000
+++ /dev/null
@@ -1,2271 +0,0 @@
-#if defined(CSQC)
-#elif defined(MENUQC)
-#elif defined(SVQC)
-       #include "../dpdefs/progsdefs.qh"
-    #include "../dpdefs/dpextensions.qh"
-    #include "../warpzonelib/mathlib.qh"
-    #include "../warpzonelib/common.qh"
-    #include "../warpzonelib/util_server.qh"
-    #include "../common/constants.qh"
-    #include "../common/util.qh"
-    #include "../common/weapons/weapons.qh"
-    #include "constants.qh"
-    #include "defs.qh"
-    #include "../common/notifications.qh"
-    #include "../common/deathtypes.qh"
-    #include "command/common.qh"
-    #include "../csqcmodellib/sv_model.qh"
-#endif
-
-.float dmgtime2;
-void generic_plat_blocked()
-{
-    if(self.dmg && other.takedamage != DAMAGE_NO) {
-        if(self.dmgtime2 < time) {
-            Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
-            self.dmgtime2 = time + self.dmgtime;
-        }
-
-        // Gib dead/dying stuff
-        if(other.deadflag != DEAD_NO)
-            Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
-    }
-}
-
-
-.entity trigger_field;
-
-void() plat_center_touch;
-void() plat_outside_touch;
-void() plat_trigger_use;
-void() plat_go_up;
-void() plat_go_down;
-void() plat_crush;
-const float PLAT_LOW_TRIGGER = 1;
-
-void plat_spawn_inside_trigger()
-{
-       entity trigger;
-       vector tmin, tmax;
-
-       trigger = spawn();
-       trigger.touch = plat_center_touch;
-       trigger.movetype = MOVETYPE_NONE;
-       trigger.solid = SOLID_TRIGGER;
-       trigger.enemy = self;
-
-       tmin = self.absmin + '25 25 0';
-       tmax = self.absmax - '25 25 -8';
-       tmin.z = tmax.z - (self.pos1_z - self.pos2_z + 8);
-       if (self.spawnflags & PLAT_LOW_TRIGGER)
-               tmax.z = tmin.z + 8;
-
-       if (self.size.x <= 50)
-       {
-               tmin.x = (self.mins.x + self.maxs.x) / 2;
-               tmax.x = tmin.x + 1;
-       }
-       if (self.size.y <= 50)
-       {
-               tmin.y = (self.mins.y + self.maxs.y) / 2;
-               tmax.y = tmin.y + 1;
-       }
-
-       if(tmin.x < tmax.x)
-               if(tmin.y < tmax.y)
-                       if(tmin.z < tmax.z)
-                       {
-                               setsize (trigger, tmin, tmax);
-                               return;
-                       }
-
-       // otherwise, something is fishy...
-       remove(trigger);
-       objerror("plat_spawn_inside_trigger: platform has odd size or lip, can't spawn");
-}
-
-void plat_hit_top()
-{
-       sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
-       self.state = 1;
-       self.think = plat_go_down;
-       self.nextthink = self.ltime + 3;
-}
-
-void plat_hit_bottom()
-{
-       sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
-       self.state = 2;
-}
-
-void plat_go_down()
-{
-       sound (self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_NORM);
-       self.state = 3;
-       SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, plat_hit_bottom);
-}
-
-void plat_go_up()
-{
-       sound (self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_NORM);
-       self.state = 4;
-       SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, plat_hit_top);
-}
-
-void plat_center_touch()
-{
-       if (!other.iscreature)
-               return;
-
-       if (other.health <= 0)
-               return;
-
-       self = self.enemy;
-       if (self.state == 2)
-               plat_go_up ();
-       else if (self.state == 1)
-               self.nextthink = self.ltime + 1;        // delay going down
-}
-
-void plat_outside_touch()
-{
-       if (!other.iscreature)
-               return;
-
-       if (other.health <= 0)
-               return;
-
-       self = self.enemy;
-       if (self.state == 1)
-               plat_go_down ();
-}
-
-void plat_trigger_use()
-{
-       if (self.think)
-               return;         // already activated
-       plat_go_down();
-}
-
-
-void plat_crush()
-{
-    if((self.spawnflags & 4) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
-        Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
-    } else {
-        if((self.dmg) && (other.takedamage != DAMAGE_NO)) {   // Shall we bite?
-            Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
-            // Gib dead/dying stuff
-            if(other.deadflag != DEAD_NO)
-                Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
-        }
-
-        if (self.state == 4)
-            plat_go_down ();
-        else if (self.state == 3)
-            plat_go_up ();
-       // when in other states, then the plat_crush event came delayed after
-       // plat state already had changed
-       // this isn't a bug per se!
-    }
-}
-
-void plat_use()
-{
-       self.use = func_null;
-       if (self.state != 4)
-               objerror ("plat_use: not in up state");
-       plat_go_down();
-}
-
-.string sound1, sound2;
-
-void plat_reset()
-{
-       IFTARGETED
-       {
-               setorigin (self, self.pos1);
-               self.state = 4;
-               self.use = plat_use;
-       }
-       else
-       {
-               setorigin (self, self.pos2);
-               self.state = 2;
-               self.use = plat_trigger_use;
-       }
-}
-
-.float platmovetype_start_default, platmovetype_end_default;
-float set_platmovetype(entity e, string s)
-{
-       // sets platmovetype_start and platmovetype_end based on a string consisting of two values
-
-       float n;
-       n = tokenize_console(s);
-       if(n > 0)
-               e.platmovetype_start = stof(argv(0));
-       else
-               e.platmovetype_start = 0;
-
-       if(n > 1)
-               e.platmovetype_end = stof(argv(1));
-       else
-               e.platmovetype_end = e.platmovetype_start;
-
-       if(n > 2)
-               if(argv(2) == "force")
-                       return true; // no checking, return immediately
-
-       if(!cubic_speedfunc_is_sane(e.platmovetype_start, e.platmovetype_end))
-       {
-               objerror("Invalid platform move type; platform would go in reverse, which is not allowed.");
-               return false;
-       }
-
-       return true;
-}
-
-void spawnfunc_path_corner()
-{
-       // setup values for overriding train movement
-       // if a second value does not exist, both start and end speeds are the single value specified
-       if(!set_platmovetype(self, self.platmovetype))
-               return;
-}
-void spawnfunc_func_plat()
-{
-       if (self.sounds == 0)
-               self.sounds = 2;
-
-    if(self.spawnflags & 4)
-        self.dmg = 10000;
-
-    if(self.dmg && (self.message == ""))
-               self.message = "was squished";
-    if(self.dmg && (self.message2 == ""))
-               self.message2 = "was squished by";
-
-       if (self.sounds == 1)
-       {
-               precache_sound ("plats/plat1.wav");
-               precache_sound ("plats/plat2.wav");
-               self.noise = "plats/plat1.wav";
-               self.noise1 = "plats/plat2.wav";
-       }
-
-       if (self.sounds == 2)
-       {
-               precache_sound ("plats/medplat1.wav");
-               precache_sound ("plats/medplat2.wav");
-               self.noise = "plats/medplat1.wav";
-               self.noise1 = "plats/medplat2.wav";
-       }
-
-       if (self.sound1)
-       {
-               precache_sound (self.sound1);
-               self.noise = self.sound1;
-       }
-       if (self.sound2)
-       {
-               precache_sound (self.sound2);
-               self.noise1 = self.sound2;
-       }
-
-       self.mangle = self.angles;
-       self.angles = '0 0 0';
-
-       self.classname = "plat";
-       if (!InitMovingBrushTrigger())
-               return;
-       self.effects |= EF_LOWPRECISION;
-       setsize (self, self.mins , self.maxs);
-
-       self.blocked = plat_crush;
-
-       if (!self.speed)
-               self.speed = 150;
-       if (!self.lip)
-               self.lip = 16;
-       if (!self.height)
-               self.height = self.size.z - self.lip;
-
-       self.pos1 = self.origin;
-       self.pos2 = self.origin;
-       self.pos2_z = self.origin.z - self.height;
-
-       self.reset = plat_reset;
-       plat_reset();
-
-       plat_spawn_inside_trigger ();   // the "start moving" trigger
-}
-
-.float train_wait_turning;
-void() train_next;
-void train_wait()
-{
-       entity oldself;
-       oldself = self;
-       self = self.enemy;
-       SUB_UseTargets();
-       self = oldself;
-       self.enemy = world;
-
-       // if turning is enabled, the train will turn toward the next point while waiting
-       if(self.platmovetype_turn && !self.train_wait_turning)
-       {
-               entity targ, cp;
-               vector ang;
-               targ = find(world, targetname, self.target);
-               if((self.spawnflags & 1) && targ.curvetarget)
-                       cp = find(world, targetname, targ.curvetarget);
-               else
-                       cp = world;
-
-               if(cp) // bezier curves movement
-                       ang = cp.origin - (self.origin - self.view_ofs); // use the origin of the control point of the next path_corner
-               else // linear movement
-                       ang = targ.origin - (self.origin - self.view_ofs); // use the origin of the next path_corner
-               ang = vectoangles(ang);
-               ang.x = -ang.x; // flip up / down orientation
-
-               if(self.wait > 0) // slow turning
-                       SUB_CalcAngleMove(ang, TSPEED_TIME, self.ltime - time + self.wait, train_wait);
-               else // instant turning
-                       SUB_CalcAngleMove(ang, TSPEED_TIME, 0.0000001, train_wait);
-               self.train_wait_turning = true;
-               return;
-       }
-
-       if(self.noise != "")
-               stopsoundto(MSG_BROADCAST, self, CH_TRIGGER_SINGLE); // send this as unreliable only, as the train will resume operation shortly anyway
-
-       if(self.wait < 0 || self.train_wait_turning) // no waiting or we already waited while turning
-       {
-               self.train_wait_turning = false;
-               train_next();
-       }
-       else
-       {
-               self.think = train_next;
-               self.nextthink = self.ltime + self.wait;
-       }
-}
-
-void train_next()
-{
-       entity targ, cp = world;
-       vector cp_org = '0 0 0';
-
-       targ = find(world, targetname, self.target);
-       self.target = targ.target;
-       if (self.spawnflags & 1)
-       {
-               if(targ.curvetarget)
-               {
-                       cp = find(world, targetname, targ.curvetarget); // get its second target (the control point)
-                       cp_org = cp.origin - self.view_ofs; // no control point found, assume a straight line to the destination
-               }
-       }
-       if (self.target == "")
-               objerror("train_next: no next target");
-       self.wait = targ.wait;
-       if (!self.wait)
-               self.wait = 0.1;
-
-       if(targ.platmovetype)
-       {
-               // this path_corner contains a movetype overrider, apply it
-               self.platmovetype_start = targ.platmovetype_start;
-               self.platmovetype_end = targ.platmovetype_end;
-       }
-       else
-       {
-               // this path_corner doesn't contain a movetype overrider, use the train's defaults
-               self.platmovetype_start = self.platmovetype_start_default;
-               self.platmovetype_end = self.platmovetype_end_default;
-       }
-
-       if (targ.speed)
-       {
-               if (cp)
-                       SUB_CalcMove_Bezier(cp_org, targ.origin - self.view_ofs, TSPEED_LINEAR, targ.speed, train_wait);
-               else
-                       SUB_CalcMove(targ.origin - self.view_ofs, TSPEED_LINEAR, targ.speed, train_wait);
-       }
-       else
-       {
-               if (cp)
-                       SUB_CalcMove_Bezier(cp_org, targ.origin - self.view_ofs, TSPEED_LINEAR, self.speed, train_wait);
-               else
-                       SUB_CalcMove(targ.origin - self.view_ofs, TSPEED_LINEAR, self.speed, train_wait);
-       }
-
-       if(self.noise != "")
-               sound(self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
-}
-
-void func_train_find()
-{
-       entity targ;
-       targ = find(world, targetname, self.target);
-       self.target = targ.target;
-       if (self.target == "")
-               objerror("func_train_find: no next target");
-       setorigin(self, targ.origin - self.view_ofs);
-       self.nextthink = self.ltime + 1;
-       self.think = train_next;
-}
-
-/*QUAKED spawnfunc_func_train (0 .5 .8) ?
-Ridable platform, targets spawnfunc_path_corner path to follow.
-speed : speed the train moves (can be overridden by each spawnfunc_path_corner)
-target : targetname of first spawnfunc_path_corner (starts here)
-*/
-void spawnfunc_func_train()
-{
-       if (self.noise != "")
-               precache_sound(self.noise);
-
-       if (self.target == "")
-               objerror("func_train without a target");
-       if (!self.speed)
-               self.speed = 100;
-
-       if (!InitMovingBrushTrigger())
-               return;
-       self.effects |= EF_LOWPRECISION;
-
-       if (self.spawnflags & 2)
-       {
-               self.platmovetype_turn = true;
-               self.view_ofs = '0 0 0'; // don't offset a rotating train, origin works differently now
-       }
-       else
-               self.view_ofs = self.mins;
-
-       // wait for targets to spawn
-       InitializeEntity(self, func_train_find, INITPRIO_SETLOCATION);
-
-       self.blocked = generic_plat_blocked;
-       if(self.dmg && (self.message == ""))
-               self.message = " was squished";
-    if(self.dmg && (self.message2 == ""))
-               self.message2 = "was squished by";
-       if(self.dmg && (!self.dmgtime))
-               self.dmgtime = 0.25;
-       self.dmgtime2 = time;
-
-       if(!set_platmovetype(self, self.platmovetype))
-               return;
-       self.platmovetype_start_default = self.platmovetype_start;
-       self.platmovetype_end_default = self.platmovetype_end;
-
-       // TODO make a reset function for this one
-}
-
-void func_rotating_setactive(float astate)
-{
-
-       if (astate == ACTIVE_TOGGLE)
-       {
-               if(self.active == ACTIVE_ACTIVE)
-                       self.active = ACTIVE_NOT;
-               else
-                       self.active = ACTIVE_ACTIVE;
-       }
-       else
-               self.active = astate;
-
-       if(self.active  == ACTIVE_NOT)
-               self.avelocity = '0 0 0';
-       else
-               self.avelocity = self.pos1;
-}
-
-/*QUAKED spawnfunc_func_rotating (0 .5 .8) ? - - X_AXIS Y_AXIS
-Brush model that spins in place on one axis (default Z).
-speed   : speed to rotate (in degrees per second)
-noise   : path/name of looping .wav file to play.
-dmg     : Do this mutch dmg every .dmgtime intervall when blocked
-dmgtime : See above.
-*/
-
-void spawnfunc_func_rotating()
-{
-       if (self.noise != "")
-       {
-               precache_sound(self.noise);
-               ambientsound(self.origin, self.noise, VOL_BASE, ATTEN_IDLE);
-       }
-
-       self.active = ACTIVE_ACTIVE;
-       self.setactive = func_rotating_setactive;
-
-       if (!self.speed)
-               self.speed = 100;
-       // FIXME: test if this turns the right way, then remove this comment (negate as needed)
-       if (self.spawnflags & 4) // X (untested)
-               self.avelocity = '0 0 1' * self.speed;
-       // FIXME: test if this turns the right way, then remove this comment (negate as needed)
-       else if (self.spawnflags & 8) // Y (untested)
-               self.avelocity = '1 0 0' * self.speed;
-       // FIXME: test if this turns the right way, then remove this comment (negate as needed)
-       else // Z
-               self.avelocity = '0 1 0' * self.speed;
-
-       self.pos1 = self.avelocity;
-
-    if(self.dmg && (self.message == ""))
-        self.message = " was squished";
-    if(self.dmg && (self.message2 == ""))
-               self.message2 = "was squished by";
-
-
-    if(self.dmg && (!self.dmgtime))
-        self.dmgtime = 0.25;
-
-    self.dmgtime2 = time;
-
-       if (!InitMovingBrushTrigger())
-               return;
-       // no EF_LOWPRECISION here, as rounding angles is bad
-
-    self.blocked = generic_plat_blocked;
-
-       // wait for targets to spawn
-       self.nextthink = self.ltime + 999999999;
-       self.think = SUB_NullThink; // for PushMove
-
-       // TODO make a reset function for this one
-}
-
-.float height;
-void func_bobbing_controller_think()
-{
-       vector v;
-       self.nextthink = time + 0.1;
-
-       if(self.owner.active != ACTIVE_ACTIVE)
-       {
-               self.owner.velocity = '0 0 0';
-               return;
-       }
-
-       // calculate sinewave using makevectors
-       makevectors((self.nextthink * self.owner.cnt + self.owner.phase * 360) * '0 1 0');
-       v = self.owner.destvec + self.owner.movedir * v_forward.y;
-       if(self.owner.classname == "func_bobbing") // don't brake stuff if the func_bobbing was killtarget'ed
-               // * 10 so it will arrive in 0.1 sec
-               self.owner.velocity = (v - self.owner.origin) * 10;
-}
-
-/*QUAKED spawnfunc_func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS
-Brush model that moves back and forth on one axis (default Z).
-speed : how long one cycle takes in seconds (default 4)
-height : how far the cycle moves (default 32)
-phase : cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
-noise : path/name of looping .wav file to play.
-dmg : Do this mutch dmg every .dmgtime intervall when blocked
-dmgtime : See above.
-*/
-void spawnfunc_func_bobbing()
-{
-       entity controller;
-       if (self.noise != "")
-       {
-               precache_sound(self.noise);
-               soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
-       }
-       if (!self.speed)
-               self.speed = 4;
-       if (!self.height)
-               self.height = 32;
-       // center of bobbing motion
-       self.destvec = self.origin;
-       // time scale to get degrees
-       self.cnt = 360 / self.speed;
-
-       self.active = ACTIVE_ACTIVE;
-
-       // damage when blocked
-       self.blocked = generic_plat_blocked;
-       if(self.dmg && (self.message == ""))
-               self.message = " was squished";
-    if(self.dmg && (self.message2 == ""))
-               self.message2 = "was squished by";
-       if(self.dmg && (!self.dmgtime))
-               self.dmgtime = 0.25;
-       self.dmgtime2 = time;
-
-       // how far to bob
-       if (self.spawnflags & 1) // X
-               self.movedir = '1 0 0' * self.height;
-       else if (self.spawnflags & 2) // Y
-               self.movedir = '0 1 0' * self.height;
-       else // Z
-               self.movedir = '0 0 1' * self.height;
-
-       if (!InitMovingBrushTrigger())
-               return;
-
-       // wait for targets to spawn
-       controller = spawn();
-       controller.classname = "func_bobbing_controller";
-       controller.owner = self;
-       controller.nextthink = time + 1;
-       controller.think = func_bobbing_controller_think;
-       self.nextthink = self.ltime + 999999999;
-       self.think = SUB_NullThink; // for PushMove
-
-       // Savage: Reduce bandwith, critical on e.g. nexdm02
-       self.effects |= EF_LOWPRECISION;
-
-       // TODO make a reset function for this one
-}
-
-.float freq;
-void func_pendulum_controller_think()
-{
-       float v;
-       self.nextthink = time + 0.1;
-
-       if (!(self.owner.active == ACTIVE_ACTIVE))
-       {
-               self.owner.avelocity_x = 0;
-               return;
-       }
-
-       // calculate sinewave using makevectors
-       makevectors((self.nextthink * self.owner.freq + self.owner.phase) * '0 360 0');
-       v = self.owner.speed * v_forward.y + self.cnt;
-       if(self.owner.classname == "func_pendulum") // don't brake stuff if the func_bobbing was killtarget'ed
-       {
-               // * 10 so it will arrive in 0.1 sec
-               self.owner.avelocity_z = (remainder(v - self.owner.angles.z, 360)) * 10;
-       }
-}
-
-void spawnfunc_func_pendulum()
-{
-       entity controller;
-       if (self.noise != "")
-       {
-               precache_sound(self.noise);
-               soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
-       }
-
-       self.active = ACTIVE_ACTIVE;
-
-       // keys: angle, speed, phase, noise, freq
-
-       if(!self.speed)
-               self.speed = 30;
-       // not initializing self.dmg to 2, to allow damageless pendulum
-
-       if(self.dmg && (self.message == ""))
-               self.message = " was squished";
-       if(self.dmg && (self.message2 == ""))
-               self.message2 = "was squished by";
-       if(self.dmg && (!self.dmgtime))
-               self.dmgtime = 0.25;
-       self.dmgtime2 = time;
-
-       self.blocked = generic_plat_blocked;
-
-       self.avelocity_z = 0.0000001;
-       if (!InitMovingBrushTrigger())
-               return;
-
-       if(!self.freq)
-       {
-               // find pendulum length (same formula as Q3A)
-               self.freq = 1 / (M_PI * 2) * sqrt(autocvar_sv_gravity / (3 * max(8, fabs(self.mins.z))));
-       }
-
-       // copy initial angle
-       self.cnt = self.angles.z;
-
-       // wait for targets to spawn
-       controller = spawn();
-       controller.classname = "func_pendulum_controller";
-       controller.owner = self;
-       controller.nextthink = time + 1;
-       controller.think = func_pendulum_controller_think;
-       self.nextthink = self.ltime + 999999999;
-       self.think = SUB_NullThink; // for PushMove
-
-       //self.effects |= EF_LOWPRECISION;
-
-       // TODO make a reset function for this one
-}
-
-// button and multiple button
-
-void() button_wait;
-void() button_return;
-
-void button_wait()
-{
-       self.state = STATE_TOP;
-       self.nextthink = self.ltime + self.wait;
-       self.think = button_return;
-       activator = self.enemy;
-       SUB_UseTargets();
-       self.frame = 1;                 // use alternate textures
-}
-
-void button_done()
-{
-       self.state = STATE_BOTTOM;
-}
-
-void button_return()
-{
-       self.state = STATE_DOWN;
-       SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, button_done);
-       self.frame = 0;                 // use normal textures
-       if (self.health)
-               self.takedamage = DAMAGE_YES;   // can be shot again
-}
-
-
-void button_blocked()
-{
-       // do nothing, just don't come all the way back out
-}
-
-
-void button_fire()
-{
-       self.health = self.max_health;
-       self.takedamage = DAMAGE_NO;    // will be reset upon return
-
-       if (self.state == STATE_UP || self.state == STATE_TOP)
-               return;
-
-       if (self.noise != "")
-               sound (self, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
-
-       self.state = STATE_UP;
-       SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, button_wait);
-}
-
-void button_reset()
-{
-       self.health = self.max_health;
-       setorigin(self, self.pos1);
-       self.frame = 0;                 // use normal textures
-       self.state = STATE_BOTTOM;
-       if (self.health)
-               self.takedamage = DAMAGE_YES;   // can be shot again
-}
-
-void button_use()
-{
-       if(self.active != ACTIVE_ACTIVE)
-               return;
-
-       self.enemy = activator;
-       button_fire ();
-}
-
-void button_touch()
-{
-       if (!other)
-               return;
-       if (!other.iscreature)
-               return;
-       if(other.velocity * self.movedir < 0)
-               return;
-       self.enemy = other;
-       if (other.owner)
-               self.enemy = other.owner;
-       button_fire ();
-}
-
-void button_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
-{
-       if(self.spawnflags & DOOR_NOSPLASH)
-               if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
-                       return;
-       self.health = self.health - damage;
-       if (self.health <= 0)
-       {
-               self.enemy = damage_attacker;
-               button_fire ();
-       }
-}
-
-
-/*QUAKED spawnfunc_func_button (0 .5 .8) ?
-When a button is touched, it moves some distance in the direction of it's angle, triggers all of it's targets, waits some time, then returns to it's original position where it can be triggered again.
-
-"angle"                determines the opening direction
-"target"       all entities with a matching targetname will be used
-"speed"                override the default 40 speed
-"wait"         override the default 1 second wait (-1 = never return)
-"lip"          override the default 4 pixel lip remaining at end of move
-"health"       if set, the button must be killed instead of touched. If set to -1, the button will fire on ANY attack, even damageless ones like the InstaGib laser
-"sounds"
-0) steam metal
-1) wooden clunk
-2) metallic click
-3) in-out
-*/
-void spawnfunc_func_button()
-{
-       SetMovedir ();
-
-       if (!InitMovingBrushTrigger())
-               return;
-       self.effects |= EF_LOWPRECISION;
-
-       self.blocked = button_blocked;
-       self.use = button_use;
-
-//     if (self.health == 0) // all buttons are now shootable
-//             self.health = 10;
-       if (self.health)
-       {
-               self.max_health = self.health;
-               self.event_damage = button_damage;
-               self.takedamage = DAMAGE_YES;
-       }
-       else
-               self.touch = button_touch;
-
-       if (!self.speed)
-               self.speed = 40;
-       if (!self.wait)
-               self.wait = 1;
-       if (!self.lip)
-               self.lip = 4;
-
-    if(self.noise != "")
-        precache_sound(self.noise);
-
-       self.active = ACTIVE_ACTIVE;
-
-       self.pos1 = self.origin;
-       self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
-    self.flags |= FL_NOTARGET;
-
-       button_reset();
-}
-
-
-const float DOOR_START_OPEN = 1;
-const float DOOR_DONT_LINK = 4;
-const float DOOR_TOGGLE = 32;
-
-/*
-
-Doors are similar to buttons, but can spawn a fat trigger field around them
-to open without a touch, and they link together to form simultanious
-double/quad doors.
-
-Door.owner is the master door.  If there is only one door, it points to itself.
-If multiple doors, all will point to a single one.
-
-Door.enemy chains from the master door through all doors linked in the chain.
-
-*/
-
-/*
-=============================================================================
-
-THINK FUNCTIONS
-
-=============================================================================
-*/
-
-void() door_go_down;
-void() door_go_up;
-void() door_rotating_go_down;
-void() door_rotating_go_up;
-
-void door_blocked()
-{
-
-    if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
-        Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
-    } else {
-
-        if((self.dmg) && (other.takedamage == DAMAGE_YES))    // Shall we bite?
-            Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
-
-         //Dont chamge direction for dead or dying stuff
-        if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
-            if (self.wait >= 0)
-            {
-                if (self.state == STATE_DOWN)
-                       if (self.classname == "door")
-                       {
-                               door_go_up ();
-                       } else
-                       {
-                               door_rotating_go_up ();
-                       }
-                else
-                       if (self.classname == "door")
-                       {
-                               door_go_down ();
-                       } else
-                       {
-                               door_rotating_go_down ();
-                       }
-            }
-        } else {
-            //gib dying stuff just to make sure
-            if((self.dmg) && (other.takedamage != DAMAGE_NO))    // Shall we bite?
-                Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
-        }
-    }
-
-       //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
-// if a door has a negative wait, it would never come back if blocked,
-// so let it just squash the object to death real fast
-/*     if (self.wait >= 0)
-       {
-               if (self.state == STATE_DOWN)
-                       door_go_up ();
-               else
-                       door_go_down ();
-       }
-*/
-}
-
-
-void door_hit_top()
-{
-       if (self.noise1 != "")
-               sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
-       self.state = STATE_TOP;
-       if (self.spawnflags & DOOR_TOGGLE)
-               return;         // don't come down automatically
-       if (self.classname == "door")
-       {
-               self.think = door_go_down;
-       } else
-       {
-               self.think = door_rotating_go_down;
-       }
-       self.nextthink = self.ltime + self.wait;
-}
-
-void door_hit_bottom()
-{
-       if (self.noise1 != "")
-               sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
-       self.state = STATE_BOTTOM;
-}
-
-void door_go_down()
-{
-       if (self.noise2 != "")
-               sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
-       if (self.max_health)
-       {
-               self.takedamage = DAMAGE_YES;
-               self.health = self.max_health;
-       }
-
-       self.state = STATE_DOWN;
-       SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, door_hit_bottom);
-}
-
-void door_go_up()
-{
-       if (self.state == STATE_UP)
-               return;         // already going up
-
-       if (self.state == STATE_TOP)
-       {       // reset top wait time
-               self.nextthink = self.ltime + self.wait;
-               return;
-       }
-
-       if (self.noise2 != "")
-               sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
-       self.state = STATE_UP;
-       SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, door_hit_top);
-
-       string oldmessage;
-       oldmessage = self.message;
-       self.message = "";
-       SUB_UseTargets();
-       self.message = oldmessage;
-}
-
-
-
-/*
-=============================================================================
-
-ACTIVATION FUNCTIONS
-
-=============================================================================
-*/
-
-float door_check_keys(void) {
-       entity door = self.owner ? self.owner : self;
-
-       // no key needed
-       if (!door.itemkeys)
-               return true;
-
-       // this door require a key
-       // only a player can have a key
-       if (!IS_PLAYER(other))
-               return false;
-
-       if (item_keys_usekey(door, other)) {
-               // some keys were used
-               if (other.key_door_messagetime <= time) {
-                       play2(other, "misc/talk.wav");
-                       Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_LOCKED_ALSONEED, item_keys_keylist(door.itemkeys));
-                       other.key_door_messagetime = time + 2;
-               }
-       } else {
-               // no keys were used
-               if (other.key_door_messagetime <= time) {
-                       play2(other, "misc/talk.wav");
-                       Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_LOCKED_NEED, item_keys_keylist(door.itemkeys));
-                       other.key_door_messagetime = time + 2;
-               }
-       }
-
-       if (door.itemkeys) {
-               // door is now unlocked
-               play2(other, "misc/talk.wav");
-               Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_UNLOCKED);
-               return true;
-       } else
-               return false;
-}
-
-
-void door_fire()
-{
-       entity  oself;
-       entity  starte;
-
-       if (self.owner != self)
-               objerror ("door_fire: self.owner != self");
-
-       oself = self;
-
-       if (self.spawnflags & DOOR_TOGGLE)
-       {
-               if (self.state == STATE_UP || self.state == STATE_TOP)
-               {
-                       starte = self;
-                       do
-                       {
-                               if (self.classname == "door")
-                               {
-                                       door_go_down ();
-                               }
-                               else
-                               {
-                                       door_rotating_go_down ();
-                               }
-                               self = self.enemy;
-                       } while ( (self != starte) && (self != world) );
-                       self = oself;
-                       return;
-               }
-       }
-
-// trigger all paired doors
-       starte = self;
-       do
-       {
-               if (self.classname == "door")
-               {
-                       door_go_up ();
-               } else
-               {
-                       // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
-                       if ((self.spawnflags & 2) && other.trigger_reverse!=0 && self.lip!=666 && self.state == STATE_BOTTOM)
-                       {
-                               self.lip = 666; // self.lip is used to remember reverse opening direction for door_rotating
-                               self.pos2 = '0 0 0' - self.pos2;
-                       }
-                       // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
-                       if (!((self.spawnflags & 2) &&  (self.spawnflags & 8) && self.state == STATE_DOWN
-                           && (((self.lip==666) && (other.trigger_reverse==0)) || ((self.lip!=666) && (other.trigger_reverse!=0)))))
-                       {
-                               door_rotating_go_up ();
-                       }
-               }
-               self = self.enemy;
-       } while ( (self != starte) && (self != world) );
-       self = oself;
-}
-
-
-void door_use()
-{
-       entity oself;
-
-       //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
-
-       if (self.owner)
-       {
-               oself = self;
-               self = self.owner;
-               door_fire ();
-               self = oself;
-       }
-}
-
-
-void door_trigger_touch()
-{
-       if (other.health < 1)
-               if (!(other.iscreature && other.deadflag == DEAD_NO))
-                       return;
-
-       if (time < self.attack_finished_single)
-               return;
-
-       // check if door is locked
-       if (!door_check_keys())
-               return;
-
-       self.attack_finished_single = time + 1;
-
-       activator = other;
-
-       self = self.owner;
-       door_use ();
-}
-
-
-void door_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
-{
-       entity oself;
-       if(self.spawnflags & DOOR_NOSPLASH)
-               if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
-                       return;
-       self.health = self.health - damage;
-
-       if (self.itemkeys) {
-               // don't allow opening doors through damage if keys are required
-               return;
-       }
-
-       if (self.health <= 0)
-       {
-               oself = self;
-               self = self.owner;
-               self.health = self.max_health;
-               self.takedamage = DAMAGE_NO;    // wil be reset upon return
-               door_use ();
-               self = oself;
-       }
-}
-
-
-/*
-================
-door_touch
-
-Prints messages
-================
-*/
-void door_touch()
-{
-       if (!IS_PLAYER(other))
-               return;
-       if (self.owner.attack_finished_single > time)
-               return;
-
-       self.owner.attack_finished_single = time + 2;
-
-       if (!(self.owner.dmg) && (self.owner.message != ""))
-       {
-               if (IS_CLIENT(other))
-                       centerprint(other, self.owner.message);
-               play2(other, "misc/talk.wav");
-       }
-}
-
-
-void door_generic_plat_blocked()
-{
-
-    if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
-        Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
-    } else {
-
-        if((self.dmg) && (other.takedamage == DAMAGE_YES))    // Shall we bite?
-            Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
-
-         //Dont chamge direction for dead or dying stuff
-        if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
-            if (self.wait >= 0)
-            {
-                if (self.state == STATE_DOWN)
-                    door_rotating_go_up ();
-                else
-                    door_rotating_go_down ();
-            }
-        } else {
-            //gib dying stuff just to make sure
-            if((self.dmg) && (other.takedamage != DAMAGE_NO))    // Shall we bite?
-                Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
-        }
-    }
-
-       //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
-// if a door has a negative wait, it would never come back if blocked,
-// so let it just squash the object to death real fast
-/*     if (self.wait >= 0)
-       {
-               if (self.state == STATE_DOWN)
-                       door_rotating_go_up ();
-               else
-                       door_rotating_go_down ();
-       }
-*/
-}
-
-
-void door_rotating_hit_top()
-{
-       if (self.noise1 != "")
-               sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
-       self.state = STATE_TOP;
-       if (self.spawnflags & DOOR_TOGGLE)
-               return;         // don't come down automatically
-       self.think = door_rotating_go_down;
-       self.nextthink = self.ltime + self.wait;
-}
-
-void door_rotating_hit_bottom()
-{
-       if (self.noise1 != "")
-               sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
-       if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
-       {
-               self.pos2 = '0 0 0' - self.pos2;
-               self.lip = 0;
-       }
-       self.state = STATE_BOTTOM;
-}
-
-void door_rotating_go_down()
-{
-       if (self.noise2 != "")
-               sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
-       if (self.max_health)
-       {
-               self.takedamage = DAMAGE_YES;
-               self.health = self.max_health;
-       }
-
-       self.state = STATE_DOWN;
-       SUB_CalcAngleMove (self.pos1, TSPEED_LINEAR, self.speed, door_rotating_hit_bottom);
-}
-
-void door_rotating_go_up()
-{
-       if (self.state == STATE_UP)
-               return;         // already going up
-
-       if (self.state == STATE_TOP)
-       {       // reset top wait time
-               self.nextthink = self.ltime + self.wait;
-               return;
-       }
-       if (self.noise2 != "")
-               sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
-       self.state = STATE_UP;
-       SUB_CalcAngleMove (self.pos2, TSPEED_LINEAR, self.speed, door_rotating_hit_top);
-
-       string oldmessage;
-       oldmessage = self.message;
-       self.message = "";
-       SUB_UseTargets();
-       self.message = oldmessage;
-}
-
-
-
-
-/*
-=============================================================================
-
-SPAWNING FUNCTIONS
-
-=============================================================================
-*/
-
-
-entity spawn_field(vector fmins, vector fmaxs)
-{
-       entity  trigger;
-       vector  t1, t2;
-
-       trigger = spawn();
-       trigger.classname = "doortriggerfield";
-       trigger.movetype = MOVETYPE_NONE;
-       trigger.solid = SOLID_TRIGGER;
-       trigger.owner = self;
-       trigger.touch = door_trigger_touch;
-
-       t1 = fmins;
-       t2 = fmaxs;
-       setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
-       return (trigger);
-}
-
-
-entity LinkDoors_nextent(entity cur, entity near, entity pass)
-{
-       while((cur = find(cur, classname, self.classname)) && ((cur.spawnflags & 4) || cur.enemy))
-       {
-       }
-       return cur;
-}
-
-float LinkDoors_isconnected(entity e1, entity e2, entity pass)
-{
-       float DELTA = 4;
-       if (e1.absmin.x > e2.absmax.x + DELTA)
-               return false;
-       if (e1.absmin.y > e2.absmax.y + DELTA)
-               return false;
-       if (e1.absmin.z > e2.absmax.z + DELTA)
-               return false;
-       if (e2.absmin.x > e1.absmax.x + DELTA)
-               return false;
-       if (e2.absmin.y > e1.absmax.y + DELTA)
-               return false;
-       if (e2.absmin.z > e1.absmax.z + DELTA)
-               return false;
-       return true;
-}
-
-/*
-=============
-LinkDoors
-
-
-=============
-*/
-void LinkDoors()
-{
-       entity  t;
-       vector  cmins, cmaxs;
-
-       if (self.enemy)
-               return;         // already linked by another door
-       if (self.spawnflags & 4)
-       {
-               self.owner = self.enemy = self;
-
-               if (self.health)
-                       return;
-               IFTARGETED
-                       return;
-               if (self.items)
-                       return;
-               self.trigger_field = spawn_field(self.absmin, self.absmax);
-
-               return;         // don't want to link this door
-       }
-
-       FindConnectedComponent(self, enemy, LinkDoors_nextent, LinkDoors_isconnected, world);
-
-       // set owner, and make a loop of the chain
-       dprint("LinkDoors: linking doors:");
-       for(t = self; ; t = t.enemy)
-       {
-               dprint(" ", etos(t));
-               t.owner = self;
-               if(t.enemy == world)
-               {
-                       t.enemy = self;
-                       break;
-               }
-       }
-       dprint("\n");
-
-       // collect health, targetname, message, size
-       cmins = self.absmin;
-       cmaxs = self.absmax;
-       for(t = self; ; t = t.enemy)
-       {
-               if(t.health && !self.health)
-                       self.health = t.health;
-               if((t.targetname != "") && (self.targetname == ""))
-                       self.targetname = t.targetname;
-               if((t.message != "") && (self.message == ""))
-                       self.message = t.message;
-               if (t.absmin.x < cmins.x)
-                       cmins.x = t.absmin.x;
-               if (t.absmin.y < cmins.y)
-                       cmins.y = t.absmin.y;
-               if (t.absmin.z < cmins.z)
-                       cmins.z = t.absmin.z;
-               if (t.absmax.x > cmaxs.x)
-                       cmaxs.x = t.absmax.x;
-               if (t.absmax.y > cmaxs.y)
-                       cmaxs.y = t.absmax.y;
-               if (t.absmax.z > cmaxs.z)
-                       cmaxs.z = t.absmax.z;
-               if(t.enemy == self)
-                       break;
-       }
-
-       // distribute health, targetname, message
-       for(t = self; t; t = t.enemy)
-       {
-               t.health = self.health;
-               t.targetname = self.targetname;
-               t.message = self.message;
-               if(t.enemy == self)
-                       break;
-       }
-
-       // shootable, or triggered doors just needed the owner/enemy links,
-       // they don't spawn a field
-
-       if (self.health)
-               return;
-       IFTARGETED
-               return;
-       if (self.items)
-               return;
-
-       self.trigger_field = spawn_field(cmins, cmaxs);
-}
-
-
-/*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
-if two doors touch, they are assumed to be connected and operate as a unit.
-
-TOGGLE causes the door to wait in both the start and end states for a trigger event.
-
-START_OPEN causes the door to move to its destination when spawned, and operate in reverse.  It is used to temporarily or permanently close off an area when triggered (not useful for touch or takedamage doors).
-
-GOLD_KEY causes the door to open only if the activator holds a gold key.
-
-SILVER_KEY causes the door to open only if the activator holds a silver key.
-
-"message"      is printed when the door is touched if it is a trigger door and it hasn't been fired yet
-"angle"                determines the opening direction
-"targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
-"health"       if set, door must be shot open
-"speed"                movement speed (100 default)
-"wait"         wait before returning (3 default, -1 = never return)
-"lip"          lip remaining at end of move (8 default)
-"dmg"          damage to inflict when blocked (2 default)
-"sounds"
-0)     no sound
-1)     stone
-2)     base
-3)     stone chain
-4)     screechy metal
-FIXME: only one sound set available at the time being
-
-*/
-
-void door_init_startopen()
-{
-       setorigin (self, self.pos2);
-       self.pos2 = self.pos1;
-       self.pos1 = self.origin;
-}
-
-void door_reset()
-{
-       setorigin(self, self.pos1);
-       self.velocity = '0 0 0';
-       self.state = STATE_BOTTOM;
-       self.think = func_null;
-       self.nextthink = 0;
-}
-
-// spawnflags require key (for now only func_door)
-const float SPAWNFLAGS_GOLD_KEY = 8;
-const float SPAWNFLAGS_SILVER_KEY = 16;
-void spawnfunc_func_door()
-{
-       // Quake 1 keys compatibility
-       if (self.spawnflags & SPAWNFLAGS_GOLD_KEY)
-               self.itemkeys |= ITEM_KEY_BIT(0);
-       if (self.spawnflags & SPAWNFLAGS_SILVER_KEY)
-               self.itemkeys |= ITEM_KEY_BIT(1);
-
-       //if (!self.deathtype) // map makers can override this
-       //      self.deathtype = " got in the way";
-       SetMovedir ();
-
-       self.max_health = self.health;
-       if (!InitMovingBrushTrigger())
-               return;
-       self.effects |= EF_LOWPRECISION;
-       self.classname = "door";
-
-       self.blocked = door_blocked;
-       self.use = door_use;
-
-       // FIXME: undocumented flag 8, originally (Q1) GOLD_KEY
-       // if(self.spawnflags & 8)
-       //      self.dmg = 10000;
-
-    if(self.dmg && (self.message == ""))
-               self.message = "was squished";
-    if(self.dmg && (self.message2 == ""))
-               self.message2 = "was squished by";
-
-       if (self.sounds > 0)
-       {
-               precache_sound ("plats/medplat1.wav");
-               precache_sound ("plats/medplat2.wav");
-               self.noise2 = "plats/medplat1.wav";
-               self.noise1 = "plats/medplat2.wav";
-       }
-
-       if (!self.speed)
-               self.speed = 100;
-       if (!self.wait)
-               self.wait = 3;
-       if (!self.lip)
-               self.lip = 8;
-
-       self.pos1 = self.origin;
-       self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
-
-// DOOR_START_OPEN is to allow an entity to be lighted in the closed position
-// but spawn in the open position
-       if (self.spawnflags & DOOR_START_OPEN)
-               InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
-
-       self.state = STATE_BOTTOM;
-
-       if (self.health)
-       {
-               self.takedamage = DAMAGE_YES;
-               self.event_damage = door_damage;
-       }
-
-       if (self.items)
-               self.wait = -1;
-
-       self.touch = door_touch;
-
-// LinkDoors can't be done until all of the doors have been spawned, so
-// the sizes can be detected properly.
-       InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
-
-       self.reset = door_reset;
-}
-
-/*QUAKED spawnfunc_func_door_rotating (0 .5 .8) ? START_OPEN BIDIR DOOR_DONT_LINK BIDIR_IN_DOWN x TOGGLE X_AXIS Y_AXIS
-if two doors touch, they are assumed to be connected and operate as a unit.
-
-TOGGLE causes the door to wait in both the start and end states for a trigger event.
-
-BIDIR makes the door work bidirectional, so that the opening direction is always away from the requestor.
-The usage of bidirectional doors requires two manually instantiated triggers (trigger_multiple), the one to open it in the other direction
-must have set trigger_reverse to 1.
-BIDIR_IN_DOWN will the door prevent from reopening while closing if it is triggered from the other side.
-
-START_OPEN causes the door to move to its destination when spawned, and operate in reverse.  It is used to temporarily or permanently close off an area when triggered (not usefull for touch or takedamage doors).
-
-"message"      is printed when the door is touched if it is a trigger door and it hasn't been fired yet
-"angle"                determines the destination angle for opening. negative values reverse the direction.
-"targetname"    if set, no touch field will be spawned and a remote button or trigger field activates the door.
-"health"       if set, door must be shot open
-"speed"                movement speed (100 default)
-"wait"         wait before returning (3 default, -1 = never return)
-"dmg"          damage to inflict when blocked (2 default)
-"sounds"
-0)     no sound
-1)     stone
-2)     base
-3)     stone chain
-4)     screechy metal
-FIXME: only one sound set available at the time being
-*/
-
-void door_rotating_reset()
-{
-       self.angles = self.pos1;
-       self.avelocity = '0 0 0';
-       self.state = STATE_BOTTOM;
-       self.think = func_null;
-       self.nextthink = 0;
-}
-
-void door_rotating_init_startopen()
-{
-       self.angles = self.movedir;
-       self.pos2 = '0 0 0';
-       self.pos1 = self.movedir;
-}
-
-
-void spawnfunc_func_door_rotating()
-{
-
-       //if (!self.deathtype) // map makers can override this
-       //      self.deathtype = " got in the way";
-
-       // I abuse "movedir" for denoting the axis for now
-       if (self.spawnflags & 64) // X (untested)
-               self.movedir = '0 0 1';
-       else if (self.spawnflags & 128) // Y (untested)
-               self.movedir = '1 0 0';
-       else // Z
-               self.movedir = '0 1 0';
-
-       if (self.angles.y ==0) self.angles_y = 90;
-
-       self.movedir = self.movedir * self.angles.y;
-       self.angles = '0 0 0';
-
-       self.max_health = self.health;
-       self.avelocity = self.movedir;
-       if (!InitMovingBrushTrigger())
-               return;
-       self.velocity = '0 0 0';
-       //self.effects |= EF_LOWPRECISION;
-       self.classname = "door_rotating";
-
-       self.blocked = door_blocked;
-       self.use = door_use;
-
-    if(self.spawnflags & 8)
-        self.dmg = 10000;
-
-    if(self.dmg && (self.message == ""))
-               self.message = "was squished";
-    if(self.dmg && (self.message2 == ""))
-               self.message2 = "was squished by";
-
-    if (self.sounds > 0)
-       {
-               precache_sound ("plats/medplat1.wav");
-               precache_sound ("plats/medplat2.wav");
-               self.noise2 = "plats/medplat1.wav";
-               self.noise1 = "plats/medplat2.wav";
-       }
-
-       if (!self.speed)
-               self.speed = 50;
-       if (!self.wait)
-               self.wait = 1;
-       self.lip = 0; // self.lip is used to remember reverse opening direction for door_rotating
-
-       self.pos1 = '0 0 0';
-       self.pos2 = self.movedir;
-
-// DOOR_START_OPEN is to allow an entity to be lighted in the closed position
-// but spawn in the open position
-       if (self.spawnflags & DOOR_START_OPEN)
-               InitializeEntity(self, door_rotating_init_startopen, INITPRIO_SETLOCATION);
-
-       self.state = STATE_BOTTOM;
-
-       if (self.health)
-       {
-               self.takedamage = DAMAGE_YES;
-               self.event_damage = door_damage;
-       }
-
-       if (self.items)
-               self.wait = -1;
-
-       self.touch = door_touch;
-
-// LinkDoors can't be done until all of the doors have been spawned, so
-// the sizes can be detected properly.
-       InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
-
-       self.reset = door_rotating_reset;
-}
-
-/*
-=============================================================================
-
-SECRET DOORS
-
-=============================================================================
-*/
-
-void() fd_secret_move1;
-void() fd_secret_move2;
-void() fd_secret_move3;
-void() fd_secret_move4;
-void() fd_secret_move5;
-void() fd_secret_move6;
-void() fd_secret_done;
-
-const float SECRET_OPEN_ONCE = 1;              // stays open
-const float SECRET_1ST_LEFT = 2;               // 1st move is left of arrow
-const float SECRET_1ST_DOWN = 4;               // 1st move is down from arrow
-const float SECRET_NO_SHOOT = 8;               // only opened by trigger
-const float SECRET_YES_SHOOT = 16;     // shootable even if targeted
-
-void fd_secret_use()
-{
-       float temp;
-       string message_save;
-
-       self.health = 10000;
-       self.bot_attack = true;
-
-       // exit if still moving around...
-       if (self.origin != self.oldorigin)
-               return;
-
-       message_save = self.message;
-       self.message = ""; // no more message
-       SUB_UseTargets();                               // fire all targets / killtargets
-       self.message = message_save;
-
-       self.velocity = '0 0 0';
-
-       // Make a sound, wait a little...
-
-       if (self.noise1 != "")
-               sound(self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
-       self.nextthink = self.ltime + 0.1;
-
-       temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
-       makevectors(self.mangle);
-
-       if (!self.t_width)
-       {
-               if (self.spawnflags & SECRET_1ST_DOWN)
-                       self.t_width = fabs(v_up * self.size);
-               else
-                       self.t_width = fabs(v_right * self.size);
-       }
-
-       if (!self.t_length)
-               self.t_length = fabs(v_forward * self.size);
-
-       if (self.spawnflags & SECRET_1ST_DOWN)
-               self.dest1 = self.origin - v_up * self.t_width;
-       else
-               self.dest1 = self.origin + v_right * (self.t_width * temp);
-
-       self.dest2 = self.dest1 + v_forward * self.t_length;
-       SUB_CalcMove(self.dest1, TSPEED_LINEAR, self.speed, fd_secret_move1);
-       if (self.noise2 != "")
-               sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
-}
-
-void fd_secret_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
-{
-       fd_secret_use();
-}
-
-// Wait after first movement...
-void fd_secret_move1()
-{
-       self.nextthink = self.ltime + 1.0;
-       self.think = fd_secret_move2;
-       if (self.noise3 != "")
-               sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
-}
-
-// Start moving sideways w/sound...
-void fd_secret_move2()
-{
-       if (self.noise2 != "")
-               sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
-       SUB_CalcMove(self.dest2, TSPEED_LINEAR, self.speed, fd_secret_move3);
-}
-
-// Wait here until time to go back...
-void fd_secret_move3()
-{
-       if (self.noise3 != "")
-               sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
-       if (!(self.spawnflags & SECRET_OPEN_ONCE))
-       {
-               self.nextthink = self.ltime + self.wait;
-               self.think = fd_secret_move4;
-       }
-}
-
-// Move backward...
-void fd_secret_move4()
-{
-       if (self.noise2 != "")
-               sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
-       SUB_CalcMove(self.dest1, TSPEED_LINEAR, self.speed, fd_secret_move5);
-}
-
-// Wait 1 second...
-void fd_secret_move5()
-{
-       self.nextthink = self.ltime + 1.0;
-       self.think = fd_secret_move6;
-       if (self.noise3 != "")
-               sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
-}
-
-void fd_secret_move6()
-{
-       if (self.noise2 != "")
-               sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
-       SUB_CalcMove(self.oldorigin, TSPEED_LINEAR, self.speed, fd_secret_done);
-}
-
-void fd_secret_done()
-{
-       if (self.spawnflags&SECRET_YES_SHOOT)
-       {
-               self.health = 10000;
-               self.takedamage = DAMAGE_YES;
-               //self.th_pain = fd_secret_use;
-       }
-       if (self.noise3 != "")
-               sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
-}
-
-void secret_blocked()
-{
-       if (time < self.attack_finished_single)
-               return;
-       self.attack_finished_single = time + 0.5;
-       //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
-}
-
-/*
-==============
-secret_touch
-
-Prints messages
-================
-*/
-void secret_touch()
-{
-       if (!other.iscreature)
-               return;
-       if (self.attack_finished_single > time)
-               return;
-
-       self.attack_finished_single = time + 2;
-
-       if (self.message)
-       {
-               if (IS_CLIENT(other))
-                       centerprint(other, self.message);
-               play2(other, "misc/talk.wav");
-       }
-}
-
-void secret_reset()
-{
-       if (self.spawnflags&SECRET_YES_SHOOT)
-       {
-               self.health = 10000;
-               self.takedamage = DAMAGE_YES;
-       }
-       setorigin(self, self.oldorigin);
-       self.think = func_null;
-       self.nextthink = 0;
-}
-
-/*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
-Basic secret door. Slides back, then to the side. Angle determines direction.
-wait  = # of seconds before coming back
-1st_left = 1st move is left of arrow
-1st_down = 1st move is down from arrow
-always_shoot = even if targeted, keep shootable
-t_width = override WIDTH to move back (or height if going down)
-t_length = override LENGTH to move sideways
-"dmg"          damage to inflict when blocked (2 default)
-
-If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
-"sounds"
-1) medieval
-2) metal
-3) base
-*/
-
-void spawnfunc_func_door_secret()
-{
-       /*if (!self.deathtype) // map makers can override this
-               self.deathtype = " got in the way";*/
-
-       if (!self.dmg)
-               self.dmg = 2;
-
-       // Magic formula...
-       self.mangle = self.angles;
-       self.angles = '0 0 0';
-       self.classname = "door";
-       if (!InitMovingBrushTrigger())
-               return;
-       self.effects |= EF_LOWPRECISION;
-
-       self.touch = secret_touch;
-       self.blocked = secret_blocked;
-       self.speed = 50;
-       self.use = fd_secret_use;
-       IFTARGETED
-       {
-       }
-       else
-               self.spawnflags |= SECRET_YES_SHOOT;
-
-       if(self.spawnflags&SECRET_YES_SHOOT)
-       {
-               self.health = 10000;
-               self.takedamage = DAMAGE_YES;
-               self.event_damage = fd_secret_damage;
-       }
-       self.oldorigin = self.origin;
-       if (!self.wait)
-               self.wait = 5;          // 5 seconds before closing
-
-       self.reset = secret_reset;
-       secret_reset();
-}
-
-/*QUAKED spawnfunc_func_fourier (0 .5 .8) ?
-Brush model that moves in a pattern of added up sine waves, can be used e.g. for circular motions.
-netname: list of <frequencymultiplier> <phase> <x> <y> <z> quadruples, separated by spaces; note that phase 0 represents a sine wave, and phase 0.25 a cosine wave (by default, it uses 1 0 0 0 1, to match func_bobbing's defaults
-speed: how long one cycle of frequency multiplier 1 in seconds (default 4)
-height: amplitude modifier (default 32)
-phase: cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
-noise: path/name of looping .wav file to play.
-dmg: Do this mutch dmg every .dmgtime intervall when blocked
-dmgtime: See above.
-*/
-
-void func_fourier_controller_think()
-{
-       vector v;
-       float n, i, t;
-
-       self.nextthink = time + 0.1;
-       if(self.owner.active != ACTIVE_ACTIVE)
-       {
-               self.owner.velocity = '0 0 0';
-               return;
-       }
-
-
-       n = floor((tokenize_console(self.owner.netname)) / 5);
-       t = self.nextthink * self.owner.cnt + self.owner.phase * 360;
-
-       v = self.owner.destvec;
-
-       for(i = 0; i < n; ++i)
-       {
-               makevectors((t * stof(argv(i*5)) + stof(argv(i*5+1)) * 360) * '0 1 0');
-               v = v + ('1 0 0' * stof(argv(i*5+2)) + '0 1 0' * stof(argv(i*5+3)) + '0 0 1' * stof(argv(i*5+4))) * self.owner.height * v_forward.y;
-       }
-
-       if(self.owner.classname == "func_fourier") // don't brake stuff if the func_fourier was killtarget'ed
-               // * 10 so it will arrive in 0.1 sec
-               self.owner.velocity = (v - self.owner.origin) * 10;
-}
-
-void spawnfunc_func_fourier()
-{
-       entity controller;
-       if (self.noise != "")
-       {
-               precache_sound(self.noise);
-               soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
-       }
-
-       if (!self.speed)
-               self.speed = 4;
-       if (!self.height)
-               self.height = 32;
-       self.destvec = self.origin;
-       self.cnt = 360 / self.speed;
-
-       self.blocked = generic_plat_blocked;
-       if(self.dmg && (self.message == ""))
-               self.message = " was squished";
-    if(self.dmg && (self.message2 == ""))
-               self.message2 = "was squished by";
-       if(self.dmg && (!self.dmgtime))
-               self.dmgtime = 0.25;
-       self.dmgtime2 = time;
-
-       if(self.netname == "")
-               self.netname = "1 0 0 0 1";
-
-       if (!InitMovingBrushTrigger())
-               return;
-
-       self.active = ACTIVE_ACTIVE;
-
-       // wait for targets to spawn
-       controller = spawn();
-       controller.classname = "func_fourier_controller";
-       controller.owner = self;
-       controller.nextthink = time + 1;
-       controller.think = func_fourier_controller_think;
-       self.nextthink = self.ltime + 999999999;
-       self.think = SUB_NullThink; // for PushMove
-
-       // Savage: Reduce bandwith, critical on e.g. nexdm02
-       self.effects |= EF_LOWPRECISION;
-
-       // TODO make a reset function for this one
-}
-
-// reusing some fields havocbots declared
-.entity wp00, wp01, wp02, wp03;
-
-.float targetfactor, target2factor, target3factor, target4factor;
-.vector targetnormal, target2normal, target3normal, target4normal;
-
-vector func_vectormamamam_origin(entity o, float t)
-{
-       vector v, p;
-       float f;
-       entity e;
-
-       f = o.spawnflags;
-       v = '0 0 0';
-
-       e = o.wp00;
-       if(e)
-       {
-               p = e.origin + t * e.velocity;
-               if(f & 1)
-                       v = v + (p * o.targetnormal) * o.targetnormal * o.targetfactor;
-               else
-                       v = v + (p - (p * o.targetnormal) * o.targetnormal) * o.targetfactor;
-       }
-
-       e = o.wp01;
-       if(e)
-       {
-               p = e.origin + t * e.velocity;
-               if(f & 2)
-                       v = v + (p * o.target2normal) * o.target2normal * o.target2factor;
-               else
-                       v = v + (p - (p * o.target2normal) * o.target2normal) * o.target2factor;
-       }
-
-       e = o.wp02;
-       if(e)
-       {
-               p = e.origin + t * e.velocity;
-               if(f & 4)
-                       v = v + (p * o.target3normal) * o.target3normal * o.target3factor;
-               else
-                       v = v + (p - (p * o.target3normal) * o.target3normal) * o.target3factor;
-       }
-
-       e = o.wp03;
-       if(e)
-       {
-               p = e.origin + t * e.velocity;
-               if(f & 8)
-                       v = v + (p * o.target4normal) * o.target4normal * o.target4factor;
-               else
-                       v = v + (p - (p * o.target4normal) * o.target4normal) * o.target4factor;
-       }
-
-       return v;
-}
-
-void func_vectormamamam_controller_think()
-{
-       self.nextthink = time + 0.1;
-
-       if(self.owner.active != ACTIVE_ACTIVE)
-       {
-               self.owner.velocity = '0 0 0';
-               return;
-       }
-
-       if(self.owner.classname == "func_vectormamamam") // don't brake stuff if the func_vectormamamam was killtarget'ed
-               self.owner.velocity = (self.owner.destvec + func_vectormamamam_origin(self.owner, 0.1) - self.owner.origin) * 10;
-}
-
-void func_vectormamamam_findtarget()
-{
-       if(self.target != "")
-               self.wp00 = find(world, targetname, self.target);
-
-       if(self.target2 != "")
-               self.wp01 = find(world, targetname, self.target2);
-
-       if(self.target3 != "")
-               self.wp02 = find(world, targetname, self.target3);
-
-       if(self.target4 != "")
-               self.wp03 = find(world, targetname, self.target4);
-
-       if(!self.wp00 && !self.wp01 && !self.wp02 && !self.wp03)
-               objerror("No reference entity found, so there is nothing to move. Aborting.");
-
-       self.destvec = self.origin - func_vectormamamam_origin(self, 0);
-
-       entity controller;
-       controller = spawn();
-       controller.classname = "func_vectormamamam_controller";
-       controller.owner = self;
-       controller.nextthink = time + 1;
-       controller.think = func_vectormamamam_controller_think;
-}
-
-void spawnfunc_func_vectormamamam()
-{
-       if (self.noise != "")
-       {
-               precache_sound(self.noise);
-               soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
-       }
-
-       if(!self.targetfactor)
-               self.targetfactor = 1;
-
-       if(!self.target2factor)
-               self.target2factor = 1;
-
-       if(!self.target3factor)
-               self.target3factor = 1;
-
-       if(!self.target4factor)
-               self.target4factor = 1;
-
-       if(vlen(self.targetnormal))
-               self.targetnormal = normalize(self.targetnormal);
-
-       if(vlen(self.target2normal))
-               self.target2normal = normalize(self.target2normal);
-
-       if(vlen(self.target3normal))
-               self.target3normal = normalize(self.target3normal);
-
-       if(vlen(self.target4normal))
-               self.target4normal = normalize(self.target4normal);
-
-       self.blocked = generic_plat_blocked;
-       if(self.dmg && (self.message == ""))
-               self.message = " was squished";
-    if(self.dmg && (self.message == ""))
-               self.message2 = "was squished by";
-       if(self.dmg && (!self.dmgtime))
-               self.dmgtime = 0.25;
-       self.dmgtime2 = time;
-
-       if(self.netname == "")
-               self.netname = "1 0 0 0 1";
-
-       if (!InitMovingBrushTrigger())
-               return;
-
-       // wait for targets to spawn
-       self.nextthink = self.ltime + 999999999;
-       self.think = SUB_NullThink; // for PushMove
-
-       // Savage: Reduce bandwith, critical on e.g. nexdm02
-       self.effects |= EF_LOWPRECISION;
-
-       self.active = ACTIVE_ACTIVE;
-
-       InitializeEntity(self, func_vectormamamam_findtarget, INITPRIO_FINDTARGET);
-}
-
-void conveyor_think()
-{
-       entity e;
-
-       // set myself as current conveyor where possible
-       for(e = world; (e = findentity(e, conveyor, self)); )
-               e.conveyor = world;
-
-       if(self.state)
-       {
-               for(e = findradius((self.absmin + self.absmax) * 0.5, vlen(self.absmax - self.absmin) * 0.5 + 1); e; e = e.chain)
-                       if(!e.conveyor.state)
-                               if(isPushable(e))
-                               {
-                                       vector emin = e.absmin;
-                                       vector emax = e.absmax;
-                                       if(self.solid == SOLID_BSP)
-                                       {
-                                               emin -= '1 1 1';
-                                               emax += '1 1 1';
-                                       }
-                                       if(boxesoverlap(emin, emax, self.absmin, self.absmax)) // quick
-                                               if(WarpZoneLib_BoxTouchesBrush(emin, emax, self, e)) // accurate
-                                                       e.conveyor = self;
-                               }
-
-               for(e = world; (e = findentity(e, conveyor, self)); )
-               {
-                       if(IS_CLIENT(e)) // doing it via velocity has quite some advantages
-                               continue; // done in SV_PlayerPhysics
-
-                       setorigin(e, e.origin + self.movedir * sys_frametime);
-                       move_out_of_solid(e);
-                       UpdateCSQCProjectile(e);
-                       /*
-                       // stupid conveyor code
-                       tracebox(e.origin, e.mins, e.maxs, e.origin + self.movedir * sys_frametime, MOVE_NORMAL, e);
-                       if(trace_fraction > 0)
-                               setorigin(e, trace_endpos);
-                       */
-               }
-       }
-
-       self.nextthink = time;
-}
-
-void conveyor_use()
-{
-       self.state = !self.state;
-}
-
-void conveyor_reset()
-{
-       self.state = (self.spawnflags & 1);
-}
-
-void conveyor_init()
-{
-       if (!self.speed)
-               self.speed = 200;
-       self.movedir = self.movedir * self.speed;
-       self.think = conveyor_think;
-       self.nextthink = time;
-       IFTARGETED
-       {
-               self.use = conveyor_use;
-               self.reset = conveyor_reset;
-               conveyor_reset();
-       }
-       else
-               self.state = 1;
-}
-
-void spawnfunc_trigger_conveyor()
-{
-       SetMovedir();
-       EXACTTRIGGER_INIT;
-       conveyor_init();
-}
-
-void spawnfunc_func_conveyor()
-{
-       SetMovedir();
-       InitMovingBrushTrigger();
-       self.movetype = MOVETYPE_NONE;
-       conveyor_init();
-}
diff --git a/qcsrc/server/t_swamp.qc b/qcsrc/server/t_swamp.qc
deleted file mode 100644 (file)
index b63e7ff..0000000
+++ /dev/null
@@ -1,111 +0,0 @@
-#if defined(CSQC)
-#elif defined(MENUQC)
-#elif defined(SVQC)
-       #include "../dpdefs/progsdefs.qh"
-    #include "../warpzonelib/util_server.qh"
-    #include "../common/weapons/weapons.qh"
-    #include "defs.qh"
-    #include "../common/deathtypes.qh"
-#endif
-
-/*
-*              t_swamp.c
-*              Adds spawnfunc_trigger_swamp and suppoart routines for xonotic 1.2.1+
-*              Author tZork (Jakob MG)
-*              jakob@games43.se
-*              2005 11 29
-*/
-
-.float swamp_interval; //Hurt players in swamp with this interval
-.float swamp_slowdown; //Players in swamp get slowd down by this mutch 0-1 is slowdown 1-~ is speedup (!?)
-.entity swampslug;
-
-void spawnfunc_trigger_swamp(void);
-void swamp_touch(void);
-void swampslug_think();
-
-
-/*
-* Uses a entity calld swampslug to handle players in the swamp
-* It works like this: When the plyer enters teh swamp the spawnfunc_trigger_swamp
-* attaches a new "swampslug" to the player. As long as the plyer is inside
-* the swamp the swamp gives the slug new health. But the slug slowly kills itself
-* so when the player goes outside the swamp, it dies and releases the player from the
-* swamps curses (dmg/slowdown)
-*
-* I do it this way becuz there is no "untouch" event.
-*
-* --NOTE--
-* THE ACCTUAL slowdown is done in cl_physics.c on line 57-60
-* --NOTE--
-*/
-void swampslug_think(void)
-{
-       //Slowly kill the slug
-       self.health = self.health - 1;
-
-       //Slug dead? then remove curses.
-       if(self.health <= 0) {
-               self.owner.in_swamp = 0;
-               remove(self);
-               //centerprint(self.owner,"Killing slug...\n");
-               return;
-       }
-
-       // Slug still alive, so we are still in the swamp
-       // Or we have exited it very recently.
-       // Do the damage and renew the timer.
-       Damage (self.owner, self, self, self.dmg, DEATH_SWAMP, other.origin, '0 0 0');
-
-       self.nextthink = time + self.swamp_interval;
-}
-
-void swamp_touch(void)
-{
-       // If whatever thats touching the swamp is not a player
-       // or if its a dead player, just dont care abt it.
-       if(!IS_PLAYER(other) || other.deadflag != DEAD_NO)
-               return;
-
-       EXACTTRIGGER_TOUCH;
-
-       // Chech if player alredy got a swampslug.
-       if(other.in_swamp != 1) {
-               // If not attach one.
-               //centerprint(other,"Entering swamp!\n");
-               other.swampslug = spawn();
-               other.swampslug.health = 2;
-               other.swampslug.think = swampslug_think;
-               other.swampslug.nextthink = time;
-               other.swampslug.owner = other;
-               other.swampslug.dmg = self.dmg;
-               other.swampslug.swamp_interval = self.swamp_interval;
-               other.swamp_slowdown = self.swamp_slowdown;
-               other.in_swamp = 1;
-               return;
-       }
-
-       //other.in_swamp = 1;
-
-       //Revitalize players swampslug
-       other.swampslug.health = 2;
-}
-
-/*QUAKED spawnfunc_trigger_swamp (.5 .5 .5) ?
-Players gettin into the swamp will
-get slowd down and damaged
-*/
-void spawnfunc_trigger_swamp(void)
-{
-       // Init stuff
-       EXACTTRIGGER_INIT;
-       self.touch = swamp_touch;
-
-       // Setup default keys, if missing
-       if(self.dmg <= 0)
-               self.dmg = 5;
-       if(self.swamp_interval <= 0)
-               self.swamp_interval = 1;
-       if(self.swamp_slowdown <= 0)
-               self.swamp_slowdown = 0.5;
-}
diff --git a/qcsrc/server/t_teleporters.qc b/qcsrc/server/t_teleporters.qc
deleted file mode 100644 (file)
index 46df0eb..0000000
+++ /dev/null
@@ -1,348 +0,0 @@
-#include "t_teleporters.qh"
-
-#if defined(CSQC)
-#elif defined(MENUQC)
-#elif defined(SVQC)
-    #include "../warpzonelib/common.qh"
-    #include "../warpzonelib/util_server.qh"
-    #include "../warpzonelib/server.qh"
-    #include "../common/constants.qh"
-    #include "../common/util.qh"
-    #include "weapons/csqcprojectile.qh"
-    #include "autocvars.qh"
-    #include "constants.qh"
-    #include "defs.qh"
-    #include "../common/deathtypes.qh"
-    #include "tturrets/include/turrets_early.qh"
-    #include "vehicles/vehicles_def.qh"
-    #include "../common/mapinfo.qh"
-    #include "anticheat.qh"
-#endif
-
-void trigger_teleport_use()
-{
-       if(teamplay)
-               self.team = activator.team;
-}
-
-float check_tdeath(entity player, vector org, vector telefragmin, vector telefragmax)
-{
-       if (IS_PLAYER(player) && player.health >= 1)
-       {
-               TDEATHLOOP(org)
-               {
-                       if (!(teamplay && autocvar_g_telefrags_teamplay && head.team == player.team))
-                               if(IS_PLAYER(head))
-                                       if(head.health >= 1)
-                                               return 1;
-               }
-       }
-       return 0;
-}
-
-void tdeath(entity player, entity teleporter, entity telefragger, vector telefragmin, vector telefragmax)
-{
-       TDEATHLOOP(player.origin)
-       {
-               if (IS_PLAYER(player) && player.health >= 1)
-               {
-                       if (!(teamplay && autocvar_g_telefrags_teamplay && head.team == player.team))
-                       {
-                               if(IS_PLAYER(head))
-                                       if(head.health >= 1)
-                                               ++tdeath_hit;
-                               Damage (head, teleporter, telefragger, 10000, DEATH_TELEFRAG, head.origin, '0 0 0');
-                       }
-               }
-               else // dead bodies and monsters gib themselves instead of telefragging
-                       Damage (telefragger, teleporter, telefragger, 10000, DEATH_TELEFRAG, telefragger.origin, '0 0 0');
-       }
-}
-
-void spawn_tdeath(vector v0, entity e, vector v)
-{
-       tdeath(e, e, e, '0 0 0', '0 0 0');
-}
-
-void TeleportPlayer(entity teleporter, entity player, vector to, vector to_angles, vector to_velocity, vector telefragmin, vector telefragmax, float tflags)
-{
-       entity telefragger;
-       vector from;
-
-       if(teleporter.owner)
-               telefragger = teleporter.owner;
-       else
-               telefragger = player;
-
-       makevectors (to_angles);
-
-       if(player.teleportable == TELEPORT_NORMAL) // don't play sounds or show particles for anything that isn't a player, maybe change later to block only observers
-       {
-               if(self.pushltime < time) // only show one teleport effect per teleporter per 0.2 seconds, for better fps
-               {
-                       if(tflags & TELEPORT_FLAG_SOUND)
-                               sound (player, CH_TRIGGER, "misc/teleport.wav", VOL_BASE, ATTEN_NORM);
-                       if(tflags & TELEPORT_FLAG_PARTICLES)
-                       {
-                               pointparticles(particleeffectnum("teleport"), player.origin, '0 0 0', 1);
-                               pointparticles(particleeffectnum("teleport"), to + v_forward * 32, '0 0 0', 1);
-                       }
-                       self.pushltime = time + 0.2;
-               }
-       }
-
-       // Relocate the player
-       // assuming to allows PL_MIN to PL_MAX box and some more
-       from = player.origin;
-       setorigin (player, to);
-       player.oldorigin = to; // don't undo the teleport by unsticking
-       player.angles = to_angles;
-       player.fixangle = true;
-       player.velocity = to_velocity;
-       BITXOR_ASSIGN(player.effects, EF_TELEPORT_BIT);
-
-       makevectors(player.angles);
-       Reset_ArcBeam(player, v_forward);
-       UpdateCSQCProjectileAfterTeleport(player);
-
-       if(IS_PLAYER(player))
-       {
-               if(tflags & TELEPORT_FLAG_TDEATH)
-                       if(player.takedamage && player.deadflag == DEAD_NO && !g_race && !g_cts && (autocvar_g_telefrags || (tflags & TELEPORT_FLAG_FORCE_TDEATH)))
-                               tdeath(player, teleporter, telefragger, telefragmin, telefragmax);
-
-               // player no longer is on ground
-               player.flags &= ~FL_ONGROUND;
-
-               // reset tracking of oldvelocity for impact damage (sudden velocity changes)
-               player.oldvelocity = player.velocity;
-
-               // reset tracking of who pushed you into a hazard (for kill credit)
-               if(teleporter.owner)
-               {
-                       player.pusher = teleporter.owner;
-                       player.pushltime = time + autocvar_g_maxpushtime;
-                       player.istypefrag = player.BUTTON_CHAT;
-               }
-               else
-               {
-                       player.pushltime = 0;
-                       player.istypefrag = 0;
-               }
-
-               player.lastteleporttime = time;
-       }
-}
-
-entity Simple_TeleportPlayer(entity teleporter, entity player)
-{
-       vector locout;
-       entity e;
-       float p;
-
-       // Find the output teleporter
-       if(teleporter.enemy)
-       {
-               e = teleporter.enemy;
-       }
-       else
-       {
-               RandomSelection_Init();
-               for(e = world; (e = find(e, targetname, teleporter.target)); )
-               {
-                       p = 1;
-                       if(autocvar_g_telefrags_avoid)
-                       {
-                               locout = e.origin + '0 0 1' * (1 - player.mins.z - 24);
-                               if(check_tdeath(player, locout, '0 0 0', '0 0 0'))
-                                       p = 0;
-                       }
-                       RandomSelection_Add(e, 0, string_null, (e.cnt ? e.cnt : 1), p);
-               }
-               e = RandomSelection_chosen_ent;
-       }
-
-       if(!e) { sprint(player, "Teleport destination vanished. Sorry... please complain to the mapper.\n"); }
-
-       makevectors(e.mangle);
-
-       if(e.speed)
-               if(vlen(player.velocity) > e.speed)
-                       player.velocity = normalize(player.velocity) * max(0, e.speed);
-
-       if(autocvar_g_teleport_maxspeed)
-               if(vlen(player.velocity) > autocvar_g_teleport_maxspeed)
-                       player.velocity = normalize(player.velocity) * max(0, autocvar_g_teleport_maxspeed);
-
-       locout = e.origin + '0 0 1' * (1 - player.mins.z - 24);
-       TeleportPlayer(teleporter, player, locout, e.mangle, v_forward * vlen(player.velocity), '0 0 0', '0 0 0', TELEPORT_FLAGS_TELEPORTER);
-
-       return e;
-}
-
-void Teleport_Touch (void)
-{
-       entity oldself;
-       string s;
-
-       if (self.active != ACTIVE_ACTIVE)
-               return;
-
-       if (!other.teleportable)
-               return;
-
-       if(other.vehicle)
-       if(!other.vehicle.teleportable)
-               return;
-
-       if(other.turrcaps_flags & TFL_TURRCAPS_ISTURRET)
-               return;
-
-       if(other.deadflag != DEAD_NO)
-               return;
-
-       if(self.team)
-               if(((self.spawnflags & 4) == 0) == (self.team != other.team))
-                       return;
-
-       EXACTTRIGGER_TOUCH;
-
-       if(IS_PLAYER(other))
-               RemoveGrapplingHook(other);
-
-       entity e;
-       e = Simple_TeleportPlayer(self, other);
-
-       activator = other;
-       s = self.target; self.target = string_null;
-       SUB_UseTargets();
-       if (!self.target) self.target = s;
-
-       oldself = self;
-       self = e;
-       SUB_UseTargets();
-       self = oldself;
-}
-
-void spawnfunc_info_teleport_destination (void)
-{
-       self.classname = "info_teleport_destination";
-
-       self.mangle = self.angles;
-       self.angles = '0 0 0';
-
-       //setorigin (self, self.origin + '0 0 27');     // To fix a mappers' habit as old as Quake
-       setorigin (self, self.origin);
-
-       IFTARGETED
-       {
-       }
-       else
-               objerror ("^3Teleport destination without a targetname");
-}
-
-void spawnfunc_misc_teleporter_dest (void)
-{
-       spawnfunc_info_teleport_destination();
-}
-
-void spawnfunc_target_teleporter (void)
-{
-       spawnfunc_info_teleport_destination();
-}
-
-void teleport_findtarget (void)
-{
-       entity e;
-       float n;
-
-       n = 0;
-       for(e = world; (e = find(e, targetname, self.target)); )
-       {
-               ++n;
-               if(e.movetype == MOVETYPE_NONE)
-                       waypoint_spawnforteleporter(self, e.origin, 0);
-               if(e.classname != "info_teleport_destination")
-                       print("^3MAPPER ERROR: teleporter does target an invalid teleport destination entity. Angles will not work.\n");
-       }
-
-       if(n == 0)
-       {
-               // no dest!
-               objerror ("Teleporter with nonexistant target");
-               return;
-       }
-       else if(n == 1)
-       {
-               // exactly one dest - bots love that
-               self.enemy = find(e, targetname, self.target);
-       }
-       else
-       {
-               // have to use random selection every single time
-               self.enemy = world;
-       }
-
-       // now enable touch
-       self.touch = Teleport_Touch;
-}
-
-entity Teleport_Find(vector mi, vector ma)
-{
-       entity e;
-       for(e = world; (e = find(e, classname, "trigger_teleport")); )
-               if(WarpZoneLib_BoxTouchesBrush(mi, ma, e, world))
-                       return e;
-       return world;
-}
-
-void spawnfunc_trigger_teleport (void)
-{
-       self.angles = '0 0 0';
-
-       EXACTTRIGGER_INIT;
-
-       self.active = ACTIVE_ACTIVE;
-
-       self.use = trigger_teleport_use;
-
-       // this must be called to spawn the teleport waypoints for bots
-       InitializeEntity(self, teleport_findtarget, INITPRIO_FINDTARGET);
-
-       if (self.target == "")
-       {
-               objerror ("Teleporter with no target");
-               return;
-       }
-
-       self.teleport_next = teleport_first;
-       teleport_first = self;
-}
-
-void WarpZone_PostTeleportPlayer_Callback(entity pl)
-{
-       makevectors(pl.angles);
-       Reset_ArcBeam(pl, v_forward);
-       UpdateCSQCProjectileAfterTeleport(pl);
-       {
-               entity oldself = self;
-               self = pl;
-               anticheat_fixangle();
-               self = oldself;
-       }
-       // "disown" projectiles after teleport
-       if(pl.owner)
-       if(pl.owner == pl.realowner)
-       {
-               if(!(pl.flags & FL_PROJECTILE))
-                       print("A non-projectile got through a warpzone and its owner cleared. It's a ", pl.classname, ".\n");
-               pl.owner = world;
-       }
-       if(IS_PLAYER(pl))
-       {
-               // reset tracking of oldvelocity for impact damage (sudden velocity changes)
-               pl.oldvelocity = pl.velocity;
-               // reset teleport time tracking too (or multijump can cause insane speeds)
-               pl.lastteleporttime = time;
-       }
-}
diff --git a/qcsrc/server/t_teleporters.qh b/qcsrc/server/t_teleporters.qh
deleted file mode 100644 (file)
index 03e3c3f..0000000
+++ /dev/null
@@ -1,71 +0,0 @@
-#ifndef T_TELEPORTERS_H
-#define T_TELEPORTERS_H
-
-void trigger_teleport_use();
-
-#define TDEATHLOOP(o) \
-       entity head; \
-       vector deathmin; \
-       vector deathmax; \
-       float deathradius; \
-       deathmin = (o) + player.mins; \
-       deathmax = (o) + player.maxs; \
-       if(telefragmin != telefragmax) \
-       { \
-               if(deathmin.x > telefragmin.x) deathmin.x = telefragmin.x; \
-               if(deathmin.y > telefragmin.y) deathmin.y = telefragmin.y; \
-               if(deathmin.z > telefragmin.z) deathmin.z = telefragmin.z; \
-               if(deathmax.x < telefragmax.x) deathmax.x = telefragmax.x; \
-               if(deathmax.y < telefragmax.y) deathmax.y = telefragmax.y; \
-               if(deathmax.z < telefragmax.z) deathmax.z = telefragmax.z; \
-       } \
-       deathradius = max(vlen(deathmin), vlen(deathmax)); \
-       for(head = findradius(o, deathradius); head; head = head.chain) \
-               if(head != player) \
-                       if(head.takedamage) \
-                               if(boxesoverlap(deathmin, deathmax, head.absmin, head.absmax))
-
-
-float check_tdeath(entity player, vector org, vector telefragmin, vector telefragmax);
-float tdeath_hit;
-void tdeath(entity player, entity teleporter, entity telefragger, vector telefragmin, vector telefragmax);
-
-void spawn_tdeath(vector v0, entity e, vector v);
-
-.entity pusher;
-const float TELEPORT_FLAG_SOUND = 1;
-const float TELEPORT_FLAG_PARTICLES = 2;
-const float TELEPORT_FLAG_TDEATH = 4;
-const float TELEPORT_FLAG_FORCE_TDEATH = 8;
-
-#define TELEPORT_FLAGS_WARPZONE   0
-#define TELEPORT_FLAGS_PORTAL     (TELEPORT_FLAG_SOUND | TELEPORT_FLAG_PARTICLES | TELEPORT_FLAG_TDEATH | TELEPORT_FLAG_FORCE_TDEATH)
-#define TELEPORT_FLAGS_TELEPORTER (TELEPORT_FLAG_SOUND | TELEPORT_FLAG_PARTICLES | TELEPORT_FLAG_TDEATH)
-
-// types for .teleportable entity setting
-const float TELEPORT_NORMAL = 1; // play sounds/effects etc
-const float TELEPORT_SIMPLE = 2; // only do teleport, nothing special
-
-void Reset_ArcBeam(entity player, vector forward);
-void TeleportPlayer(entity teleporter, entity player, vector to, vector to_angles, vector to_velocity, vector telefragmin, vector telefragmax, float tflags);
-
-entity Simple_TeleportPlayer(entity teleporter, entity player);
-
-void Teleport_Touch (void);
-
-void spawnfunc_info_teleport_destination (void);
-
-void spawnfunc_misc_teleporter_dest (void);
-
-void spawnfunc_target_teleporter (void);
-
-void teleport_findtarget (void);
-
-entity Teleport_Find(vector mi, vector ma);
-
-entity teleport_first;
-.entity teleport_next;
-void spawnfunc_trigger_teleport (void);
-
-void WarpZone_PostTeleportPlayer_Callback(entity pl);
-#endif
diff --git a/qcsrc/server/target_music.qc b/qcsrc/server/target_music.qc
deleted file mode 100644 (file)
index 94bc92f..0000000
+++ /dev/null
@@ -1,144 +0,0 @@
-#if defined(CSQC)
-#elif defined(MENUQC)
-#elif defined(SVQC)
-       #include "../dpdefs/progsdefs.qh"
-    #include "../dpdefs/dpextensions.qh"
-    #include "../common/constants.qh"
-    #include "constants.qh"
-    #include "defs.qh"
-#endif
-
-.float lifetime;
-// values:
-//   volume
-//   noise
-//   targetname
-//   lifetime
-//   fade_time
-//   fade_rate
-// when triggered, the music is overridden for activator until lifetime (or forever, if lifetime is 0)
-// when targetname is not set, THIS ONE is default
-void target_music_sendto(float to, float is)
-{
-       WriteByte(to, SVC_TEMPENTITY);
-       WriteByte(to, TE_CSQC_TARGET_MUSIC);
-       WriteShort(to, num_for_edict(self));
-       WriteByte(to, self.volume * 255.0 * is);
-       WriteByte(to, self.fade_time * 16.0);
-       WriteByte(to, self.fade_rate * 16.0);
-       WriteByte(to, self.lifetime);
-       WriteString(to, self.noise);
-}
-void target_music_reset()
-{
-       if(self.targetname == "")
-               target_music_sendto(MSG_ALL, 1);
-}
-void target_music_use()
-{
-       if(!activator)
-               return;
-       if(IS_REAL_CLIENT(activator))
-       {
-               msg_entity = activator;
-               target_music_sendto(MSG_ONE, 1);
-       }
-       entity head;
-       FOR_EACH_SPEC(head) if(head.enemy == activator) { msg_entity = head; target_music_sendto(MSG_ONE, 1); }
-}
-void spawnfunc_target_music()
-{
-       self.use = target_music_use;
-       self.reset = target_music_reset;
-       if(!self.volume)
-               self.volume = 1;
-       if(self.targetname == "")
-               target_music_sendto(MSG_INIT, 1);
-       else
-               target_music_sendto(MSG_INIT, 0);
-}
-void TargetMusic_RestoreGame()
-{
-       for(self = world; (self = find(self, classname, "target_music")); )
-       {
-               if(self.targetname == "")
-                       target_music_sendto(MSG_INIT, 1);
-               else
-                       target_music_sendto(MSG_INIT, 0);
-       }
-}
-// values:
-//   volume
-//   noise
-//   targetname
-//   fade_time
-// spawnflags:
-//   1 = START_OFF
-// when triggered, it is disabled/enabled for everyone
-float trigger_music_SendEntity(entity to, float sf)
-{
-       WriteByte(MSG_ENTITY, ENT_CLIENT_TRIGGER_MUSIC);
-       sf &= ~0x80;
-       if(self.cnt)
-               sf |= 0x80;
-       WriteByte(MSG_ENTITY, sf);
-       if(sf & 4)
-       {
-               WriteCoord(MSG_ENTITY, self.origin.x);
-               WriteCoord(MSG_ENTITY, self.origin.y);
-               WriteCoord(MSG_ENTITY, self.origin.z);
-       }
-       if(sf & 1)
-       {
-               if(self.model != "null")
-               {
-                       WriteShort(MSG_ENTITY, self.modelindex);
-                       WriteCoord(MSG_ENTITY, self.mins.x);
-                       WriteCoord(MSG_ENTITY, self.mins.y);
-                       WriteCoord(MSG_ENTITY, self.mins.z);
-                       WriteCoord(MSG_ENTITY, self.maxs.x);
-                       WriteCoord(MSG_ENTITY, self.maxs.y);
-                       WriteCoord(MSG_ENTITY, self.maxs.z);
-               }
-               else
-               {
-                       WriteShort(MSG_ENTITY, 0);
-                       WriteCoord(MSG_ENTITY, self.maxs.x);
-                       WriteCoord(MSG_ENTITY, self.maxs.y);
-                       WriteCoord(MSG_ENTITY, self.maxs.z);
-               }
-               WriteByte(MSG_ENTITY, self.volume * 255.0);
-               WriteByte(MSG_ENTITY, self.fade_time * 16.0);
-               WriteByte(MSG_ENTITY, self.fade_rate * 16.0);
-               WriteString(MSG_ENTITY, self.noise);
-       }
-       return 1;
-}
-void trigger_music_reset()
-{
-       self.cnt = !(self.spawnflags & 1);
-       self.SendFlags |= 0x80;
-}
-void trigger_music_use()
-{
-       self.cnt = !self.cnt;
-       self.SendFlags |= 0x80;
-}
-void spawnfunc_trigger_music()
-{
-       if(self.model != "")
-               setmodel(self, self.model);
-       if(!self.volume)
-               self.volume = 1;
-       if(!self.modelindex)
-       {
-               setorigin(self, self.origin + self.mins);
-               setsize(self, '0 0 0', self.maxs - self.mins);
-       }
-       trigger_music_reset();
-
-       self.use = trigger_music_use;
-       self.reset = trigger_music_reset;
-
-       Net_LinkEntity(self, false, 0, trigger_music_SendEntity);
-}
diff --git a/qcsrc/server/target_spawn.qc b/qcsrc/server/target_spawn.qc
deleted file mode 100644 (file)
index b4b9b18..0000000
+++ /dev/null
@@ -1,367 +0,0 @@
-#if defined(CSQC)
-#elif defined(MENUQC)
-#elif defined(SVQC)
-       #include "../dpdefs/progsdefs.qh"
-    #include "../dpdefs/dpextensions.qh"
-    #include "../common/util.qh"
-    #include "defs.qh"
-#endif
-
-// spawner entity
-// "classname" "target_spawn"
-// "message" "fieldname value fieldname value ..."
-// "spawnflags"
-//   1 = call the spawn function
-//   2 = trigger on map load
-
-float target_spawn_initialized;
-.void() target_spawn_spawnfunc;
-float target_spawn_spawnfunc_field;
-.entity target_spawn_activator;
-.float target_spawn_id;
-float target_spawn_count;
-
-void target_spawn_helper_setmodel()
-{
-       setmodel(self, self.model);
-}
-
-void target_spawn_helper_setsize()
-{
-       setsize(self, self.mins, self.maxs);
-}
-
-void target_spawn_edit_entity(entity e, string msg, entity kt, entity t2, entity t3, entity t4, entity act)
-{
-       float i, n, valuefieldpos;
-       string key, value, valuefield, valueoffset, valueoffsetrandom;
-       entity valueent;
-       vector data, data2;
-       entity oldself;
-       entity oldactivator;
-
-       n = tokenize_console(msg);
-
-       for(i = 0; i < n-1; i += 2)
-       {
-               key = argv(i);
-               value = argv(i+1);
-               if(key == "$")
-               {
-                       data.x = -1;
-                       data.y = FIELD_STRING;
-               }
-               else
-               {
-                       data = stov(db_get(TemporaryDB, strcat("/target_spawn/field/", key)));
-                       if(data.y == 0) // undefined field, i.e., invalid type
-                       {
-                               print("target_spawn: invalid/unknown entity key ", key, " specified, ignored!\n");
-                               continue;
-                       }
-               }
-               if(substring(value, 0, 1) == "$")
-               {
-                       value = substring(value, 1, strlen(value) - 1);
-                       if(substring(value, 0, 1) == "$")
-                       {
-                               // deferred replacement
-                               // do nothing
-                               // useful for creating target_spawns with this!
-                       }
-                       else
-                       {
-                               // replace me!
-                               valuefieldpos = strstrofs(value, "+", 0);
-                               valueoffset = "";
-                               if(valuefieldpos != -1)
-                               {
-                                       valueoffset = substring(value, valuefieldpos + 1, strlen(value) - valuefieldpos - 1);
-                                       value = substring(value, 0, valuefieldpos);
-                               }
-
-                               valuefieldpos = strstrofs(valueoffset, "+", 0);
-                               valueoffsetrandom = "";
-                               if(valuefieldpos != -1)
-                               {
-                                       valueoffsetrandom = substring(valueoffset, valuefieldpos + 1, strlen(valueoffset) - valuefieldpos - 1);
-                                       valueoffset = substring(valueoffset, 0, valuefieldpos);
-                               }
-
-                               valuefieldpos = strstrofs(value, ".", 0);
-                               valuefield = "";
-                               if(valuefieldpos != -1)
-                               {
-                                       valuefield = substring(value, valuefieldpos + 1, strlen(value) - valuefieldpos - 1);
-                                       value = substring(value, 0, valuefieldpos);
-                               }
-
-                               if(value == "self")
-                               {
-                                       valueent = self;
-                                       value = "";
-                               }
-                               else if(value == "activator")
-                               {
-                                       valueent = act;
-                                       value = "";
-                               }
-                               else if(value == "other")
-                               {
-                                       valueent = other;
-                                       value = "";
-                               }
-                               else if(value == "pusher")
-                               {
-                                       if(time < act.pushltime)
-                                               valueent = act.pusher;
-                                       else
-                                               valueent = world;
-                                       value = "";
-                               }
-                               else if(value == "target")
-                               {
-                                       valueent = e;
-                                       value = "";
-                               }
-                               else if(value == "killtarget")
-                               {
-                                       valueent = kt;
-                                       value = "";
-                               }
-                               else if(value == "target2")
-                               {
-                                       valueent = t2;
-                                       value = "";
-                               }
-                               else if(value == "target3")
-                               {
-                                       valueent = t3;
-                                       value = "";
-                               }
-                               else if(value == "target4")
-                               {
-                                       valueent = t4;
-                                       value = "";
-                               }
-                               else if(value == "time")
-                               {
-                                       valueent = world;
-                                       value = ftos(time);
-                               }
-                               else
-                               {
-                                       print("target_spawn: invalid/unknown variable replacement ", value, " specified, ignored!\n");
-                                       continue;
-                               }
-
-                               if(valuefield == "")
-                               {
-                                       if(value == "")
-                                               value = ftos(num_for_edict(valueent));
-                               }
-                               else
-                               {
-                                       if(value != "")
-                                       {
-                                               print("target_spawn: try to get a field of a non-entity, ignored!\n");
-                                               continue;
-                                       }
-                                       data2 = stov(db_get(TemporaryDB, strcat("/target_spawn/field/", valuefield)));
-                                       if(data2_y == 0) // undefined field, i.e., invalid type
-                                       {
-                                               print("target_spawn: invalid/unknown entity key replacement ", valuefield, " specified, ignored!\n");
-                                               continue;
-                                       }
-                                       value = getentityfieldstring(data2_x, valueent);
-                               }
-
-                               if(valueoffset != "")
-                               {
-                                       switch(data.y)
-                                       {
-                                               case FIELD_STRING:
-                                                       value = strcat(value, valueoffset);
-                                                       break;
-                                               case FIELD_FLOAT:
-                                                       value = ftos(stof(value) + stof(valueoffset));
-                                                       break;
-                                               case FIELD_VECTOR:
-                                                       value = vtos(stov(value) + stov(valueoffset));
-                                                       break;
-                                               default:
-                                                       print("target_spawn: only string, float and vector fields can do calculations, calculation ignored!\n");
-                                                       break;
-                                       }
-                               }
-
-                               if(valueoffsetrandom != "")
-                               {
-                                       switch(data.y)
-                                       {
-                                               case FIELD_FLOAT:
-                                                       value = ftos(stof(value) + random() * stof(valueoffsetrandom));
-                                                       break;
-                                               case FIELD_VECTOR:
-                                                       data2 = stov(valueoffsetrandom);
-                                                       value = vtos(stov(value) + random() * data2_x * '1 0 0' + random() * data2_y * '0 1 0' + random() * data2_z * '0 0 1');
-                                                       break;
-                                               default:
-                                                       print("target_spawn: only float and vector fields can do random calculations, calculation ignored!\n");
-                                                       break;
-                                       }
-                               }
-                       }
-               }
-               if(key == "$")
-               {
-                       if(substring(value, 0, 1) == "_")
-                               value = strcat("target_spawn_helper", value);
-                       putentityfieldstring(target_spawn_spawnfunc_field, e, value);
-
-                       oldself = self;
-                       oldactivator = activator;
-
-                       self = e;
-                       activator = act;
-
-                       self.target_spawn_spawnfunc();
-
-                       self = oldself;
-                       activator = oldactivator;
-
-                       // We called an external function, so we have to re-tokenize msg.
-                       n = tokenize_console(msg);
-               }
-               else
-               {
-                       if(data.y == FIELD_VECTOR)
-                               value = strreplace("'", "", value); // why?!?
-                       putentityfieldstring(data.x, e, value);
-               }
-       }
-}
-
-void target_spawn_useon(entity e)
-{
-       self.target_spawn_activator = activator;
-       target_spawn_edit_entity(
-               e,
-               self.message,
-               find(world, targetname, self.killtarget),
-               find(world, targetname, self.target2),
-               find(world, targetname, self.target3),
-               find(world, targetname, self.target4),
-               activator
-       );
-}
-
-float target_spawn_cancreate()
-{
-       float c;
-       entity e;
-
-       c = self.count;
-       if(c == 0) // no limit?
-               return 1;
-
-       ++c; // increase count to not include MYSELF
-       for(e = world; (e = findfloat(e, target_spawn_id, self.target_spawn_id)); --c)
-               ;
-
-       // if c now is 0, we have AT LEAST the given count (maybe more), so don't spawn any more
-       if(c == 0)
-               return 0;
-       return 1;
-}
-
-void target_spawn_use()
-{
-       entity e;
-
-       if(self.target == "")
-       {
-               // spawn new entity
-               if(!target_spawn_cancreate())
-                       return;
-               e = spawn();
-               target_spawn_useon(e);
-               e.target_spawn_id = self.target_spawn_id;
-       }
-       else if(self.target == "*activator")
-       {
-               // edit entity
-               if(activator)
-                       target_spawn_useon(activator);
-       }
-       else
-       {
-               // edit entity
-               for(e = world; (e = find(e, targetname, self.target)); )
-                       target_spawn_useon(e);
-       }
-}
-
-void target_spawn_spawnfirst()
-{
-       activator = self.target_spawn_activator;
-       if(self.spawnflags & 2)
-               target_spawn_use();
-}
-
-void initialize_field_db()
-{
-       if(!target_spawn_initialized)
-       {
-               float n, i;
-               string fn;
-               vector prev, new;
-               float ft;
-
-               n = numentityfields();
-               for(i = 0; i < n; ++i)
-               {
-                       fn = entityfieldname(i);
-                       ft = entityfieldtype(i);
-                       new = i * '1 0 0' + ft * '0 1 0' + '0 0 1';
-                       prev = stov(db_get(TemporaryDB, strcat("/target_spawn/field/", fn)));
-                       if(prev.y == 0)
-                       {
-                               db_put(TemporaryDB, strcat("/target_spawn/field/", fn), vtos(new));
-                               if(fn == "target_spawn_spawnfunc")
-                                       target_spawn_spawnfunc_field = i;
-                       }
-               }
-
-               target_spawn_initialized = 1;
-       }
-}
-
-void spawnfunc_target_spawn()
-{
-       initialize_field_db();
-       self.use = target_spawn_use;
-       self.message = strzone(strreplace("'", "\"", self.message));
-       self.target_spawn_id = ++target_spawn_count;
-       InitializeEntity(self, target_spawn_spawnfirst, INITPRIO_LAST);
-}
-
-
-void trigger_relay_if_use()
-{
-       float n;
-       n = self.count;
-
-       // TODO make this generic AND faster than nextent()ing through all, if somehow possible
-       n = (cvar_string(self.netname) == cvar_string(self.message));
-       if(self.spawnflags & 1)
-               n = !n;
-
-       if(n)
-               SUB_UseTargets();
-}
-
-void spawnfunc_trigger_relay_if()
-{
-       self.use = trigger_relay_if_use;
-}
index f2b0c56ce846d596cdd486772c89faa98d3b2160..4c3a52324ae3237bcc844f67a9e833ce2cc22fd8 100644 (file)
@@ -1,3 +1,5 @@
+#include "../../../common/triggers/subs.qh"
+
 #define cvar_base "g_turrets_unit_"
 .float clientframe;
 void turrets_setframe(float _frame, float client_only)
index 787510694e9101eca0c0158564170fdf846b5d9f..eab1e1aaef0ad29eb6f3672bfceb6a309e26897c 100644 (file)
@@ -229,7 +229,6 @@ void turrets_precash()
 
 
 #ifdef TURRET_DEBUG
-void SUB_Remove();
 void marker_think()
 {
     if(self.cnt)
index 87dfe52a7e55f2d0a8b94dadbbb4dcacca51653a..06760b09f401e674941dd2ee68f29e273e0baeff 100644 (file)
@@ -2,6 +2,8 @@ const vector RACER_MIN = '-120 -120 -40';
 const vector RACER_MAX = '120 120 40';
 
 #ifdef SVQC
+#include "../../common/triggers/trigger/impulse.qh"
+
 void racer_exit(float eject);
 void racer_enter();
 
index 96d54e3de7bfa832da6a1d6ba8458031389deed8..5f639a88063f709743cc84a26d7e4c05e228d64f 100644 (file)
@@ -1,3 +1,5 @@
+#include "../../common/triggers/subs.qh"
+
 float autocvar_g_vehicles_crush_dmg;
 float autocvar_g_vehicles_crush_force;
 float autocvar_g_vehicles_delayspawn;
index aa0de911963a9bc700e6aa3167b2181633d7b7b6..a817e82eb28e27c8666a9717cdfdbad9f50ecac5 100644 (file)
@@ -573,6 +573,45 @@ vector WarpZoneLib_NearestPointOnBox(vector mi, vector ma, vector org)
        return nearest;
 }
 
+float WarpZoneLib_BadClassname(string myclassname)
+{
+       switch(myclassname)
+       {
+               case "weapon_info":
+               case "monster_info":
+               case "deathtype":
+               case "callback":
+               case "callbackchain":
+               case "weaponentity":
+               case "exteriorweaponentity":
+               case "csqc_score_team":
+               case "pingplreport":
+               case "ent_client_scoreinfo":
+               case "saved_cvar_value":
+               case "accuracy":
+               case "entcs_sender_v2":
+               case "entcs_receiver_v2":
+               case "clientinit":
+               case "sprite_waypoint":
+               case "waypoint":
+               case "gibsplash":
+               //case "net_linked": // actually some real entities are linked without classname, fail
+               case "":
+                       return true;
+       }
+
+       if(startsWith(myclassname, "msg_"))
+               return true;
+
+       if(startsWith(myclassname, "target_"))
+               return true;
+
+       if(startsWith(myclassname, "info_"))
+               return true;
+
+       return false;
+}
+
 .float WarpZone_findradius_hit;
 .entity WarpZone_findradius_next;
 void WarpZone_FindRadius_Recurse(vector org, float rad,        vector org0,               vector transform, vector shift, float needlineofsight)
@@ -591,6 +630,8 @@ void WarpZone_FindRadius_Recurse(vector org, float rad,        vector org0,
 
        for(e = e0; e; e = e.chain)
        {
+               if(WarpZoneLib_BadClassname(e.classname))
+                       continue;
                p = WarpZoneLib_NearestPointOnBox(e.origin + e.mins, e.origin + e.maxs, org0);
                if(needlineofsight)
                {
@@ -626,6 +667,9 @@ void WarpZone_FindRadius_Recurse(vector org, float rad,        vector org0,
        }
        for(e = wz; e; e = e.WarpZone_findradius_next)
        {
+               if(WarpZoneLib_BadClassname(e.classname))
+                       continue;
+
                org0_new = WarpZone_TransformOrigin(e, org);
                traceline(e.warpzone_targetorigin, org0_new, MOVE_NOMONSTERS, e);
                org_new = trace_endpos;
@@ -772,3 +816,64 @@ entity WarpZone_RefSys_SpawnSameRefSys(entity me)
        WarpZone_RefSys_Copy(e, me);
        return e;
 }
+
+float WarpZoneLib_ExactTrigger_Touch()
+{
+       return !WarpZoneLib_BoxTouchesBrush(other.absmin, other.absmax, self, other);
+}
+
+
+void WarpZoneLib_MoveOutOfSolid_Expand(entity e, vector by)
+{
+       float eps = 0.0625;
+       tracebox(e.origin, e.mins - '1 1 1' * eps, e.maxs + '1 1 1' * eps, e.origin + by, MOVE_WORLDONLY, e);
+       if (trace_startsolid)
+               return;
+       if (trace_fraction < 1)
+       {
+               // hit something
+               // adjust origin in the other direction...
+               setorigin(e,e.origin - by * (1 - trace_fraction));
+       }
+}
+
+float WarpZoneLib_MoveOutOfSolid(entity e)
+{
+       vector o, m0, m1;
+
+       o = e.origin;
+       traceline(o, o, MOVE_WORLDONLY, e);
+       if (trace_startsolid)
+               return false;
+
+       tracebox(o, e.mins, e.maxs, o, MOVE_WORLDONLY, e);
+       if (!trace_startsolid)
+               return true;
+
+       m0 = e.mins;
+       m1 = e.maxs;
+       e.mins = '0 0 0';
+       e.maxs = '0 0 0';
+       WarpZoneLib_MoveOutOfSolid_Expand(e, '1 0 0' * m0_x);
+       e.mins_x = m0_x;
+       WarpZoneLib_MoveOutOfSolid_Expand(e, '1 0 0' * m1_x);
+       e.maxs_x = m1_x;
+       WarpZoneLib_MoveOutOfSolid_Expand(e, '0 1 0' * m0_y);
+       e.mins_y = m0_y;
+       WarpZoneLib_MoveOutOfSolid_Expand(e, '0 1 0' * m1_y);
+       e.maxs_y = m1_y;
+       WarpZoneLib_MoveOutOfSolid_Expand(e, '0 0 1' * m0_z);
+       e.mins_z = m0_z;
+       WarpZoneLib_MoveOutOfSolid_Expand(e, '0 0 1' * m1_z);
+       e.maxs_z = m1_z;
+       setorigin(e, e.origin);
+
+       tracebox(e.origin, e.mins, e.maxs, e.origin, MOVE_WORLDONLY, e);
+       if (trace_startsolid)
+       {
+               setorigin(e, o);
+               return false;
+       }
+
+       return true;
+}
index 7742db8b9b101f54968503b3f5e238f993120faf..76a77ab0219b1b5103dc3c54151b5dfd4deba1ce 100644 (file)
@@ -104,4 +104,6 @@ entity WarpZone_RefSys_SpawnSameRefSys(entity me); // spawn().R = me.R
 #ifndef BITXOR_ASSIGN
 # define BITXOR_ASSIGN(a,b) ((a) = ((a) | (b)) - ((a) & (b)))
 #endif
+float WarpZoneLib_MoveOutOfSolid(entity e);
+#define move_out_of_solid(e) WarpZoneLib_MoveOutOfSolid(e)
 #endif
index 5d529c12e215ab78c66f85c48d7c79a8f3800147..8e25b983f60dcf52d701fcc5cf0cbaa5a61488fa 100644 (file)
@@ -6,6 +6,7 @@
        #include "common.qh"
        #include "server.qh"
        #include "../common/constants.qh"
+       #include "../common/triggers/subs.qh"
        #include "../common/util.qh"
        #include "../server/constants.qh"
        #include "../server/defs.qh"
index 79cff0174a3a98f3c046a68777de6c07e3bb7eee..4cb0617946d5711ad4f7404be1495b7241ae2a87 100644 (file)
@@ -3,70 +3,10 @@
 #elif defined(SVQC)
        #include "../dpdefs/progsdefs.qh"
     #include "../dpdefs/dpextensions.qh"
-    #include "common.qh"
     #include "util_server.qh"
     #include "../csqcmodellib/sv_model.qh"
 #endif
-
-void WarpZoneLib_MoveOutOfSolid_Expand(entity e, vector by)
-{
-       float eps = 0.0625;
-       tracebox(e.origin, e.mins - '1 1 1' * eps, e.maxs + '1 1 1' * eps, e.origin + by, MOVE_WORLDONLY, e);
-       if (trace_startsolid)
-               return;
-       if (trace_fraction < 1)
-       {
-               // hit something
-               // adjust origin in the other direction...
-               setorigin(e,e.origin - by * (1 - trace_fraction));
-       }
-}
-
-float WarpZoneLib_MoveOutOfSolid(entity e)
-{
-       vector o, m0, m1;
-
-       o = e.origin;
-       traceline(o, o, MOVE_WORLDONLY, e);
-       if (trace_startsolid)
-               return false;
-
-       tracebox(o, e.mins, e.maxs, o, MOVE_WORLDONLY, e);
-       if (!trace_startsolid)
-               return true;
-
-       m0 = e.mins;
-       m1 = e.maxs;
-       e.mins = '0 0 0';
-       e.maxs = '0 0 0';
-       WarpZoneLib_MoveOutOfSolid_Expand(e, '1 0 0' * m0_x);
-       e.mins_x = m0_x;
-       WarpZoneLib_MoveOutOfSolid_Expand(e, '1 0 0' * m1_x);
-       e.maxs_x = m1_x;
-       WarpZoneLib_MoveOutOfSolid_Expand(e, '0 1 0' * m0_y);
-       e.mins_y = m0_y;
-       WarpZoneLib_MoveOutOfSolid_Expand(e, '0 1 0' * m1_y);
-       e.maxs_y = m1_y;
-       WarpZoneLib_MoveOutOfSolid_Expand(e, '0 0 1' * m0_z);
-       e.mins_z = m0_z;
-       WarpZoneLib_MoveOutOfSolid_Expand(e, '0 0 1' * m1_z);
-       e.maxs_z = m1_z;
-       setorigin(e, e.origin);
-
-       tracebox(e.origin, e.mins, e.maxs, e.origin, MOVE_WORLDONLY, e);
-       if (trace_startsolid)
-       {
-               setorigin(e, o);
-               return false;
-       }
-
-       return true;
-}
-
-float WarpZoneLib_ExactTrigger_Touch()
-{
-       return !WarpZoneLib_BoxTouchesBrush(other.absmin, other.absmax, self, other);
-}
+#include "common.qh"
 
 void WarpZoneLib_ExactTrigger_Init()
 {
index 38df7fe0cc804f55c344a368046b4e5adb428fe3..0a42d7d12c67227417356d86403ae37c1718d3b3 100644 (file)
@@ -3,5 +3,7 @@
 
 float WarpZoneLib_MoveOutOfSolid(entity e);
 float WarpZoneLib_ExactTrigger_Touch();
+#ifdef SVQC
 void WarpZoneLib_ExactTrigger_Init();
 #endif
+#endif