From: Mario Date: Tue, 24 Mar 2015 01:30:55 +0000 (+1100) Subject: Merge branch 'Mario/qc_physics_prehax' into Mario/combined_updates X-Git-Url: https://git.rm.cloudns.org/?a=commitdiff_plain;h=01cb4523cb4034e2bb98a96ce4bf485a84ba5ebd;p=xonotic%2Fxonotic-data.pk3dir.git Merge branch 'Mario/qc_physics_prehax' into Mario/combined_updates Conflicts: qcsrc/client/laser.qc qcsrc/client/main.qc qcsrc/client/progs.src qcsrc/client/vehicles/vehicles.qc qcsrc/common/constants.qh qcsrc/common/csqcmodel_settings.qh qcsrc/common/nades.qc qcsrc/common/triggers/teleporters.qc qcsrc/common/triggers/trigger/jumppads.qc qcsrc/common/weapons/w_porto.qc qcsrc/csqcmodellib/cl_player.qc qcsrc/server/cl_client.qc qcsrc/server/cl_physics.qc qcsrc/server/cl_player.qc qcsrc/server/defs.qh qcsrc/server/g_triggers.qc qcsrc/server/mutators/gamemode_assault.qc 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/progs.src qcsrc/server/race.qh qcsrc/server/t_halflife.qc 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 --- 01cb4523cb4034e2bb98a96ce4bf485a84ba5ebd diff --cc qcsrc/client/damage.qc index ce132dbd0,836c9163a..b0d1bbdee --- a/qcsrc/client/damage.qc +++ b/qcsrc/client/damage.qc @@@ -7,9 -7,9 +7,9 @@@ #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" + #include "../common/vehicles/cl_vehicles.qh" #elif defined(MENUQC) #elif defined(SVQC) #endif diff --cc qcsrc/client/main.qc index 93226c70e,f51674b0b..9b349fa7d --- a/qcsrc/client/main.qc +++ b/qcsrc/client/main.qc @@@ -7,8 -3,7 +7,7 @@@ #include "particles.qh" #include "scoreboard.qh" #include "shownames.qh" - #include "target_music.qh" -#include "tturrets.qh" +#include "../common/turrets/cl_turrets.qh" #include "tuba.qh" #include "wall.qh" #include "waypointsprites.qh" @@@ -23,9 -15,9 +22,11 @@@ #include "../common/net_notice.qh" #include "../common/monsters/monsters.qh" +#include "../common/turrets/turrets.qh" +#include "../common/vehicles/vehicles.qh" + #include "../common/triggers/include.qh" + #include "../warpzonelib/client.qh" // -------------------------------------------------------------------------- @@@ -1030,11 -844,17 +1032,22 @@@ 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_JAILCAMERA: ent_jailcamera(); break; + case ENT_CLIENT_MINIGAME: ent_read_minigame(); break; + case ENT_CLIENT_EFFECT: Read_Effect(bIsNewEntity); break; + case ENT_CLIENT_VIEWLOC: ent_viewloc(); break; + case ENT_CLIENT_VIEWLOC_TRIGGER: ent_viewloc_trigger(); 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; + case ENT_CLIENT_TRIGGER_IMPULSE: ent_trigger_impulse(); break; default: //error(strcat(_("unknown entity type in CSQC_Ent_Update: %d\n"), self.enttype)); diff --cc qcsrc/client/progs.src index 019adbf23,a72a2104b..daf5aaed6 --- a/qcsrc/client/progs.src +++ b/qcsrc/client/progs.src @@@ -30,9 -25,10 +28,8 @@@ rubble.q scoreboard.qc shownames.qc sortlist.qc - target_music.qc teamradar.qc -tturrets.qc tuba.qc -vehicles/vehicles.qc view.qc wall.qc waypointsprites.qc @@@ -47,8 -44,8 +45,9 @@@ weapons/projectile.qc // TOD ../common/nades.qc ../common/net_notice.qc ../common/notifications.qc + ../common/physics.qc ../common/playerstats.qc +../common/p2mathlib.qc ../common/test.qc ../common/urllib.qc ../common/util.qc @@@ -65,24 -56,22 +64,29 @@@ ../common/monsters/monsters.qc +../common/turrets/cl_turrets.qc +../common/turrets/turrets.qc + +../common/vehicles/cl_vehicles.qc +../common/vehicles/vehicles.qc + +../common/viewloc.qc + ../common/weapons/weapons.qc // TODO + ../common/triggers/include.qc + ../csqcmodellib/cl_model.qc ../csqcmodellib/cl_player.qc ../csqcmodellib/interpolate.qc ../server/movelib.qc + -../server/mutators/mutator_multijump.qc - -../server/vehicles/bumblebee.qc - ../server/t_items.qc +../server/t_viewloc.qc + ++../server/mutators/mutator_multijump.qc + ../warpzonelib/anglestransform.qc ../warpzonelib/client.qc ../warpzonelib/common.qc diff --cc qcsrc/common/constants.qh index b795a0f41,9e1355ba4..6553d4cb4 --- a/qcsrc/common/constants.qh +++ b/qcsrc/common/constants.qh @@@ -79,14 -103,17 +104,25 @@@ const int ENT_CLIENT_ELIMINATEDPLAYERS const int ENT_CLIENT_TURRET = 40; const int ENT_CLIENT_AUXILIARYXHAIR = 50; const int ENT_CLIENT_VEHICLE = 60; - const int ENT_CLIENT_GENERATOR = 70; - const int ENT_CLIENT_CONTROLPOINT_ICON = 71; - const int ENT_CLIENT_JAILCAMERA = 72; - const int ENT_CLIENT_EFFECT = 73; - const int ENT_CLIENT_CONQUEST_CONTROLPOINT = 74; - const int ENT_CLIENT_MINIGAME = 75; - const int ENT_CLIENT_VIEWLOC = 76; - const int ENT_CLIENT_VIEWLOC_TRIGGER = 77; + 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; + const int ENT_CLIENT_TRIGGER_IMPULSE = 68; + const int ENT_CLIENT_SWAMP = 69; + const int ENT_CLIENT_CORNER = 70; + const int ENT_CLIENT_KEYLOCK = 71; ++const int ENT_CLIENT_GENERATOR = 72; ++const int ENT_CLIENT_CONTROLPOINT_ICON = 73; ++const int ENT_CLIENT_JAILCAMERA = 74; ++const int ENT_CLIENT_EFFECT = 75; ++const int ENT_CLIENT_CONQUEST_CONTROLPOINT = 76; ++const int ENT_CLIENT_MINIGAME = 77; ++const int ENT_CLIENT_VIEWLOC = 78; ++const int ENT_CLIENT_VIEWLOC_TRIGGER = 79; const int ENT_CLIENT_HEALING_ORB = 80; diff --cc qcsrc/common/csqcmodel_settings.qh index fdddc7463,5e5ff42eb..adc2e1ddb --- a/qcsrc/common/csqcmodel_settings.qh +++ b/qcsrc/common/csqcmodel_settings.qh @@@ -60,7 -53,7 +60,8 @@@ CSQCMODEL_ENDIF \ CSQCMODEL_PROPERTY(1024, float, ReadAngle, WriteAngle, v_angle_x) \ CSQCMODEL_PROPERTY_SCALED(4096, float, ReadByte, WriteByte, scale, 16, 0, 255) \ - CSQCMODEL_PROPERTY(8192, TAG_VIEWLOC_TYPE, ReadShort, WriteEntity, TAG_VIEWLOC_NAME) - CSQCMODEL_PROPERTY(8192, int, ReadInt24_t, WriteInt24_t, dphitcontentsmask) ++ CSQCMODEL_PROPERTY(8192, TAG_VIEWLOC_TYPE, ReadShort, WriteEntity, TAG_VIEWLOC_NAME) \ ++ CSQCMODEL_PROPERTY(16384, 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 diff --cc qcsrc/common/monsters/sv_monsters.qc index 0a57edbec,3526a1f18..6b909dc72 --- a/qcsrc/common/monsters/sv_monsters.qc +++ b/qcsrc/common/monsters/sv_monsters.qc @@@ -21,15 -19,16 +21,16 @@@ #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" #endif -// ========================= -// SVQC Monster Properties -// ========================= - +void monsters_setstatus() +{ + self.stat_monsters_total = monsters_total; + self.stat_monsters_killed = monsters_killed; +} void monster_dropitem() { diff --cc qcsrc/common/nades.qc index f4e863ef8,fcdc5533e..f9ddb8e99 --- a/qcsrc/common/nades.qc +++ b/qcsrc/common/nades.qc @@@ -3,7 -3,8 +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" #elif defined(MENUQC) diff --cc qcsrc/common/physics.qc index 000000000,d2287ea18..8c17e2785 mode 000000,100644..100644 --- a/qcsrc/common/physics.qc +++ b/qcsrc/common/physics.qc @@@ -1,0 -1,1762 +1,1839 @@@ + #include "physics.qh" + #include "triggers/trigger/swamp.qh" + #include "triggers/trigger/jumppads.qh" ++#include "viewloc.qh" + + #ifdef SVQC + + #include "../server/miscfunctions.qh" ++#include "../server/t_viewloc.qh" ++ ++// client side physics ++bool Physics_Valid(string thecvar) ++{ ++ return autocvar_g_physics_clientselect && checkinlist(thecvar, autocvar_g_physics_clientselect_options); ++} ++ ++float Physics_ClientOption(entity pl, string option) ++{ ++ if (Physics_Valid(pl.cvar_cl_physics)) ++ { ++ string var = sprintf("g_physics_%s_%s", pl.cvar_cl_physics, option); ++ if (cvar_type(var) & 1) ++ return cvar(var); ++ } ++ return cvar(strcat("sv_", option)); ++} + + 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); ++ addstat(STAT_JETPACK_REVERSE_THRUST, AS_INT, stat_jetpack_reverse_thrust); + + // 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); ++ ++ // new properties ++ addstat(STAT_MOVEVARS_JUMPVELOCITY, AS_FLOAT, stat_sv_jumpvelocity); ++ addstat(STAT_MOVEVARS_AIRACCEL_QW_STRETCHFACTOR, AS_FLOAT, stat_sv_airaccel_qw_stretchfactor); ++ addstat(STAT_MOVEVARS_MAXAIRSTRAFESPEED, AS_FLOAT, stat_sv_maxairstrafespeed); ++ addstat(STAT_MOVEVARS_MAXAIRSPEED, AS_FLOAT, stat_sv_maxairspeed); ++ addstat(STAT_MOVEVARS_AIRSTRAFEACCELERATE, AS_FLOAT, stat_sv_airstrafeaccelerate); ++ addstat(STAT_MOVEVARS_WARSOWBUNNY_TURNACCEL, AS_FLOAT, stat_sv_warsowbunny_turnaccel); ++ addstat(STAT_MOVEVARS_AIRACCEL_SIDEWAYS_FRICTION, AS_FLOAT, stat_sv_airaccel_sideways_friction); ++ addstat(STAT_MOVEVARS_AIRCONTROL, AS_FLOAT, stat_sv_aircontrol); ++ addstat(STAT_MOVEVARS_AIRCONTROL_POWER, AS_FLOAT, stat_sv_aircontrol_power); ++ addstat(STAT_MOVEVARS_AIRCONTROL_PENALTY, AS_FLOAT, stat_sv_aircontrol_penalty); ++ addstat(STAT_MOVEVARS_WARSOWBUNNY_AIRFORWARDACCEL, AS_FLOAT, stat_sv_warsowbunny_airforwardaccel); ++ addstat(STAT_MOVEVARS_WARSOWBUNNY_TOPSPEED, AS_FLOAT, stat_sv_warsowbunny_topspeed); ++ addstat(STAT_MOVEVARS_WARSOWBUNNY_ACCEL, AS_FLOAT, stat_sv_warsowbunny_accel); ++ addstat(STAT_MOVEVARS_WARSOWBUNNY_BACKTOSIDERATIO, AS_FLOAT, stat_sv_warsowbunny_backtosideratio); ++ addstat(STAT_MOVEVARS_FRICTION, AS_FLOAT, stat_sv_friction); ++ addstat(STAT_MOVEVARS_ACCELERATE, AS_FLOAT, stat_sv_accelerate); ++ addstat(STAT_MOVEVARS_STOPSPEED, AS_FLOAT, stat_sv_stopspeed); ++ addstat(STAT_MOVEVARS_AIRACCELERATE, AS_FLOAT, stat_sv_airaccelerate); ++ addstat(STAT_MOVEVARS_AIRSTOPACCELERATE, AS_FLOAT, stat_sv_airstopaccelerate); + } + + 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); ++ self.stat_sv_airaccel_qw = AdjustAirAccelQW(Physics_ClientOption(self, "airaccel_qw"), maxspd_mod); ++ if(Physics_ClientOption(self, "airstrafeaccel_qw")) ++ self.stat_sv_airstrafeaccel_qw = AdjustAirAccelQW(Physics_ClientOption(self, "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_sv_airspeedlimit_nonqw = Physics_ClientOption(self, "airspeedlimit_nonqw") * maxspd_mod; ++ self.stat_sv_maxspeed = Physics_ClientOption(self, "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_jetpack_reverse_thrust = PHYS_JETPACK_REVERSE_THRUST; + + 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; ++ ++ ++ // old stats ++ // fix some new settings ++ self.stat_sv_airaccel_qw_stretchfactor = Physics_ClientOption(self, "airaccel_qw_stretchfactor"); ++ self.stat_sv_maxairstrafespeed = Physics_ClientOption(self, "maxairstrafespeed"); ++ self.stat_sv_maxairspeed = Physics_ClientOption(self, "maxairspeed"); ++ self.stat_sv_airstrafeaccelerate = Physics_ClientOption(self, "airstrafeaccelerate"); ++ self.stat_sv_warsowbunny_turnaccel = Physics_ClientOption(self, "warsowbunny_turnaccel"); ++ self.stat_sv_airaccel_sideways_friction = Physics_ClientOption(self, "airaccel_sideways_friction"); ++ self.stat_sv_aircontrol = Physics_ClientOption(self, "aircontrol"); ++ self.stat_sv_aircontrol_power = Physics_ClientOption(self, "aircontrol_power"); ++ self.stat_sv_aircontrol_penalty = Physics_ClientOption(self, "aircontrol_penalty"); ++ self.stat_sv_warsowbunny_airforwardaccel = Physics_ClientOption(self, "warsowbunny_airforwardaccel"); ++ self.stat_sv_warsowbunny_topspeed = Physics_ClientOption(self, "warsowbunny_topspeed"); ++ self.stat_sv_warsowbunny_accel = Physics_ClientOption(self, "warsowbunny_accel"); ++ self.stat_sv_warsowbunny_backtosideratio = Physics_ClientOption(self, "warsowbunny_backtosideratio"); ++ self.stat_sv_friction = Physics_ClientOption(self, "friction"); ++ self.stat_sv_accelerate = Physics_ClientOption(self, "accelerate"); ++ self.stat_sv_stopspeed = Physics_ClientOption(self, "stopspeed"); ++ self.stat_sv_airaccelerate = Physics_ClientOption(self, "airaccelerate"); ++ self.stat_sv_airstopaccelerate = Physics_ClientOption(self, "airstopaccelerate"); ++ self.stat_sv_jumpvelocity = Physics_ClientOption(self, "jumpvelocity"); + } + #endif + -float IsMoveInDirection(vector mv, float angle) // key mix factor ++float IsMoveInDirection(vector mv, float ang) // 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); ++ ang -= RAD2DEG * atan2(mv_y, mv_x); ++ ang = remainder(ang, 360) / 45; ++ return ang > 1 ? 0 : ang < -1 ? 0 : 1 - fabs(ang); + } + + 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 + // 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)) + { + #ifdef SVQC + if(autocvar_speedmeter) + dprint(strcat("landing velocity: ", vtos(self.velocity), " (abs: ", ftos(vlen(self.velocity)), ")\n")); + #endif + if(self.lastground < time - 0.3) + { + self.velocity_x *= (1 - PHYS_FRICTION_ONLAND); + self.velocity_y *= (1 - PHYS_FRICTION_ONLAND); + } + #ifdef SVQC + if(self.jumppadcount > 1) + dprint(strcat(ftos(self.jumppadcount), "x jumppad combo\n")); + self.jumppadcount = 0; + #endif + } + + 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; + self.flags |= FL_WATERJUMP; + SET_JUMP_HELD(self); + self.teleport_time = time + 2; // safety net + } + } + } + + + #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) + ITEMS(self) &= ~IT_USING_JETPACK; + + if(PHYS_INPUT_BUTTON_JUMP(self) || PHYS_INPUT_BUTTON_JETPACK(self)) + { + 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 = !PHYS_JETPACK_FUEL || PHYS_AMMO_FUEL(self) || ITEMS(self) & IT_UNLIMITED_WEAPON_AMMO; + + 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; + ITEMS(self) &= ~IT_USING_JETPACK; + } + else if (activate && !PHYS_FROZEN(self)) + ITEMS(self) |= IT_USING_JETPACK; + } + else + { + self.jetpack_stopped = false; + ITEMS(self) &= ~IT_USING_JETPACK; + } + 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); + #ifdef SVQC + if (time >= self.teleport_time) + #endif + PM_Accelerate(wishdir, wishspeed, wishspeed, PHYS_ACCELERATE * maxspd_mod, 1, 0, 0, 0); + PM_ClientMovement_Move(); + } + + 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 + } + } + // water acceleration + PM_Accelerate(wishdir, wishspeed, wishspeed, PHYS_ACCELERATE * maxspd_mod, 1, 0, 0, 0); + PM_ClientMovement_Move(); + } + + 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); + #ifdef SVQC + if (time >= self.teleport_time) + #endif + // water acceleration + PM_Accelerate(wishdir, wishspeed, wishspeed, PHYS_ACCELERATE*maxspd_mod, 1, 0, 0, 0); + PM_ClientMovement_Move(); + } + + 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; + ++ if(PHYS_JETPACK_REVERSE_THRUST && PHYS_INPUT_BUTTON_CROUCH(self)) { a_up = PHYS_JETPACK_REVERSE_THRUST; } ++ + wishvel_x *= a_side; + wishvel_y *= a_side; + wishvel_z *= a_up; + wishvel_z += a_add; + ++ if(PHYS_JETPACK_REVERSE_THRUST && PHYS_INPUT_BUTTON_CROUCH(self)) { wishvel_z *= -1; } ++ + 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 + } + + void PM_walk(float buttons_prev, float maxspd_mod) + { + if (!WAS_ONGROUND(self)) + { + #ifdef SVQC + if (autocvar_speedmeter) + dprint(strcat("landing velocity: ", vtos(self.velocity), " (abs: ", ftos(vlen(self.velocity)), ")\n")); + #endif + if (self.lastground < time - 0.3) + self.velocity *= (1 - PHYS_FRICTION_ONLAND); + #ifdef SVQC + if (self.jumppadcount > 1) + dprint(strcat(ftos(self.jumppadcount), "x jumppad combo\n")); + self.jumppadcount = 0; + #endif + } + + // 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.items = getstati(STAT_ITEMS, 0, 24); + + 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.discomode ) ++ { ++ if(IS_PLAYER(self)) ++ self.BUTTON_JUMP = 1; ++ ++ self.angles_y = time*180; ++ self.velocity = randomvec() * 80; ++ self.fixangle = true; ++ } ++ + if (self.movetype == MOVETYPE_NONE) + return; + + // when we get here, disableclientprediction cannot be 2 + self.disableclientprediction = 0; ++ ++ viewloc_PlayerPhysics(); + #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) + { + 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; + } + } + + #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 || (BUFFS(self) & BUFF_FLIGHT)) + 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 = self.flags; + + 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.flags & FL_DUCKED) ? PMF_DUCKED : 0) | + (!(self.flags & FL_JUMPRELEASED) ? 0 : PMF_JUMP_HELD) | + ((self.flags & FL_ONGROUND) ? PMF_ONGROUND : 0); + #endif + } diff --cc qcsrc/common/physics.qh index 000000000,01b087455..c0cee6e0e mode 000000,100644..100644 --- a/qcsrc/common/physics.qh +++ b/qcsrc/common/physics.qh @@@ -1,0 -1,338 +1,367 @@@ + #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 + + const int FL_WATERJUMP = 2048; // player jumping out of water + const int FL_JUMPRELEASED = 4096; // for jump debouncing + + float PM_multijump_checkjump(); + void PM_multijump(); + + .float watertype; + .int items; + + // 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 GAMEPLAYFIX_DOWNTRACEONGROUND getstati(STAT_GAMEPLAYFIX_DOWNTRACEONGROUND) + #define GAMEPLAYFIX_STEPMULTIPLETIMES getstati(STAT_GAMEPLAYFIX_STEPMULTIPLETIMES) + #define GAMEPLAYFIX_UNSTICKPLAYERS getstati(STAT_GAMEPLAYFIX_UNSTICKPLAYERS) + #define GAMEPLAYFIX_STEPDOWN getstati(STAT_GAMEPLAYFIX_STEPDOWN) + + #define IS_DUCKED(s) !!(s.flags & FL_DUCKED) + #define SET_DUCKED(s) s.flags |= FL_DUCKED + #define UNSET_DUCKED(s) s.flags &= ~FL_DUCKED + + #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) !!(s.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 BUFFS(s) getstati(STAT_BUFFS) + + #define PHYS_AMMO_FUEL(s) getstati(STAT_FUEL) + + #define PHYS_FROZEN(s) getstati(STAT_FROZEN) + + #define PHYS_DOUBLEJUMP getstati(STAT_DOUBLEJUMP) + + #define PHYS_BUGRIGS getstati(STAT_BUGRIGS) + #define PHYS_BUGRIGS_ANGLE_SMOOTHING getstati(STAT_BUGRIGS_ANGLE_SMOOTHING) + #define PHYS_BUGRIGS_PLANAR_MOVEMENT getstati(STAT_BUGRIGS_PLANAR_MOVEMENT) + #define PHYS_BUGRIGS_REVERSE_SPEEDING getstati(STAT_BUGRIGS_REVERSE_SPEEDING) + #define PHYS_BUGRIGS_FRICTION_FLOOR getstatf(STAT_BUGRIGS_FRICTION_FLOOR) + #define PHYS_BUGRIGS_AIR_STEERING getstati(STAT_BUGRIGS_AIR_STEERING) + #define PHYS_BUGRIGS_FRICTION_BRAKE getstatf(STAT_BUGRIGS_FRICTION_BRAKE) + #define PHYS_BUGRIGS_ACCEL getstatf(STAT_BUGRIGS_ACCEL) + #define PHYS_BUGRIGS_SPEED_REF getstatf(STAT_BUGRIGS_SPEED_REF) + #define PHYS_BUGRIGS_SPEED_POW getstatf(STAT_BUGRIGS_SPEED_POW) + #define PHYS_BUGRIGS_STEER getstatf(STAT_BUGRIGS_STEER) + #define PHYS_BUGRIGS_FRICTION_AIR getstatf(STAT_BUGRIGS_FRICTION_AIR) + #define PHYS_BUGRIGS_CAR_JUMPING getstatf(STAT_BUGRIGS_CAR_JUMPING) + #define PHYS_BUGRIGS_REVERSE_SPINNING getstatf(STAT_BUGRIGS_REVERSE_SPINNING) + #define PHYS_BUGRIGS_REVERSE_STOPPING getstatf(STAT_BUGRIGS_REVERSE_STOPPING) + + #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_WALLFRICTION getstati(STAT_MOVEVARS_WALLFRICTION) + + #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_JETPACK_REVERSE_THRUST getstati(STAT_JETPACK_REVERSE_THRUST) + + #define PHYS_DODGING_FROZEN getstati(STAT_DODGING_FROZEN) + + #define PHYS_NOSTEP getstati(STAT_NOSTEP) + #define PHYS_JUMPSTEP getstati(STAT_MOVEVARS_JUMPSTEP) + + #elif defined(SVQC) + ++ bool Physics_Valid(string thecvar); ++ ++ .float discomode; ++ + .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_jetpack_reverse_thrust; ++ + .float stat_gameplayfix_easierwaterjump; + .float stat_gameplayfix_downtracesupportsongroundflag; + .float stat_gameplayfix_stepmultipletimes; + .float stat_gameplayfix_unstickplayers; + .float stat_gameplayfix_stepdown; + + .float stat_bugrigs; + .float stat_bugrigs_angle_smoothing; + .float stat_bugrigs_planar_movement; + .float stat_bugrigs_reverse_speeding; + .float stat_bugrigs_friction_floor; + .float stat_bugrigs_air_steering; + .float stat_bugrigs_friction_brake; + .float stat_bugrigs_accel; + .float stat_bugrigs_speed_ref; + .float stat_bugrigs_speed_pow; + .float stat_bugrigs_steer; + .float stat_bugrigs_friction_air; + .float stat_bugrigs_car_jumping; + .float stat_bugrigs_reverse_spinning; + .float stat_bugrigs_reverse_stopping; + ++ // new properties ++ .float stat_sv_jumpvelocity; ++ .float stat_sv_airaccel_qw_stretchfactor; ++ .float stat_sv_maxairstrafespeed; ++ .float stat_sv_maxairspeed; ++ .float stat_sv_airstrafeaccelerate; ++ .float stat_sv_warsowbunny_turnaccel; ++ .float stat_sv_airaccel_sideways_friction; ++ .float stat_sv_aircontrol; ++ .float stat_sv_aircontrol_power; ++ .float stat_sv_aircontrol_penalty; ++ .float stat_sv_warsowbunny_airforwardaccel; ++ .float stat_sv_warsowbunny_topspeed; ++ .float stat_sv_warsowbunny_accel; ++ .float stat_sv_warsowbunny_backtosideratio; ++ .float stat_sv_friction; ++ .float stat_sv_accelerate; ++ .float stat_sv_stopspeed; ++ .float stat_sv_airaccelerate; ++ .float stat_sv_airstopaccelerate; ++ + .float stat_nostep; + .float stat_jumpstep; + + #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 GAMEPLAYFIX_DOWNTRACEONGROUND cvar("sv_gameplayfix_downtracesupportsongroundflag") + #define GAMEPLAYFIX_STEPMULTIPLETIMES cvar("sv_gameplayfix_stepmultipletimes") + #define GAMEPLAYFIX_UNSTICKPLAYERS cvar("sv_gameplayfix_unstickplayers") + #define GAMEPLAYFIX_STEPDOWN cvar("sv_gameplayfix_stepdown") + + #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 IS_ONGROUND(s) !!(s.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 BUFFS(s) (s).buffs + + #define PHYS_AMMO_FUEL(s) s.ammo_fuel + + #define PHYS_FROZEN(s) s.frozen + + #define PHYS_DOUBLEJUMP autocvar_sv_doublejump + + #define PHYS_BUGRIGS g_bugrigs + #define PHYS_BUGRIGS_ANGLE_SMOOTHING g_bugrigs_angle_smoothing + #define PHYS_BUGRIGS_PLANAR_MOVEMENT g_bugrigs_planar_movement + #define PHYS_BUGRIGS_REVERSE_SPEEDING g_bugrigs_reverse_speeding + #define PHYS_BUGRIGS_FRICTION_FLOOR g_bugrigs_friction_floor + #define PHYS_BUGRIGS_AIR_STEERING g_bugrigs_air_steering + #define PHYS_BUGRIGS_FRICTION_BRAKE g_bugrigs_friction_brake + #define PHYS_BUGRIGS_ACCEL g_bugrigs_accel + #define PHYS_BUGRIGS_SPEED_REF g_bugrigs_speed_ref + #define PHYS_BUGRIGS_SPEED_POW g_bugrigs_speed_pow + #define PHYS_BUGRIGS_STEER g_bugrigs_steer + #define PHYS_BUGRIGS_FRICTION_AIR g_bugrigs_friction_air + #define PHYS_BUGRIGS_CAR_JUMPING g_bugrigs_planar_movement_car_jumping + #define PHYS_BUGRIGS_REVERSE_SPINNING g_bugrigs_reverse_spinning + #define PHYS_BUGRIGS_REVERSE_STOPPING g_bugrigs_reverse_stopping + + #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_ACCELERATE self.stat_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_AIRACCEL_QW_STRETCHFACTOR(s) self.stat_sv_airaccel_qw_stretchfactor ++ #define PHYS_AIRACCEL_SIDEWAYS_FRICTION self.stat_sv_airaccel_sideways_friction ++ #define PHYS_AIRACCELERATE self.stat_sv_airaccelerate ++ #define PHYS_AIRCONTROL self.stat_sv_aircontrol ++ #define PHYS_AIRCONTROL_PENALTY self.stat_sv_aircontrol_penalty ++ #define PHYS_AIRCONTROL_POWER self.stat_sv_aircontrol_power + #define PHYS_AIRSPEEDLIMIT_NONQW(s) s.stat_sv_airspeedlimit_nonqw - #define PHYS_AIRSTOPACCELERATE autocvar_sv_airstopaccelerate ++ #define PHYS_AIRSTOPACCELERATE self.stat_sv_airstopaccelerate + #define PHYS_AIRSTRAFEACCEL_QW(s) s.stat_sv_airstrafeaccel_qw - #define PHYS_AIRSTRAFEACCELERATE autocvar_sv_airstrafeaccelerate ++ #define PHYS_AIRSTRAFEACCELERATE self.stat_sv_airstrafeaccelerate + #define PHYS_ENTGRAVITY(s) s.gravity - #define PHYS_FRICTION autocvar_sv_friction ++ #define PHYS_FRICTION self.stat_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_JUMPVELOCITY self.stat_sv_jumpvelocity ++ #define PHYS_MAXAIRSPEED self.stat_sv_maxairspeed ++ #define PHYS_MAXAIRSTRAFESPEED self.stat_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_STOPSPEED self.stat_sv_stopspeed ++ #define PHYS_WARSOWBUNNY_ACCEL self.stat_sv_warsowbunny_accel ++ #define PHYS_WARSOWBUNNY_BACKTOSIDERATIO self.stat_sv_warsowbunny_backtosideratio ++ #define PHYS_WARSOWBUNNY_AIRFORWARDACCEL self.stat_sv_warsowbunny_airforwardaccel ++ #define PHYS_WARSOWBUNNY_TOPSPEED self.stat_sv_warsowbunny_topspeed ++ #define PHYS_WARSOWBUNNY_TURNACCEL self.stat_sv_warsowbunny_turnaccel + + #define PHYS_WALLFRICTION cvar("sv_wallfriction") + + #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_JETPACK_REVERSE_THRUST autocvar_g_jetpack_reverse_thrust + + #define PHYS_DODGING_FROZEN autocvar_sv_dodging_frozen + + #define PHYS_NOSTEP cvar("sv_nostep") + #define PHYS_JUMPSTEP cvar("sv_jumpstep") + + #endif + #endif diff --cc qcsrc/common/stats.qh index 2c6a1cd82,c3d67ca1a..cad96d93f --- a/qcsrc/common/stats.qh +++ b/qcsrc/common/stats.qh @@@ -195,59 -202,59 +195,59 @@@ const int STAT_VIP_PINK // 164 empty? // 165 empty? // 166 empty? --// 167 empty? --// 168 empty? - // 169 empty? - // 170 empty? - // 171 empty? - // 172 empty? - // 173 empty? - // 174 empty? - // 175 empty? - // 176 empty? - // 177 empty? - // 178 empty? - // 179 empty? - // 180 empty? - // 181 empty? - // 182 empty? - // 183 empty? - // 184 empty? - // 185 empty? - // 186 empty? - // 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_MULTIJUMP_MAXSPEED = 167; ++const int STAT_JETPACK_REVERSE_THRUST = 168; + const int STAT_BUGRIGS_REVERSE_STOPPING = 169; + const int STAT_BUGRIGS_REVERSE_SPINNING = 170; + const int STAT_BUGRIGS_CAR_JUMPING = 171; + const int STAT_BUGRIGS_FRICTION_AIR = 172; + const int STAT_BUGRIGS_STEER = 173; + const int STAT_BUGRIGS_SPEED_POW = 174; + const int STAT_BUGRIGS_SPEED_REF = 175; + const int STAT_BUGRIGS_ACCEL = 176; + const int STAT_BUGRIGS_FRICTION_BRAKE = 177; + const int STAT_BUGRIGS_AIR_STEERING = 178; + const int STAT_BUGRIGS_FRICTION_FLOOR = 179; + const int STAT_BUGRIGS_REVERSE_SPEEDING = 180; + const int STAT_BUGRIGS_PLANAR_MOVEMENT = 181; + const int STAT_BUGRIGS_ANGLE_SMOOTHING = 182; + const int STAT_BUGRIGS = 183; + const int STAT_GAMEPLAYFIX_STEPDOWN = 184; + const int STAT_MOVEVARS_JUMPSTEP = 185; + const int STAT_NOSTEP = 186; + const int STAT_GAMEPLAYFIX_UNSTICKPLAYERS = 187; + const int STAT_GAMEPLAYFIX_STEPMULTIPLETIMES = 188; + const int STAT_GAMEPLAYFIX_DOWNTRACEONGROUND = 189; + 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 --cc qcsrc/common/teams.qh index fd32b46f6,074835d5e..88b61187e --- a/qcsrc/common/teams.qh +++ b/qcsrc/common/teams.qh @@@ -160,8 -160,8 +160,13 @@@ float Team_TeamToNumber(float teamid #define TCR(input,teamcolor,teamtext) strreplace("^TC", teamcolor, strreplace("^TT", teamtext, input)) // safe team comparisons ++#ifdef SVQC +#define SAME_TEAM(a,b) (g_infection ? ((a.inf_team == b.inf_team) ? 1 : 0) : (teamplay ? ((a.team == b.team) ? 1 : 0) : ((a == b) ? 1 : 0))) +#define DIFF_TEAM(a,b) (g_infection ? ((a.inf_team != b.inf_team) ? 1 : 0) : (teamplay ? ((a.team != b.team) ? 1 : 0) : ((a != b) ? 1 : 0))) ++#elif defined(CSQC) + #define SAME_TEAM(a,b) (teamplay ? ((a.team == b.team) ? 1 : 0) : ((a == b) ? 1 : 0)) + #define DIFF_TEAM(a,b) (teamplay ? ((a.team != b.team) ? 1 : 0) : ((a != b) ? 1 : 0)) ++#endif // used for notification system multi-team identifiers #define APP_TEAM_NUM_2(num,prefix) ((num == NUM_TEAM_1) ? prefix##RED : prefix##BLUE) diff --cc qcsrc/common/triggers/func/breakable.qc index 000000000,9c371b5d4..94b70d96c mode 000000,100644..100644 --- a/qcsrc/common/triggers/func/breakable.qc +++ b/qcsrc/common/triggers/func/breakable.qc @@@ -1,0 -1,315 +1,316 @@@ + #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.pain_finished = time; + 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 --cc qcsrc/common/triggers/teleporters.qc index 000000000,5e91d7607..296bc8f9d mode 000000,100644..100644 --- a/qcsrc/common/triggers/teleporters.qc +++ b/qcsrc/common/triggers/teleporters.qc @@@ -1,0 -1,252 +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 "../turrets/sv_turrets.qh" ++ #include "../vehicles/sv_vehicles.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); ++ Send_Effect(EFFECT_TELEPORT, player.origin, '0 0 0', 1); ++ Send_Effect(EFFECT_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 --cc qcsrc/common/triggers/trigger/jumppads.qc index 000000000,61cd92bb8..a78ef9913 mode 000000,100644..100644 --- a/qcsrc/common/triggers/trigger/jumppads.qc +++ b/qcsrc/common/triggers/trigger/jumppads.qc @@@ -1,0 -1,476 +1,477 @@@ + // TODO: split target_push and put it in the target folder + #ifdef SVQC + #include "jumppads.qh" ++#include "../../effects.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); ++ Send_Effect(EFFECT_JUMPPAD, 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 --cc qcsrc/common/triggers/trigger/teleport.qc index 000000000,5ff5fcf33..d983cf3ae mode 000000,100644..100644 --- a/qcsrc/common/triggers/trigger/teleport.qc +++ b/qcsrc/common/triggers/trigger/teleport.qc @@@ -1,0 -1,77 +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) ++ if(IS_TURRET(other)) + 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 --cc qcsrc/common/triggers/triggers.qc index 000000000,fdffbb9e0..04225f84e mode 000000,100644..100644 --- a/qcsrc/common/triggers/triggers.qc +++ b/qcsrc/common/triggers/triggers.qc @@@ -1,0 -1,297 +1,330 @@@ + 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 ++#include "../../server/mutators/gamemode_infection.qh" ++ + 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 + ++float foreachtarget_callid; ++.float targethandled; ++void SUB_ForEachTarget(entity s, void(entity, float, vector, string, entity) cback, float recursive, float fdata, vector vdata, string sdata, entity edata) ++{ ++ float i; ++ string targname = ""; ++ entity targ; ++ ++ for(i = 0; i < 4; ++i) ++ { ++ switch(i) ++ { ++ case 0: targname = s.target ; break; ++ case 1: targname = s.target2; break; ++ case 2: targname = s.target3; break; ++ case 3: targname = s.target4; break; ++ } ++ ++ if(targname != "") ++ { ++ for(targ = world; (targ = find(targ, targetname, targname));) ++ if(targ && targ.use && targ.targethandled != foreachtarget_callid) ++ { ++ cback(targ, fdata, vdata, sdata, edata); ++ targ.targethandled = foreachtarget_callid; ++ ++ if(recursive) ++ SUB_ForEachTarget(targ, cback, recursive, fdata, vdata, sdata, edata); ++ } ++ } ++ } ++} ++ ++void SUB_ForEachTarget_Init() { ++foreachtarget_callid; } ++ + /* + ============================== + 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() ++void SUB_UseTargets_Ex(bool preventReuse) + { + 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(t.use && (t.sub_target_used != time || !preventReuse)) + { + if(stemp.target_random) + { + RandomSelection_Add(t, 0, string_null, 1, 0); + } + else + { + self = t; + other = stemp; + activator = act; + self.use(); ++ if(preventReuse) ++ self.sub_target_used = time; + } + } + } + } + + if(stemp.target_random && RandomSelection_chosen_ent) + { + self = RandomSelection_chosen_ent; + other = stemp; + activator = act; + self.use(); ++ if(preventReuse) ++ self.sub_target_used = time; + } + + activator = act; + self = stemp; + other = otemp; + } + ++void SUB_UseTargets() { SUB_UseTargets_Ex(false); } ++void SUB_UseTargets_PreventReuse() { SUB_UseTargets_Ex(true); } ++ + #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 --cc qcsrc/common/triggers/triggers.qh index 000000000,c50d95b89..8dd7618d3 mode 000000,100644..100644 --- a/qcsrc/common/triggers/triggers.qh +++ b/qcsrc/common/triggers/triggers.qh @@@ -1,0 -1,58 +1,64 @@@ + #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); ++ ++void SUB_ForEachTarget_Init(); ++void SUB_ForEachTarget(entity s, void(entity, float, vector, string, entity) cback, float recursive, float fdata, vector vdata, string sdata, entity edata); ++void SUB_UseTargets_PreventReuse(); + #endif + ++.bool sub_target_used; ++ + .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 diff --cc qcsrc/common/vehicles/unit/racer.qc index 860555646,000000000..837000329 mode 100644,000000..100644 --- a/qcsrc/common/vehicles/unit/racer.qc +++ b/qcsrc/common/vehicles/unit/racer.qc @@@ -1,941 -1,0 +1,942 @@@ +#ifdef REGISTER_VEHICLE +REGISTER_VEHICLE( +/* VEH_##id */ RACER, +/* function */ v_racer, +/* spawnflags */ VHF_DMGSHAKE | VHF_DMGROLL, +/* mins,maxs */ '-120 -120 -40' * 0.5, '120 120 40' * 0.5, +/* model */ "models/vehicles/wakizashi.dpm", +/* head_model */ "null", +/* hud_model */ "models/vehicles/wakizashi_cockpit.dpm", +/* tags */ "", "", "tag_viewport", +/* netname */ "racer", +/* fullname */ _("Racer") +); +#else +#ifdef SVQC +#include "../../effects.qh" ++#include "../../triggers/trigger/impulse.qh" + +bool autocvar_g_vehicle_racer; + +float autocvar_g_vehicle_racer_speed_afterburn; +float autocvar_g_vehicle_racer_afterburn_cost; + +float autocvar_g_vehicle_racer_waterburn_cost; +float autocvar_g_vehicle_racer_waterburn_speed; + +float autocvar_g_vehicle_racer_water_speed_forward; +float autocvar_g_vehicle_racer_water_speed_strafe; + +float autocvar_g_vehicle_racer_pitchlimit = 30; + +float autocvar_g_vehicle_racer_water_downforce = 0.03; +float autocvar_g_vehicle_racer_water_upforcedamper = 15; + +float autocvar_g_vehicle_racer_anglestabilizer; +float autocvar_g_vehicle_racer_downforce; + +float autocvar_g_vehicle_racer_speed_forward; +float autocvar_g_vehicle_racer_speed_strafe; +float autocvar_g_vehicle_racer_springlength; +float autocvar_g_vehicle_racer_upforcedamper; +float autocvar_g_vehicle_racer_friction; + +float autocvar_g_vehicle_racer_water_time = 5; + +float autocvar_g_vehicle_racer_hovertype; +float autocvar_g_vehicle_racer_hoverpower; + +float autocvar_g_vehicle_racer_turnroll; +float autocvar_g_vehicle_racer_turnspeed; +float autocvar_g_vehicle_racer_pitchspeed; + +float autocvar_g_vehicle_racer_energy; +float autocvar_g_vehicle_racer_energy_regen; +float autocvar_g_vehicle_racer_energy_regen_pause; + +float autocvar_g_vehicle_racer_health; +float autocvar_g_vehicle_racer_health_regen; +float autocvar_g_vehicle_racer_health_regen_pause; + +float autocvar_g_vehicle_racer_shield; +float autocvar_g_vehicle_racer_shield_regen; +float autocvar_g_vehicle_racer_shield_regen_pause; + +float autocvar_g_vehicle_racer_cannon_cost; +float autocvar_g_vehicle_racer_cannon_damage; +float autocvar_g_vehicle_racer_cannon_radius; +float autocvar_g_vehicle_racer_cannon_refire; +float autocvar_g_vehicle_racer_cannon_speed; +float autocvar_g_vehicle_racer_cannon_spread; +float autocvar_g_vehicle_racer_cannon_force; + +float autocvar_g_vehicle_racer_rocket_accel; +float autocvar_g_vehicle_racer_rocket_damage; +float autocvar_g_vehicle_racer_rocket_radius; +float autocvar_g_vehicle_racer_rocket_force; +float autocvar_g_vehicle_racer_rocket_refire; +float autocvar_g_vehicle_racer_rocket_speed; +float autocvar_g_vehicle_racer_rocket_turnrate; + +float autocvar_g_vehicle_racer_rocket_locktarget; +float autocvar_g_vehicle_racer_rocket_locking_time; +float autocvar_g_vehicle_racer_rocket_locking_releasetime; +float autocvar_g_vehicle_racer_rocket_locked_time; +float autocvar_g_vehicle_racer_rocket_locked_maxangle; +float autocvar_g_vehicle_racer_rocket_climbspeed; + +float autocvar_g_vehicle_racer_respawntime; + +float autocvar_g_vehicle_racer_blowup_radius; +float autocvar_g_vehicle_racer_blowup_coredamage; +float autocvar_g_vehicle_racer_blowup_edgedamage; +float autocvar_g_vehicle_racer_blowup_forceintensity; + +float autocvar_g_vehicle_racer_bouncefactor; +float autocvar_g_vehicle_racer_bouncestop; +vector autocvar_g_vehicle_racer_bouncepain; + +.float racer_watertime; + +var vector racer_force_from_tag(string tag_name, float spring_length, float max_power); + +void racer_align4point(float _delta) +{ + vector push_vector; + float fl_push, fr_push, bl_push, br_push; + + push_vector = racer_force_from_tag("tag_engine_fr", autocvar_g_vehicle_racer_springlength, autocvar_g_vehicle_racer_hoverpower); + fr_push = force_fromtag_normpower; + //vehicles_sweap_collision(force_fromtag_origin, self.velocity, _delta, v_add, autocvar_g_vehicle_racer_collision_multiplier); + + push_vector += racer_force_from_tag("tag_engine_fl", autocvar_g_vehicle_racer_springlength, autocvar_g_vehicle_racer_hoverpower); + fl_push = force_fromtag_normpower; + //vehicles_sweap_collision(force_fromtag_origin, self.velocity, _delta, v_add, autocvar_g_vehicle_racer_collision_multiplier); + + push_vector += racer_force_from_tag("tag_engine_br", autocvar_g_vehicle_racer_springlength, autocvar_g_vehicle_racer_hoverpower); + br_push = force_fromtag_normpower; + //vehicles_sweap_collision(force_fromtag_origin, self.velocity, _delta, v_add, autocvar_g_vehicle_racer_collision_multiplier); + + push_vector += racer_force_from_tag("tag_engine_bl", autocvar_g_vehicle_racer_springlength, autocvar_g_vehicle_racer_hoverpower); + bl_push = force_fromtag_normpower; + //vehicles_sweap_collision(force_fromtag_origin, self.velocity, _delta, v_add, autocvar_g_vehicle_racer_collision_multiplier); + + self.velocity += push_vector * _delta; + + float uforce = autocvar_g_vehicle_racer_upforcedamper; + + int cont = pointcontents(self.origin - '0 0 64'); + if(cont == CONTENT_WATER || cont == CONTENT_LAVA || cont == CONTENT_SLIME) + { + uforce = autocvar_g_vehicle_racer_water_upforcedamper; + + if(self.owner.BUTTON_CROUCH && time < self.air_finished) + self.velocity_z += 30; + else + self.velocity_z += 200; + } + + + // Anti ocilation + if(self.velocity_z > 0) + self.velocity_z *= 1 - uforce * _delta; + + push_vector_x = (fl_push - bl_push); + push_vector_x += (fr_push - br_push); + push_vector_x *= 360; + + push_vector_z = (fr_push - fl_push); + push_vector_z += (br_push - bl_push); + push_vector_z *= 360; + + // Apply angle diffrance + self.angles_z += push_vector_z * _delta; + self.angles_x += push_vector_x * _delta; + + // Apply stabilizer + self.angles_x *= 1 - (autocvar_g_vehicle_racer_anglestabilizer * _delta); + self.angles_z *= 1 - (autocvar_g_vehicle_racer_anglestabilizer * _delta); +} + +void racer_fire_cannon(string tagname) +{ + vector v; + entity bolt; + + v = gettaginfo(self, gettagindex(self, tagname)); + bolt = vehicles_projectile(EFFECT_RACER_MUZZLEFLASH, W_Sound("lasergun_fire"), + v, normalize(v_forward + randomvec() * autocvar_g_vehicle_racer_cannon_spread) * autocvar_g_vehicle_racer_cannon_speed, + autocvar_g_vehicle_racer_cannon_damage, autocvar_g_vehicle_racer_cannon_radius, autocvar_g_vehicle_racer_cannon_force, 0, + DEATH_VH_WAKI_GUN, PROJECTILE_WAKICANNON, 0, true, true, self.owner); + + // Fix z-aim (for chase mode) + v = normalize(trace_endpos - bolt.origin); + v_forward_z = v_z * 0.5; + bolt.velocity = v_forward * autocvar_g_vehicle_racer_cannon_speed; +} + +void racer_rocket_groundhugger() +{ + vector olddir, newdir; + float oldvel, newvel; + + self.nextthink = time; + + if(self.owner.deadflag != DEAD_NO || self.cnt < time) + { + self.use(); + return; + } + + if(!self.realowner.vehicle) + { + UpdateCSQCProjectile(self); + return; + } + + olddir = normalize(self.velocity); + oldvel = vlen(self.velocity); + newvel = oldvel + self.lip; + + tracebox(self.origin, self.mins, self.maxs, self.origin + olddir * 64, MOVE_WORLDONLY,self); + if(trace_fraction <= 0.5) + { + // Hitting somethign soon, just speed ahead + self.velocity = olddir * newvel; + UpdateCSQCProjectile(self); + return; + } + + traceline(trace_endpos, trace_endpos - '0 0 64', MOVE_NORMAL, self); + if(trace_fraction != 1.0) + { + newdir = normalize(trace_endpos + '0 0 64' - self.origin) * autocvar_g_vehicle_racer_rocket_turnrate; + self.velocity = normalize(olddir + newdir) * newvel; + } + else + { + self.velocity = olddir * newvel; + self.velocity_z -= 1600 * sys_frametime; // 2x grav looks better for this one + } + + int cont = pointcontents(self.origin - '0 0 32'); + if(cont == CONTENT_WATER || cont == CONTENT_LAVA || cont == CONTENT_SLIME) + self.velocity_z += 200; + + UpdateCSQCProjectile(self); + return; +} + +void racer_rocket_tracker() +{ + vector olddir, newdir; + float oldvel, newvel; + + self.nextthink = time; + + if (self.owner.deadflag != DEAD_NO || self.cnt < time) + { + self.use(); + return; + } + + if(!self.realowner.vehicle) + { + UpdateCSQCProjectile(self); + return; + } + + olddir = normalize(self.velocity); + oldvel = vlen(self.velocity); + newvel = oldvel + self.lip; + makevectors(vectoangles(olddir)); + + float time_to_impact = min(vlen(self.enemy.origin - self.origin) / vlen(self.velocity), 1); + vector predicted_origin = self.enemy.origin + self.enemy.velocity * time_to_impact; + + traceline(self.origin, self.origin + v_forward * 64 - '0 0 32', MOVE_NORMAL, self); + newdir = normalize(predicted_origin - self.origin); + + //vector + float height_diff = predicted_origin_z - self.origin_z; + + if(vlen(newdir - v_forward) > autocvar_g_vehicle_racer_rocket_locked_maxangle) + { + //bprint("Target lost!\n"); + //dprint("OF:", ftos(vlen(newdir - v_forward)), "\n"); + self.think = racer_rocket_groundhugger; + return; + } + + if(trace_fraction != 1.0 && trace_ent != self.enemy) + newdir_z += 16 * sys_frametime; + + self.velocity = normalize(olddir + newdir * autocvar_g_vehicle_racer_rocket_turnrate) * newvel; + self.velocity_z -= 800 * sys_frametime; + self.velocity_z += max(height_diff, autocvar_g_vehicle_racer_rocket_climbspeed) * sys_frametime ; + + UpdateCSQCProjectile(self); + return; +} + +void racer_fire_rocket(string tagname, entity trg) +{ + vector v = gettaginfo(self, gettagindex(self, tagname)); + entity rocket = vehicles_projectile(EFFECT_RACER_ROCKETLAUNCH, W_Sound("rocket_fire"), + v, v_forward * autocvar_g_vehicle_racer_rocket_speed, + autocvar_g_vehicle_racer_rocket_damage, autocvar_g_vehicle_racer_rocket_radius, autocvar_g_vehicle_racer_rocket_force, 3, + DEATH_VH_WAKI_ROCKET, PROJECTILE_WAKIROCKET, 20, false, false, self.owner); + + rocket.lip = autocvar_g_vehicle_racer_rocket_accel * sys_frametime; + rocket.wait = autocvar_g_vehicle_racer_rocket_turnrate; + rocket.nextthink = time; + rocket.enemy = trg; + rocket.cnt = time + 15; + + if(trg) + rocket.think = racer_rocket_tracker; + else + rocket.think = racer_rocket_groundhugger; +} + +float racer_frame() +{ + entity player, racer; + vector df; + float ftmp; + + if(intermission_running) + { + self.vehicle.velocity = '0 0 0'; + self.vehicle.avelocity = '0 0 0'; + return 1; + } + + player = self; + racer = self.vehicle; + self = racer; + + vehicles_painframe(); + + if(pointcontents(racer.origin) != CONTENT_WATER) + racer.air_finished = time + autocvar_g_vehicle_racer_water_time; + + if(racer.deadflag != DEAD_NO) + { + self = player; + player.BUTTON_ATCK = player.BUTTON_ATCK2 = 0; + return 1; + } + + racer_align4point(frametime); + + player.BUTTON_ZOOM = player.BUTTON_CROUCH = 0; + + crosshair_trace(player); + + racer.angles_x *= -1; + + // Yaw + ftmp = autocvar_g_vehicle_racer_turnspeed * frametime; + ftmp = bound(-ftmp, shortangle_f(player.v_angle_y - racer.angles_y, racer.angles_y), ftmp); + racer.angles_y = anglemods(racer.angles_y + ftmp); + + // Roll + racer.angles_z += -ftmp * autocvar_g_vehicle_racer_turnroll * frametime; + + // Pitch + ftmp = autocvar_g_vehicle_racer_pitchspeed * frametime; + ftmp = bound(-ftmp, shortangle_f(player.v_angle_x - racer.angles_x, racer.angles_x), ftmp); + racer.angles_x = bound(-autocvar_g_vehicle_racer_pitchlimit, anglemods(racer.angles_x + ftmp), autocvar_g_vehicle_racer_pitchlimit); + + makevectors(racer.angles); + racer.angles_x *= -1; + + //ftmp = racer.velocity_z; + df = racer.velocity * -autocvar_g_vehicle_racer_friction; + //racer.velocity_z = ftmp; + + int cont = pointcontents(racer.origin); + if(vlen(player.movement) != 0) + { + if(cont == CONTENT_WATER || cont == CONTENT_LAVA || cont == CONTENT_SLIME) + { + if(player.movement_x) { df += v_forward * ((player.movement_x > 0) ? autocvar_g_vehicle_racer_water_speed_forward : -autocvar_g_vehicle_racer_water_speed_forward); } + if(player.movement_y) { df += v_right * ((player.movement_y > 0) ? autocvar_g_vehicle_racer_water_speed_strafe : -autocvar_g_vehicle_racer_water_speed_strafe); } + } + else + { + if(player.movement_x) { df += v_forward * ((player.movement_x > 0) ? autocvar_g_vehicle_racer_speed_forward : -autocvar_g_vehicle_racer_speed_forward); } + if(player.movement_y) { df += v_right * ((player.movement_y > 0) ? autocvar_g_vehicle_racer_speed_strafe : -autocvar_g_vehicle_racer_speed_strafe); } + } + + if(self.sound_nexttime < time || self.sounds != 1) + { + self.sounds = 1; + self.sound_nexttime = time + 10.922667; //soundlength("vehicles/racer_move.wav"); + sound (self, CH_TRIGGER_SINGLE, "vehicles/racer_move.wav", VOL_VEHICLEENGINE, ATTEN_NORM); + } + } + else + { + if(self.sound_nexttime < time || self.sounds != 0) + { + self.sounds = 0; + self.sound_nexttime = time + 11.888604; //soundlength("vehicles/racer_idle.wav"); + sound (self, CH_TRIGGER_SINGLE, "vehicles/racer_idle.wav", VOL_VEHICLEENGINE, ATTEN_NORM); + } + } + + // Afterburn + if (player.BUTTON_JUMP && racer.vehicle_energy >= (autocvar_g_vehicle_racer_afterburn_cost * frametime)) + { + if(time - racer.wait > 0.2) + pointparticles(particleeffectnum("wakizashi_booster_smoke"), self.origin - v_forward * 32, v_forward * vlen(self.velocity), 1); + + racer.wait = time; + + if(cont == CONTENT_WATER || cont == CONTENT_LAVA || cont == CONTENT_SLIME) + { + racer.vehicle_energy -= autocvar_g_vehicle_racer_waterburn_cost * frametime; + df += (v_forward * autocvar_g_vehicle_racer_waterburn_speed); + } + else + { + racer.vehicle_energy -= autocvar_g_vehicle_racer_afterburn_cost * frametime; + df += (v_forward * autocvar_g_vehicle_racer_speed_afterburn); + } + + if(racer.invincible_finished < time) + { + traceline(racer.origin, racer.origin - '0 0 256', MOVE_NORMAL, self); + if(trace_fraction != 1.0) + pointparticles(particleeffectnum("smoke_small"), trace_endpos, '0 0 0', 1); + + racer.invincible_finished = time + 0.1 + (random() * 0.1); + } + + if(racer.strength_finished < time) + { + racer.strength_finished = time + 10.922667; //soundlength("vehicles/racer_boost.wav"); + sound (racer.tur_head, CH_TRIGGER_SINGLE, "vehicles/racer_boost.wav", VOL_VEHICLEENGINE, ATTEN_NORM); + } + } + else + { + racer.strength_finished = 0; + sound (racer.tur_head, CH_TRIGGER_SINGLE, "misc/null.wav", VOL_VEHICLEENGINE, ATTEN_NORM); + } + + if(cont == CONTENT_WATER || cont == CONTENT_LAVA || cont == CONTENT_SLIME) + racer.racer_watertime = time; + + float dforce = autocvar_g_vehicle_racer_downforce; + if(time - racer.racer_watertime <= 3) + dforce = autocvar_g_vehicle_racer_water_downforce; + + df -= v_up * (vlen(racer.velocity) * dforce); + player.movement = racer.velocity += df * frametime; + + if(!forbidWeaponUse(player)) + if(player.BUTTON_ATCK) + if(time > racer.attack_finished_single) + if(racer.vehicle_energy >= autocvar_g_vehicle_racer_cannon_cost) + { + racer.vehicle_energy -= autocvar_g_vehicle_racer_cannon_cost; + racer.wait = time; + + crosshair_trace(player); + if(racer.cnt) + { + racer_fire_cannon("tag_fire1"); + racer.cnt = 0; + } + else + { + racer_fire_cannon("tag_fire2"); + racer.cnt = 1; + } + racer.attack_finished_single = time + autocvar_g_vehicle_racer_cannon_refire; + } + + if(autocvar_g_vehicle_racer_rocket_locktarget) + { + vehicles_locktarget((1 / autocvar_g_vehicle_racer_rocket_locking_time) * frametime, + (1 / autocvar_g_vehicle_racer_rocket_locking_releasetime) * frametime, + autocvar_g_vehicle_racer_rocket_locked_time); + + if(self.lock_target) + { + if(racer.lock_strength == 1) + UpdateAuxiliaryXhair(player, real_origin(self.lock_target), '1 0 0', 0); + else if(self.lock_strength > 0.5) + UpdateAuxiliaryXhair(player, real_origin(self.lock_target), '0 1 0', 0); + else if(self.lock_strength < 0.5) + UpdateAuxiliaryXhair(player, real_origin(self.lock_target), '0 0 1', 0); + } + } + + if(!forbidWeaponUse(player)) + if(time > racer.delay) + if(player.BUTTON_ATCK2) + { + racer.misc_bulletcounter += 1; + racer.delay = time + 0.3; + + if(racer.misc_bulletcounter == 1) + racer_fire_rocket("tag_rocket_r", (racer.lock_strength == 1 && racer.lock_target) ? racer.lock_target : world); + else if(racer.misc_bulletcounter == 2) + { + racer_fire_rocket("tag_rocket_l", (racer.lock_strength == 1 && racer.lock_target) ? racer.lock_target : world); + racer.lock_strength = 0; + racer.lock_target = world; + racer.misc_bulletcounter = 0; + + racer.delay = time + autocvar_g_vehicle_racer_rocket_refire; + racer.lip = time; + } + } + player.vehicle_reload1 = bound(0, 100 * ((time - racer.lip) / (racer.delay - racer.lip)), 100); + + if(racer.vehicle_flags & VHF_SHIELDREGEN) + vehicles_regen(racer.dmg_time, vehicle_shield, autocvar_g_vehicle_racer_shield, autocvar_g_vehicle_racer_shield_regen_pause, autocvar_g_vehicle_racer_shield_regen, frametime, true); + + if(racer.vehicle_flags & VHF_HEALTHREGEN) + vehicles_regen(racer.dmg_time, vehicle_health, autocvar_g_vehicle_racer_health, autocvar_g_vehicle_racer_health_regen_pause, autocvar_g_vehicle_racer_health_regen, frametime, false); + + if(racer.vehicle_flags & VHF_ENERGYREGEN) + vehicles_regen(racer.wait, vehicle_energy, autocvar_g_vehicle_racer_energy, autocvar_g_vehicle_racer_energy_regen_pause, autocvar_g_vehicle_racer_energy_regen, frametime, false); + + + VEHICLE_UPDATE_PLAYER(player, health, racer); + VEHICLE_UPDATE_PLAYER(player, energy, racer); + + if(racer.vehicle_flags & VHF_HASSHIELD) + VEHICLE_UPDATE_PLAYER(player, shield, racer); + + player.BUTTON_ATCK = player.BUTTON_ATCK2 = 0; + setorigin(player,racer.origin + '0 0 32'); + player.velocity = racer.velocity; + + self = player; + return 1; +} + +void racer_think() +{ + self.nextthink = time; + + float pushdeltatime = time - self.lastpushtime; + if (pushdeltatime > 0.15) pushdeltatime = 0; + self.lastpushtime = time; + if(!pushdeltatime) return; + + tracebox(self.origin, self.mins, self.maxs, self.origin - ('0 0 1' * autocvar_g_vehicle_racer_springlength), MOVE_NOMONSTERS, self); + + vector df = self.velocity * -autocvar_g_vehicle_racer_friction; + df_z += (1 - trace_fraction) * autocvar_g_vehicle_racer_hoverpower + sin(time * 2) * (autocvar_g_vehicle_racer_springlength * 2); + + float forced = autocvar_g_vehicle_racer_upforcedamper; + + int cont = pointcontents(self.origin - '0 0 64'); + if(cont == CONTENT_WATER || cont == CONTENT_LAVA || cont == CONTENT_SLIME) + { + forced = autocvar_g_vehicle_racer_water_upforcedamper; + self.velocity_z += 200; + } + + self.velocity += df * pushdeltatime; + if(self.velocity_z > 0) + self.velocity_z *= 1 - forced * pushdeltatime; + + self.angles_x *= 1 - (autocvar_g_vehicle_racer_anglestabilizer * pushdeltatime); + self.angles_z *= 1 - (autocvar_g_vehicle_racer_anglestabilizer * pushdeltatime); + + CSQCMODEL_AUTOUPDATE(); +} + +void racer_exit(float eject) +{ + vector spot; + + self.think = racer_think; + self.nextthink = time; + self.movetype = MOVETYPE_BOUNCE; + sound (self.tur_head, CH_TRIGGER_SINGLE, "misc/null.wav", VOL_VEHICLEENGINE, ATTEN_NORM); + + if(!self.owner) + return; + + makevectors(self.angles); + if(eject) + { + spot = self.origin + v_forward * 100 + '0 0 64'; + spot = vehicles_findgoodexit(spot); + setorigin(self.owner , spot); + self.owner.velocity = (v_up + v_forward * 0.25) * 750; + self.owner.oldvelocity = self.owner.velocity; + } + else + { + if(vlen(self.velocity) > 2 * autocvar_sv_maxairspeed) + { + self.owner.velocity = normalize(self.velocity) * autocvar_sv_maxairspeed * 2; + self.owner.velocity_z += 200; + spot = self.origin + v_forward * 32 + '0 0 32'; + spot = vehicles_findgoodexit(spot); + } + else + { + self.owner.velocity = self.velocity * 0.5; + self.owner.velocity_z += 10; + spot = self.origin - v_forward * 200 + '0 0 32'; + spot = vehicles_findgoodexit(spot); + } + self.owner.oldvelocity = self.owner.velocity; + setorigin(self.owner , spot); + } + antilag_clear(self.owner); + self.owner = world; +} + +void racer_blowup() +{ + self.deadflag = DEAD_DEAD; + self.vehicle_exit(VHEF_NORMAL); + + RadiusDamage (self, self.enemy, autocvar_g_vehicle_racer_blowup_coredamage, + autocvar_g_vehicle_racer_blowup_edgedamage, + autocvar_g_vehicle_racer_blowup_radius, world, world, + autocvar_g_vehicle_racer_blowup_forceintensity, + DEATH_VH_WAKI_DEATH, world); + + self.nextthink = time + autocvar_g_vehicle_racer_respawntime; + self.think = vehicles_spawn; + self.movetype = MOVETYPE_NONE; + self.effects = EF_NODRAW; + + self.colormod = '0 0 0'; + self.avelocity = '0 0 0'; + self.velocity = '0 0 0'; + + setorigin(self, self.pos1); +} + +void racer_blowup_think() +{ + self.nextthink = time; + + if(time >= self.delay) + racer_blowup(); + + CSQCMODEL_AUTOUPDATE(); +} + +void racer_deadtouch() +{ + self.avelocity_x *= 0.7; + self.cnt -= 1; + if(self.cnt <= 0) + racer_blowup(); +} + +void spawnfunc_vehicle_racer() +{ + if(!autocvar_g_vehicle_racer) { remove(self); return; } + if(!vehicle_initialize(VEH_RACER, false)) { remove(self); return; } +} + +float v_racer(float req) +{ + switch(req) + { + case VR_IMPACT: + { + if(autocvar_g_vehicle_racer_bouncepain) + vehicles_impact(autocvar_g_vehicle_racer_bouncepain_x, autocvar_g_vehicle_racer_bouncepain_y, autocvar_g_vehicle_racer_bouncepain_z); + return true; + } + case VR_ENTER: + { + self.movetype = MOVETYPE_BOUNCE; + self.owner.vehicle_health = (self.vehicle_health / autocvar_g_vehicle_racer_health) * 100; + self.owner.vehicle_shield = (self.vehicle_shield / autocvar_g_vehicle_racer_shield) * 100; + + if(self.owner.flagcarried) + setorigin(self.owner.flagcarried, '-190 0 96'); + + return true; + } + case VR_THINK: + { + return true; + } + case VR_DEATH: + { + self.health = 0; + self.event_damage = func_null; + self.solid = SOLID_CORPSE; + self.takedamage = DAMAGE_NO; + self.deadflag = DEAD_DYING; + self.movetype = MOVETYPE_BOUNCE; + self.wait = time; + self.delay = 2 + time + random() * 3; + self.cnt = 1 + random() * 2; + self.touch = racer_deadtouch; + + Send_Effect(EFFECT_EXPLOSION_MEDIUM, self.origin, '0 0 0', 1); + + if(random() < 0.5) + self.avelocity_z = 32; + else + self.avelocity_z = -32; + + self.avelocity_x = -vlen(self.velocity) * 0.2; + self.velocity += '0 0 700'; + self.colormod = '-0.5 -0.5 -0.5'; + + self.think = racer_blowup_think; + self.nextthink = time; + + return true; + } + case VR_SPAWN: + { + if(self.scale != 0.5) + { + if(autocvar_g_vehicle_racer_hovertype != 0) + racer_force_from_tag = vehicles_force_fromtag_maglev; + else + racer_force_from_tag = vehicles_force_fromtag_hover; + + // FIXME: this be hakkz, fix the models insted (scale body, add tag_viewport to the hudmodel). + self.scale = 0.5; + setattachment(self.vehicle_hudmodel, self, ""); + setattachment(self.vehicle_viewport, self, "tag_viewport"); + + self.mass = 900; + } + + self.think = racer_think; + self.nextthink = time; + self.vehicle_health = autocvar_g_vehicle_racer_health; + self.vehicle_shield = autocvar_g_vehicle_racer_shield; + + self.movetype = MOVETYPE_TOSS; + self.solid = SOLID_SLIDEBOX; + self.delay = time; + self.scale = 0.5; + + self.PlayerPhysplug = racer_frame; + + self.bouncefactor = autocvar_g_vehicle_racer_bouncefactor; + self.bouncestop = autocvar_g_vehicle_racer_bouncestop; + self.damageforcescale = 0.5; + self.vehicle_health = autocvar_g_vehicle_racer_health; + self.vehicle_shield = autocvar_g_vehicle_racer_shield; + + return true; + } + case VR_SETUP: + { + if(autocvar_g_vehicle_racer_energy) + if(autocvar_g_vehicle_racer_energy_regen) + self.vehicle_flags |= VHF_ENERGYREGEN; + + if(autocvar_g_vehicle_racer_shield) + self.vehicle_flags |= VHF_HASSHIELD; + + if(autocvar_g_vehicle_racer_shield_regen) + self.vehicle_flags |= VHF_SHIELDREGEN; + + if(autocvar_g_vehicle_racer_health_regen) + self.vehicle_flags |= VHF_HEALTHREGEN; + + self.vehicle_exit = racer_exit; + self.respawntime = autocvar_g_vehicle_racer_respawntime; + self.vehicle_health = autocvar_g_vehicle_racer_health; + self.vehicle_shield = autocvar_g_vehicle_racer_shield; + self.max_health = self.vehicle_health; + + return true; + } + case VR_PRECACHE: + { + precache_sound (W_Sound("lasergun_fire")); + precache_sound (W_Sound("rocket_fire")); + + precache_sound ("vehicles/racer_idle.wav"); + precache_sound ("vehicles/racer_move.wav"); + precache_sound ("vehicles/racer_boost.wav"); + + precache_model ("models/vhshield.md3"); + precache_model ("models/vehicles/wakizashi.dpm"); + precache_model ("models/vehicles/wakizashi_cockpit.dpm"); + return true; + } + } + + return true; +} + +#endif // SVQC +#ifdef CSQC + +#define waki_ico "gfx/vehicles/waki.tga" +#define waki_eng "gfx/vehicles/waki_e.tga" +#define waki_gun "gfx/vehicles/waki_guns.tga" +#define waki_rkt "gfx/vehicles/waki_rockets.tga" +#define waki_xhair "gfx/vehicles/axh-special1.tga" + +float v_racer(float req) +{ + switch(req) + { + case VR_HUD: + { + if(autocvar_r_letterbox) + return true; + + vector picsize, hudloc = '0 0 0', pic2size, picloc; + + // Fetch health & ammo stats + HUD_GETVEHICLESTATS + + picsize = draw_getimagesize(hud_bg) * autocvar_cl_vehicles_hudscale; + hudloc_y = vid_conheight - picsize_y; + hudloc_x = vid_conwidth * 0.5 - picsize_x * 0.5; + + drawpic(hudloc, hud_bg, picsize, '1 1 1', autocvar_cl_vehicles_hudalpha, DRAWFLAG_NORMAL); + + shield *= 0.01; + vh_health *= 0.01; + energy *= 0.01; + reload1 *= 0.01; + + pic2size = draw_getimagesize(waki_ico) * (autocvar_cl_vehicles_hudscale * 0.8); + picloc = picsize * 0.5 - pic2size * 0.5; + if(vh_health < 0.25) + drawpic(hudloc + picloc, waki_ico, pic2size, '1 0 0' + '0 1 1' * sin(time * 8), 1, DRAWFLAG_NORMAL); + else + drawpic(hudloc + picloc, waki_ico, pic2size, '1 1 1' * vh_health + '1 0 0' * (1 - vh_health), 1, DRAWFLAG_NORMAL); + drawpic(hudloc + picloc, waki_eng, pic2size, '1 1 1' * energy + '1 0 0' * (1 - energy), 1, DRAWFLAG_NORMAL); + drawpic(hudloc + picloc, waki_gun, pic2size, '1 1 1' * energy + '1 0 0' * (1 - energy), 1, DRAWFLAG_NORMAL); + drawpic(hudloc + picloc, waki_rkt, pic2size, '1 1 1' * reload1 + '1 0 0' * (1 - reload1), 1, DRAWFLAG_NORMAL); + drawpic(hudloc + picloc, hud_sh, pic2size, '1 1 1', shield, DRAWFLAG_NORMAL); + + // Health bar + picsize = draw_getimagesize(hud_hp_bar) * autocvar_cl_vehicles_hudscale; + picloc = '69 69 0' * autocvar_cl_vehicles_hudscale; + drawsetcliparea(hudloc_x + picloc_x + (picsize_x * (1 - vh_health)), 0, vid_conwidth, vid_conheight); + drawpic(hudloc + picloc, hud_hp_bar, picsize, '1 1 1', 1 , DRAWFLAG_NORMAL); + drawresetcliparea(); + // .. and icon + picsize = draw_getimagesize(hud_hp_ico) * autocvar_cl_vehicles_hudscale; + picloc = '37 65 0' * autocvar_cl_vehicles_hudscale; + if(vh_health < 0.25) + { + if(alarm1time < time) + { + alarm1time = time + 2; + vehicle_alarm(self, CH_PAIN_SINGLE, "vehicles/alarm.wav"); + } + + drawpic(hudloc + picloc, hud_hp_ico, picsize, '1 0 0' + '0 1 1' * sin(time * 8), 1, DRAWFLAG_NORMAL); + } + else + { + drawpic(hudloc + picloc, hud_hp_ico, picsize, '1 1 1', 1, DRAWFLAG_NORMAL); + if(alarm1time) + { + vehicle_alarm(self, CH_PAIN_SINGLE, "misc/null.wav"); + alarm1time = 0; + } + } + + + // Shield bar + picsize = draw_getimagesize(hud_sh_bar) * autocvar_cl_vehicles_hudscale; + picloc = '69 140 0' * autocvar_cl_vehicles_hudscale; + drawsetcliparea(hudloc_x + picloc_x + (picsize_x * (1 - shield)), 0, vid_conwidth, vid_conheight); + drawpic(hudloc + picloc, hud_sh_bar, picsize, '1 1 1', 1, DRAWFLAG_NORMAL); + drawresetcliparea(); + // .. and icon + picloc = '40 136 0' * autocvar_cl_vehicles_hudscale; + picsize = draw_getimagesize(hud_sh_ico) * autocvar_cl_vehicles_hudscale; + if(shield < 0.25) + { + if(alarm2time < time) + { + alarm2time = time + 1; + vehicle_alarm(self, CH_TRIGGER_SINGLE, "vehicles/alarm_shield.wav"); + } + drawpic(hudloc + picloc, hud_sh_ico, picsize, '1 0 0' + '0 1 1' * sin(time * 8), 1, DRAWFLAG_NORMAL); + } + else + { + drawpic(hudloc + picloc, hud_sh_ico, picsize, '1 1 1', 1, DRAWFLAG_NORMAL); + if(alarm2time) + { + vehicle_alarm(self, CH_TRIGGER_SINGLE, "misc/null.wav"); + alarm2time = 0; + } + } + + // Gun bar + picsize = draw_getimagesize(hud_ammo1_bar) * autocvar_cl_vehicles_hudscale; + picloc = '450 69 0' * autocvar_cl_vehicles_hudscale; + drawsetcliparea(hudloc_x + picloc_x, picloc_y, picsize_x * energy, vid_conheight); + drawpic(hudloc + picloc, hud_ammo1_bar, picsize, '1 1 1', 1, DRAWFLAG_NORMAL); + drawresetcliparea(); + // .. and icon + picsize = draw_getimagesize(hud_ammo1_ico) * autocvar_cl_vehicles_hudscale; + picloc = '664 60 0' * autocvar_cl_vehicles_hudscale; + if(energy < 0.2) + drawpic(hudloc + picloc, hud_ammo1_ico, picsize, '1 0 0' + '0 1 1' * sin(time * 8), 1, DRAWFLAG_NORMAL); + else + drawpic(hudloc + picloc, hud_ammo1_ico, picsize, '1 1 1', 1, DRAWFLAG_NORMAL); + + // Bomb bar + picsize = draw_getimagesize(hud_ammo2_bar) * autocvar_cl_vehicles_hudscale; + picloc = '450 140 0' * autocvar_cl_vehicles_hudscale; + drawsetcliparea(hudloc_x + picloc_x, hudloc_y + picloc_y, picsize_x * reload1, vid_conheight); + drawpic(hudloc + picloc, hud_ammo2_bar, picsize, '1 1 1', 1, DRAWFLAG_NORMAL); + drawresetcliparea(); + // .. and icon + pic2size = draw_getimagesize(hud_ammo2_ico) * autocvar_cl_vehicles_hudscale; + picloc = '664 130 0' * autocvar_cl_vehicles_hudscale; + if(reload1 != 1) + drawpic(hudloc + picloc, hud_ammo2_ico, pic2size, '1 0 0' + '0 1 1' * sin(time * 8), 1, DRAWFLAG_NORMAL); + else + drawpic(hudloc + picloc, hud_ammo2_ico, pic2size, '1 1 1', 1, DRAWFLAG_NORMAL); + + if (scoreboard_showscores) + HUD_DrawScoreboard(); + else + { + picsize = draw_getimagesize(waki_xhair); + picsize_x *= 0.5; + picsize_y *= 0.5; + + + drawpic('0.5 0 0' * (vid_conwidth - picsize_x) + '0 0.5 0' * (vid_conheight - picsize_y), waki_xhair, picsize, '1 1 1', 1, DRAWFLAG_NORMAL); + } + return true; + } + case VR_SETUP: + { + AuxiliaryXhair[0].axh_image = "gfx/vehicles/axh-bracket.tga"; + AuxiliaryXhair[0].axh_scale = 0.25; + return true; + } + case VR_PRECACHE: + { + return true; + } + } + + return true; +} + +#endif // CSQC +#endif // REGISTER_VEHICLE diff --cc qcsrc/common/weapons/w_porto.qc index 87b5af045,569a15d97..ffc1aed48 --- a/qcsrc/common/weapons/w_porto.qc +++ b/qcsrc/common/weapons/w_porto.qc @@@ -38,7 -38,7 +38,8 @@@ PORTO_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PR #endif #else #ifdef SVQC +#include "../effects.qh" + #include "../triggers/trigger/jumppads.qh" void spawnfunc_weapon_porto(void) { weapon_defaultspawnfunc(WEP_PORTO); } diff --cc qcsrc/csqcmodellib/cl_player.qc index 331759218,1f5f2251f..e0e2c5807 --- a/qcsrc/csqcmodellib/cl_player.qc +++ b/qcsrc/csqcmodellib/cl_player.qc @@@ -261,14 -264,13 +273,14 @@@ void CSQCPlayer_SetCamera( } CSQCPlayer_PredictTo(clientcommandframe + 1, true); -#ifdef CSQCMODEL_SERVERSIDE_CROUCH +//#ifdef CSQCMODEL_SERVERSIDE_CROUCH // get crouch state from the server (LAG) + if(!autocvar_cl_crouch) if(getstati(STAT_VIEWHEIGHT) == PL_VIEW_OFS_z) - self.pmove_flags &= ~PMF_DUCKED; + self.flags &= ~FL_DUCKED; else if(getstati(STAT_VIEWHEIGHT) == PL_CROUCH_VIEW_OFS_z) - self.pmove_flags |= PMF_DUCKED; + self.flags |= FL_DUCKED; -#endif +//#endif CSQCPlayer_SetMinsMaxs(); diff --cc qcsrc/server/autocvars.qh index 77062e317,e595e47a9..456c3bb66 --- a/qcsrc/server/autocvars.qh +++ b/qcsrc/server/autocvars.qh @@@ -651,7 -632,9 +651,8 @@@ float autocvar_sv_eventlog_files_counte string autocvar_sv_eventlog_files_nameprefix; 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") diff --cc qcsrc/server/bot/havocbot/havocbot.qc index 421f46713,7ba2e1612..ba1055fac --- a/qcsrc/server/bot/havocbot/havocbot.qc +++ b/qcsrc/server/bot/havocbot/havocbot.qc @@@ -1,5 -1,8 +1,6 @@@ #include "havocbot.qh" -#include "role_onslaught.qc" -#include "role_keyhunt.qc" #include "roles.qc" + #include "../../../common/triggers/trigger/jumppads.qh" void havocbot_ai() { diff --cc qcsrc/server/cheats.qc index 6b331cd80,b8eaf6e42..19474988b --- a/qcsrc/server/cheats.qc +++ b/qcsrc/server/cheats.qc @@@ -17,8 -18,9 +18,10 @@@ #include "weapons/tracing.qh" #include "autocvars.qh" #include "defs.qh" + #include "../common/effects.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 diff --cc qcsrc/server/cl_client.qc index a26de6e02,a49268d19..18ce00f0e --- a/qcsrc/server/cl_client.qc +++ b/qcsrc/server/cl_client.qc @@@ -10,8 -9,6 +10,7 @@@ #include "portals.qh" #include "teamplay.qh" #include "playerdemo.qh" +#include "round_handler.qh" - #include "secret.qh" #include "bot/bot.qh" #include "bot/navigation.qh" @@@ -19,18 -16,15 +18,23 @@@ #include "weapons/hitplot.qh" #include "weapons/weaponsystem.qh" +#include "../common/animdecide.qh" + +#include "../common/effects.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/minigames/sv_minigames.qh" + #include "../common/monsters/sv_monsters.qh" +#include "../common/vehicles/sv_vehicles.qh" + #include "../warpzonelib/server.qh" float c1, c2, c3, c4; diff --cc qcsrc/server/cl_physics.qc index 6aebc8011,000000000..05dea9f23 mode 100644,000000..100644 --- a/qcsrc/server/cl_physics.qc +++ b/qcsrc/server/cl_physics.qc @@@ -1,1384 -1,0 +1,1374 @@@ +#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 "cl_physics.qh" + #include "defs.qh" + #include "miscfunctions.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" + #include "t_viewloc.qh" + #include "../common/viewloc.qh" +#endif + +.float race_penalty; + +.float ladder_time; +.entity ladder_entity; +.float gravity; +.float swamp_slowdown; +.int lastflags; +.float lastground; +.float wasFlying; +.float spectatorspeed; + +// client side physics +float Physics_Valid(string thecvar) +{ + return autocvar_g_physics_clientselect && checkinlist(thecvar, autocvar_g_physics_clientselect_options); +} + +float Physics_ClientOption(entity pl, string option) +{ + if (Physics_Valid(pl.cvar_cl_physics)) + { + string var = sprintf("g_physics_%s_%s", pl.cvar_cl_physics, option); + if (cvar_type(var) & 1) + return cvar(var); + } + return cvar(strcat("sv_", option)); +} + +/* +============= +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 = self.stat_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 / self.stat_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, self.stat_sv_aircontrol_power)*frametime; + xyspeed = max(0, xyspeed - self.stat_sv_aircontrol_penalty * sqrt(max(0, 1 - dot*dot)) * k/32); + k *= self.stat_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 + self.stat_sv_warsowbunny_airforwardaccel * self.stat_sv_maxspeed * frametime); + } + else + { + f = max(0, (self.stat_sv_warsowbunny_topspeed - curspeed) / (self.stat_sv_warsowbunny_topspeed - self.stat_sv_maxspeed)); + wishspeed = max(curspeed, self.stat_sv_maxspeed) + self.stat_sv_warsowbunny_accel * f * self.stat_sv_maxspeed * frametime; + } + wishvel = wishdir * wishspeed; + acceldir = wishvel - curvel; + addspeed = vlen(acceldir); + acceldir = normalize(acceldir); + + accelspeed = min(addspeed, self.stat_sv_warsowbunny_turnaccel * self.stat_sv_maxspeed * frametime); + + if(self.stat_sv_warsowbunny_backtosideratio < 1) + { + curdir = normalize(curvel); + dot = acceldir * curdir; + if(dot < 0) + acceldir = acceldir - (1 - self.stat_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() +{ + if(!CheatImpulse(99)) + print("A hollow voice says \"Plugh\".\n"); +} + +.float discomode; +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; + + maxspd_mod *= autocvar_g_movement_highspeed; + + if(time < self.spider_slowness) + maxspd_mod *= 0.5; // half speed while slow - TODO: add a cvar! + + // fix physics stats for g_movement_highspeed + // TODO maybe rather use maxairspeed? needs testing + self.stat_sv_airaccel_qw = AdjustAirAccelQW(Physics_ClientOption(self, "airaccel_qw"), maxspd_mod); + if(Physics_ClientOption(self, "airstrafeaccel_qw")) + self.stat_sv_airstrafeaccel_qw = AdjustAirAccelQW(Physics_ClientOption(self, "airstrafeaccel_qw"), maxspd_mod); + else + self.stat_sv_airstrafeaccel_qw = 0; + self.stat_sv_airspeedlimit_nonqw = Physics_ClientOption(self, "airspeedlimit_nonqw") * maxspd_mod; + self.stat_sv_maxspeed = Physics_ClientOption(self, "maxspeed") * maxspd_mod; // also slow walking + + // fix some new settings + self.stat_sv_airaccel_qw_stretchfactor = Physics_ClientOption(self, "airaccel_qw_stretchfactor"); + self.stat_sv_maxairstrafespeed = Physics_ClientOption(self, "maxairstrafespeed"); + self.stat_sv_maxairspeed = Physics_ClientOption(self, "maxairspeed"); + self.stat_sv_airstrafeaccelerate = Physics_ClientOption(self, "airstrafeaccelerate"); + self.stat_sv_warsowbunny_turnaccel = Physics_ClientOption(self, "warsowbunny_turnaccel"); + self.stat_sv_airaccel_sideways_friction = Physics_ClientOption(self, "airaccel_sideways_friction"); + self.stat_sv_aircontrol = Physics_ClientOption(self, "aircontrol"); + self.stat_sv_aircontrol_power = Physics_ClientOption(self, "aircontrol_power"); + self.stat_sv_aircontrol_penalty = Physics_ClientOption(self, "aircontrol_penalty"); + self.stat_sv_warsowbunny_airforwardaccel = Physics_ClientOption(self, "warsowbunny_airforwardaccel"); + self.stat_sv_warsowbunny_topspeed = Physics_ClientOption(self, "warsowbunny_topspeed"); + self.stat_sv_warsowbunny_accel = Physics_ClientOption(self, "warsowbunny_accel"); + self.stat_sv_warsowbunny_backtosideratio = Physics_ClientOption(self, "warsowbunny_backtosideratio"); + self.stat_sv_friction = Physics_ClientOption(self, "friction"); + self.stat_sv_accelerate = Physics_ClientOption(self, "accelerate"); + self.stat_sv_stopspeed = Physics_ClientOption(self, "stopspeed"); + self.stat_sv_airaccelerate = Physics_ClientOption(self, "airaccelerate"); + self.stat_sv_airstopaccelerate = Physics_ClientOption(self, "airstopaccelerate"); + self.stat_sv_jumpvelocity = Physics_ClientOption(self, "jumpvelocity"); + + 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.discomode ) - { - if(IS_PLAYER(self)) - self.BUTTON_JUMP = 1; - - self.angles_y = time*180; - self.velocity = randomvec() * 80; - self.fixangle = true; - } - + 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; + + viewloc_PlayerPhysics(); + + 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, self.stat_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 * self.stat_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, self.stat_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 * self.stat_sv_friction); + + // water acceleration + PM_Accelerate(wishdir, wishspeed, wishspeed, self.stat_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 * self.stat_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, self.stat_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 = self.stat_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; + if(autocvar_g_jetpack_reverse_thrust && self.crouch) { a_up = autocvar_g_jetpack_reverse_thrust; } + + wishvel.x *= a_side; + wishvel.y *= a_side; + wishvel.z *= a_up; + wishvel.z += a_add; + + if(autocvar_g_jetpack_reverse_thrust && self.crouch) { wishvel_z *= -1; } + + 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) || cvar("g_overkill"))) + 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) || cvar("g_overkill"))) + 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 < self.stat_sv_stopspeed) + f = 1 - frametime * (self.stat_sv_stopspeed / f) * self.stat_sv_friction; + else + f = 1 - frametime * self.stat_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, self.stat_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 = self.stat_sv_maxairspeed*maxspd_mod; + airaccel = self.stat_sv_airaccelerate*maxspd_mod; + } + else + { + maxairspd = self.stat_sv_maxairspeed; + airaccel = self.stat_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(self.stat_sv_airstopaccelerate) + { + vector curdir; + curdir = self.velocity; + curdir.z = 0; + curdir = normalize(curdir); + airaccel = airaccel + (self.stat_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(self.stat_sv_maxairstrafespeed) + wishspeed = min(wishspeed, GeomLerp(self.stat_sv_maxairspeed*maxspd_mod, strafity, self.stat_sv_maxairstrafespeed*maxspd_mod)); + if(self.stat_sv_airstrafeaccelerate) + airaccel = GeomLerp(airaccel, strafity, self.stat_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(self.stat_sv_warsowbunny_turnaccel && accelerating && self.movement_y == 0 && self.movement_x != 0) + PM_AirAccelerate(wishdir, wishspeed); + else + PM_Accelerate(wishdir, wishspeed, wishspeed0, airaccel, airaccelqw, self.stat_sv_airaccel_qw_stretchfactor, self.stat_sv_airaccel_sideways_friction / maxairspd, self.stat_sv_airspeedlimit_nonqw); + + if(self.stat_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; +} diff --cc qcsrc/server/cl_player.qc index 0b36b588d,49ae14a6a..7f3a1057b --- a/qcsrc/server/cl_player.qc +++ b/qcsrc/server/cl_player.qc @@@ -1,17 -1,11 +1,18 @@@ #include "cl_player.qh" +#include "jeff.qh" - #include "g_triggers.qh" #include "g_violence.qh" #include "miscfunctions.qh" +#include "mutators/base.qh" + +#include "../common/effects.qh" + +#include "../common/minigames/sv_minigames.qh" + #include "weapons/weaponstats.qh" + #include "../common/animdecide.qh" + void CopyBody_Think(void) { if(self.CopyBody_nextthink && time > self.CopyBody_nextthink) diff --cc qcsrc/server/defs.qh index e7be2b3d6,c430029d0..756671192 --- a/qcsrc/server/defs.qh +++ b/qcsrc/server/defs.qh @@@ -90,17 -73,11 +73,10 @@@ float server_is_dedicated .float pain_frame; //" .float crouch; // Crouching or not? -.float strength_finished; -.float invincible_finished; +.float invincible_finished, strength_finished; // TODO: vehicles system abuses these .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; @@@ -646,30 -557,6 +558,28 @@@ const int MIF_GUIDED_TAG = 128 .float elos; .float ranks; +.int cvar_cl_sparkle; +.bool cvar_cl_pony; +.int cvar_cl_pony_skin; +.int cvar_cl_damnfurries; +.bool cvar_cl_thestars; +.bool cvar_cl_robot; +.bool cvar_cl_goat; +.int cvar_cl_charge; + +.string cvar_cl_autovote; + +.entity lastkiller; +.entity lastkilled; + +.float vaporizer_refire; + +bool sv_showfps; + +.float clientfov; + - .bool sub_target_used; - +.string cvar_cl_physics; + .float init_for_player_needed; .void(entity) init_for_player; diff --cc qcsrc/server/miscfunctions.qc index 5485e41d2,2ce05407b..2618c34b3 --- a/qcsrc/server/miscfunctions.qc +++ b/qcsrc/server/miscfunctions.qc @@@ -22,8 -22,9 +22,9 @@@ #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/turrets/sv_turrets.qh" #include "../common/mapinfo.qh" #include "command/common.qh" #include "../csqcmodellib/sv_model.qh" diff --cc qcsrc/server/mutators/gamemode_assault.qc index e2628b508,de0932263..e564b08a9 --- a/qcsrc/server/mutators/gamemode_assault.qc +++ b/qcsrc/server/mutators/gamemode_assault.qc @@@ -1,5 -1,4 +1,6 @@@ +#include "../../common/vehicles/sv_vehicles.qh" +#include "../../common/turrets/sv_turrets.qh" + #include "../../common/triggers/subs.qh" // random functions void assault_objective_use() diff --cc qcsrc/server/mutators/gamemode_assault.qh index b94f53132,1266492ca..06044039e --- a/qcsrc/server/mutators/gamemode_assault.qh +++ b/qcsrc/server/mutators/gamemode_assault.qh @@@ -25,12 -25,9 +25,11 @@@ void(float ratingscale, vector org, flo void(float ratingscale, vector org, float sradius) havocbot_goalrating_enemyplayers; // scoreboard stuff -const float ST_ASSAULT_OBJECTIVES = 1; -const float SP_ASSAULT_OBJECTIVES = 4; +const int ST_ASSAULT_DESTROYED = 1; +const int ST_ASSAULT_OBJECTIVES = -1; +const int SP_ASSAULT_OBJECTIVES = 4; // predefined spawnfuncs - void spawnfunc_func_breakable(); void target_objective_decrease_activate(); + #endif diff --cc qcsrc/server/mutators/gamemode_ctf.qc index cc05b729e,4c94e4907..678aa11ea --- a/qcsrc/server/mutators/gamemode_ctf.qc +++ b/qcsrc/server/mutators/gamemode_ctf.qc @@@ -1,4 -1,4 +1,5 @@@ +#include "../../common/effects.qh" + #include "../../common/movetypes/movetypes.qh" void ctf_FakeTimeLimit(entity e, float t) { @@@ -736,17 -645,12 +737,18 @@@ void ctf_CheckStalemate(void void ctf_FlagDamage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) { - self.move_velocity = self.velocity; ++ self.velocity = self.velocity; if(ITEM_DAMAGE_NEEDKILL(deathtype)) { - // automatically kill the flag and return it - self.health = 0; - ctf_CheckFlagReturn(self, RETURN_NEEDKILL); + if(autocvar_g_ctf_flag_return_damage_delay) + { + self.ctf_flagdamaged = true; + } + else + { + self.health = 0; + ctf_CheckFlagReturn(self, RETURN_NEEDKILL); + } return; } if(autocvar_g_ctf_flag_return_damage) @@@ -907,6 -796,20 +907,22 @@@ void ctf_FlagUpdate( } } + void ctf_FlagThink() + { - self.nextthink = time; ++ self.nextthink = time + FLAG_THINKRATE; + - if(time >= self.ctf_thinkrate) ++ ctf_FlagUpdate(); ++ ++ /*if(time >= self.ctf_thinkrate) + { + self.ctf_thinkrate = time + FLAG_THINKRATE; + ctf_FlagUpdate(); - } ++ }*/ + + //Movetype_Physics_NoMatchServer(); - Movetype_Physics_MatchTicrate(sys_frametime, 0); ++ //Movetype_Physics_MatchTicrate(sys_frametime, 0); + } + void ctf_FlagTouch() { if(gameover) { return; } @@@ -1105,6 -969,10 +1121,10 @@@ void ctf_DelayedFlagSetup(void) // call // captureshield setup ctf_CaptureShield_Spawn(self); + - self.move_origin = self.origin; - self.move_angles = self.angles; - self.move_velocity = self.velocity; ++ //self.move_origin = self.origin; ++ //self.angles = self.angles; ++ //self.velocity = self.velocity; } void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc @@@ -2176,10 -1989,9 +2196,10 @@@ 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; ++ setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET); vh_player.flagcarried.scale = FLAG_SCALE; - vh_player.flagcarried.move_angles = '0 0 0'; + vh_player.flagcarried.angles = '0 0 0'; + vh_player.flagcarried.nodrawtoclient = world; return true; } diff --cc qcsrc/server/mutators/gamemode_ctf.qh index a334fbec7,5d57dced7..e72d49827 --- a/qcsrc/server/mutators/gamemode_ctf.qh +++ b/qcsrc/server/mutators/gamemode_ctf.qh @@@ -107,8 -103,7 +107,9 @@@ float ctf_captimerecord; // record tim .entity ctf_dropper; // don't allow spam of dropping the flag .float max_flag_health; .float next_take_time; -.float ctf_thinkrate; +.float ctf_flagdamaged; +float ctf_teams; ++//.float ctf_thinkrate; // passing/throwing properties .float pass_distance; diff --cc qcsrc/server/mutators/gamemode_domination.qc index d9dd8f864,314221a19..704e037af --- a/qcsrc/server/mutators/gamemode_domination.qc +++ b/qcsrc/server/mutators/gamemode_domination.qc @@@ -1,10 -1,20 +1,11 @@@ +#include "../../common/vehicles/sv_vehicles.qh" +#include "../round_handler.qh" + #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 +void dom_EventLog(string mode, float cpteam, entity actor) // use an alias for easy changing and quick editing later { if(autocvar_sv_eventlog) - GameLogEcho(strcat(":dom:", mode, ":", ftos(team_before), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : ""))); -} - -void set_dom_state(entity e) -{ - e.dom_total_pps = total_pps; - e.dom_pps_red = pps_red; - e.dom_pps_blue = pps_blue; - if(domination_teams >= 3) - e.dom_pps_yellow = pps_yellow; - if(domination_teams >= 4) - e.dom_pps_pink = pps_pink; + GameLogEcho(sprintf(":dom:%s:%d%s", mode, cpteam, ((actor != world) ? strcat(":", ftos(actor.playerid)) : ""))); } void dompoint_captured () diff --cc qcsrc/server/mutators/gamemode_infection.qh index b7e56f6e4,000000000..75f9a5492 mode 100644,000000..100644 --- a/qcsrc/server/mutators/gamemode_infection.qh +++ b/qcsrc/server/mutators/gamemode_infection.qh @@@ -1,11 -1,0 +1,16 @@@ ++#ifndef GAMEMODE_INFECTION_H ++#define GAMEMODE_INFECTION_H ++ +float infection_teams; +float aliveplayers[17]; + +.float inf_team; +.float inf_nextteam; +.float inf_realteam; + +float prev_total_players; + +// scores +#define SP_INF_ROUNDS 4 ++ ++#endif diff --cc qcsrc/server/mutators/gamemode_jailbreak.qc index 06a141a65,000000000..0f145a58c mode 100644,000000..100644 --- a/qcsrc/server/mutators/gamemode_jailbreak.qc +++ b/qcsrc/server/mutators/gamemode_jailbreak.qc @@@ -1,1342 -1,0 +1,1344 @@@ +#include "../../common/effects.qh" +#include "../round_handler.qh" + +// round handling +void JB_count_alive_players() +{ + entity e; + total_players = redalive = bluealive = yellowalive = pinkalive = 0; + FOR_EACH_PLAYER(e) + { + switch(e.team) + { + case NUM_TEAM_1: ++total_players; if(!e.jb_isprisoned) ++redalive; break; + case NUM_TEAM_2: ++total_players; if(!e.jb_isprisoned) ++bluealive; break; + case NUM_TEAM_3: ++total_players; if(!e.jb_isprisoned) ++yellowalive; break; + case NUM_TEAM_4: ++total_players; if(!e.jb_isprisoned) ++pinkalive; break; + } + } + FOR_EACH_REALCLIENT(e) + { + e.redalive_stat = redalive; + e.bluealive_stat = bluealive; + e.yellowalive_stat = yellowalive; + e.pinkalive_stat = pinkalive; + } +} + +float JB_GetWinnerTeam() +{ + float winner_team = 0; + if(redalive >= 1) + winner_team = NUM_TEAM_1; + if(bluealive >= 1) + { + if(winner_team) return 0; + winner_team = NUM_TEAM_2; + } + if(yellowalive >= 1) + { + if(winner_team) return 0; + winner_team = NUM_TEAM_3; + } + if(pinkalive >= 1) + { + if(winner_team) return 0; + winner_team = NUM_TEAM_4; + } + if(winner_team) + return winner_team; + return -1; // no player left +} + +#define JB_ALIVE_TEAMS() ((redalive > 0) + (bluealive > 0) + (yellowalive > 0) + (pinkalive > 0)) +#define JB_ALIVE_TEAMS_OK() (JB_ALIVE_TEAMS() == jb_teams) +float JB_CheckWinner() +{ + entity e, oldself; + if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0) + { + Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_OVER); + Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_OVER); + allowed_to_spawn = false; + round_handler_Init(5, autocvar_g_jailbreak_warmup, autocvar_g_jailbreak_round_timelimit); + FOR_EACH_PLAYER(e) + { + if(!e.jb_isprisoned) + { + oldself = self; + self = e; + if(!e.jb_isprisoned) + e.player_blocked = 1; + PutClientInServer(); + self = oldself; + } + } + nades_Clear(world, true); + jb_roundover = true; + return 1; + } + + JB_count_alive_players(); + if(JB_ALIVE_TEAMS() > 1) + return 0; + + float winner_team = JB_GetWinnerTeam(); + + if(JB_JailIsOpen(winner_team)) + return 0; // ??? + + if(winner_team > 0) + { + Send_Notification(NOTIF_ALL, world, MSG_CENTER, APP_TEAM_NUM_4(winner_team, CENTER_ROUND_TEAM_WIN_)); + Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(winner_team, INFO_ROUND_TEAM_WIN_)); + TeamScore_AddToTeam(winner_team, ST_JB_ROUNDS, +1); + + JB_ActivateCamera(winner_team); + JB_TorturePrisonersLater(winner_team, 3); + } + else if(winner_team == -1) + { + Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_TIED); + Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_TIED); + } + + jb_roundover = true; + + allowed_to_spawn = false; + round_handler_Init(JB_TORTURE_DURATION, autocvar_g_jailbreak_warmup, autocvar_g_jailbreak_round_timelimit); + + FOR_EACH_PLAYER(e) + { + if(!e.jb_isprisoned) + { + oldself = self; + self = e; + e.player_blocked = 1; + PutClientInServer(); + self = oldself; + } + } + + nades_Clear(world, true); + + return 1; +} + +void JB_RoundStart() +{ + if(warmup_stage) + allowed_to_spawn = true; + else + allowed_to_spawn = false; +} + +float JB_CheckTeams() +{ + static float prev_missing_teams_mask; + allowed_to_spawn = true; + JB_count_alive_players(); + if(JB_ALIVE_TEAMS_OK()) + { + if(prev_missing_teams_mask > 0) + Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_MISSING_TEAMS); + prev_missing_teams_mask = -1; + return 1; + } + if(total_players == 0) + { + if(prev_missing_teams_mask > 0) + Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_MISSING_TEAMS); + prev_missing_teams_mask = -1; + return 0; + } + float missing_teams_mask = (!redalive) + (!bluealive) * 2; + if(jb_teams >= 3) missing_teams_mask += (!yellowalive) * 4; + if(jb_teams >= 4) missing_teams_mask += (!pinkalive) * 8; + if(prev_missing_teams_mask != missing_teams_mask) + { + Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_MISSING_TEAMS, missing_teams_mask); + prev_missing_teams_mask = missing_teams_mask; + } + return 0; +} + +// logging +void jb_debug(string input) +{ + switch(autocvar_g_jailbreak_debug) + { + case 1: dprint(input); break; + case 2: print(input); break; + case 3: bprint(input); break; + } +} + +void JB_AddDoor(entity door, float theteam, vector vdata, string sdata, entity cpoint) +{ + if(door.classname != "door" && door.classname != "door_rotating") + { + jb_debug(sprintf("Warning: %s^7 at %v is linked with an entity of unhandled class (%s^7)\n", JB_ControlPoint_Name(cpoint), cpoint.origin, door.classname)); + return; + } + + door.jb_worlddoornext = jb_worlddoorlist; // link door into jb_worlddoorlist + jb_worlddoorlist = door; +} + +float jb_doors_opened[2]; ++void door_init_startopen(); // TODO ++void door_rotating_init_startopen(); // TODO +void JB_MaybeOpenDoor(entity door, float openjails, vector vdata, string sdata, entity cpoint) +{ + if(openjails == OPENJAILS_LOCKED && door.jaildoormode != JAILDOORMODE_OPEN) + return; + + if(openjails == OPENJAILS_OPEN && door.jaildoormode == JAILDOORMODE_CLOSED) + return; + + // OPENJAILS_LOCKED_FORCE is handled in JB_NonJBInit + // For OPENJAILS_OPEN_FORCE, the below is always executed + + entity oldself = self; + self = door; + float opened = true; + + switch(door.classname) + { + case "door": + door_init_startopen(); + break; + + case "door_rotating": + door_rotating_init_startopen(); + InitMovingBrushTrigger(); + break; + + default: + jb_debug(sprintf("Warning: %s^7 at %v is linked with an entity of unhandled class (%s^7)\n", JB_ControlPoint_Name(cpoint), cpoint.origin, door.classname)); + opened = false; + break; + } + + self = oldself; + + if(opened) + { + float idx = Team_TeamToNumber(cpoint.team); + jb_doors_opened[idx] = jb_doors_opened[idx] + 1; + } +} + +// This is called for non-jailbreak modes only, to alter jb-specific entities on the map +void JB_NonJBInit() +{ + entity tmp_entity; + float openjails = autocvar_g_jailbreak_nonjb_openjails; + + SUB_ForEachTarget_Init(); + for(tmp_entity = jb_worldcplist; tmp_entity; tmp_entity = tmp_entity.jb_worldcpnext) + { + if(tmp_entity.team) + { + if(openjails != OPENJAILS_LOCKED_FORCE) + SUB_ForEachTarget(tmp_entity, JB_MaybeOpenDoor, true, openjails, '0 0 0', string_null, tmp_entity); + tmp_entity.think = SUB_Remove; + tmp_entity.nextthink = time; + } + } + + // If all jail doors are locked, it means that the jail is not intended to be accessible. + // We have to keep the jail sectors then to ensure it's not possible to get in with translocator (or something more evil to be added in the future). + // Otherwise, they have to be removed. TODO: do something about maps with multiple jails (if we ever get any). + entity e; // TODO + for(e = findchain(classname, "jailbreak_jail"); e; e = e.chain) + { + float idx = Team_TeamToNumber(e.team); + if(!autocvar_g_nades || jb_doors_opened[idx]) + { + e.think = SUB_Remove; + e.nextthink = time; + } + } +} + +// +// Gametype logic +// + +float JB_JailIsOpen(float theteam) +{ + entity tmp_entity; + for(tmp_entity = jb_worlddoorlist; tmp_entity; tmp_entity = tmp_entity.jb_worlddoornext) + { + if(tmp_entity.team == theteam) + if(tmp_entity.state != STATE_BOTTOM) + return true; + } + return false; +} + +void JB_TeleportToJail(entity p, entity attacker) +{ + vector a; + entity spot = jb_ChooseJailSpawn(p, attacker); + + float tries; + tries = 3; + + while(!spot && tries > 0) + { + spot = jb_ChooseJailSpawn(p, attacker); + tries--; + } + + if(!spot) + { + jb_debug(strcat("Failed to pick a jail spawnpoint for ", self.netname, "^7, cannot imprison!\n")); + return; + } + + a = spot.angles; + a_z = 0; + TeleportPlayer(spot, self, spot.origin, self.mangle, a, '0 0 0', '0 0 0', TELEPORT_FLAGS_TELEPORTER); +} + +void JB_Imprison(entity attacker) +{ + if(self.jb_isprisoned) + { + jb_debug(strcat("Tried to imprison a prisoned player (", self.netname, "^7)\n")); + return; + } + + self.health = autocvar_g_jailbreak_prisoner_health; + self.armorvalue = autocvar_g_jailbreak_prisoner_armor; + + self.jb_had_unlimited_ammo = (self.items & IT_UNLIMITED_WEAPON_AMMO); + + if(!self.jb_had_unlimited_ammo) + self.items |= IT_UNLIMITED_WEAPON_AMMO; + + self.weapon_blocked = true; + + nades_Clear(self, false); + + jb_debug(sprintf("Imprisoning %s^7, attacker: %e with netname: %s\n", self.netname, attacker, attacker.netname)); + JB_TeleportToJail(self, attacker); + + self.jb_isprisoned = true; + self.jb_prisontime = time; + + Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_JAILBREAK_IMPRISON); +} + +void JB_TorturePrisonersLater_Think() +{ + JB_TorturePrisoners(self.team); + remove(self); +} + +void JB_TorturePrisonersLater(float theteam, float thedelay) +{ + entity e = spawn(); + e.team = theteam; + e.think = JB_TorturePrisonersLater_Think; + e.nextthink = time + thedelay; +} + +void JB_Release(entity saviour) +{ + if(!self.jb_isprisoned) + { + jb_debug(strcat("Tried to release a free player (", self.netname, "^7)\n")); + return; + } + + self.jb_isprisoned = false; + self.jb_imprisoner = world; + self.weapon_blocked = false; + self.player_blocked = false; // just incase + + if(!self.jb_had_unlimited_ammo) + self.items &= ~IT_UNLIMITED_WEAPON_AMMO; + + if(g_jailbreak_jail_deathmatch) + { + self.health = max(self.health, autocvar_g_jailbreak_prisoner_health); + self.armorvalue = max(self.armorvalue, autocvar_g_jailbreak_prisoner_armor); + } +} + +// +// Torture logic +// + +#define JITTER(v,j) (v) + (j) * 2 * (random() - 0.5) + +void JB_TorturePrisoners(float theteam) +{ + entity spot = world; + + for(;(spot = find(spot, classname, "info_jailbreak_torturespawn"));) + if(spot.team == theteam) + JB_Torture_Start(spot); +} + +void JB_Torture_Think() +{ + if(gameover) + { + remove(self); + return; + } + + makevectors(self.angles); + //self.nextthink = time + JITTER(self.jb_torture_delay, self.jb_torture_delay_jitter); + + float j = self.jb_torture_delay - JITTER(self.jb_torture_delay, self.jb_torture_delay_jitter); + + if(j > 0) + j = 0.5 * j; + + self.nextthink = time + max(0.1, self.jb_torture_delay + j); + self.jb_torture_suggestedforce = JITTER(self.jb_torture_force, self.jb_torture_force_jitter); + + Send_Effect(EFFECT_FIREFIELD, self.origin, '0 0 0', 2); + + entity head; + FOR_EACH_PLAYER(head) + if(DIFF_TEAM(head, self)) + if(head.jb_isprisoned) + if(!Fire_IsBurning(head)) + Fire_AddDamage(head, world, 100, 6, DEATH_FIRE); +} + +void JB_Torture_Start(entity spot) +{ + entity e = spawn(); + e.classname = "jailbreak_torture"; + e.reset = SUB_Remove; + e.reset2 = e.reset; + e.think = JB_Torture_Think; + e.angles = spot.angles; + e.jb_torture_delay = spot.jb_torture_delay; + e.jb_torture_delay_jitter = spot.jb_torture_delay_jitter; + e.jb_torture_force = spot.jb_torture_force; + e.jb_torture_force_jitter = spot.jb_torture_force_jitter; + e.owner = spot; + e.team = e.owner.team; + setorigin(e, spot.origin); + e.nextthink = time + JITTER(0, e.jb_torture_delay_jitter); +} + +#undef JITTER + +.float pointupdatetime; + +// +// Utility functions +// + +entity jb_ChooseJailSpawn(entity player, entity attacker) +{ + entity spot; + + RandomSelection_Init(); + + for(spot = world; (spot = find(spot, classname, "info_jailbreak_jailspawn")); ) + { + if(attacker && DIFF_TEAM(player, attacker)) // don't throw teammates in own jail? + { + if(SAME_TEAM(spot, attacker)) + RandomSelection_Add(spot, 0, string_null, 1, 1); + } + else + { + if(DIFF_TEAM(spot, player)) + RandomSelection_Add(spot, 0, string_null, 1, 1); + } + } + + if(!RandomSelection_chosen_ent) + jb_debug(strcat("Unable to find an enemy jail spawnpoint, player team: ", ftos(player.team), "\n")); + + return RandomSelection_chosen_ent; +} + +float JB_TotalPlayersOnTeam(float theteam) +{ + entity e; + float plcount = 0; + FOR_EACH_PLAYER(e) if(e.team == theteam) ++plcount; + + return plcount; +} + +float JB_AlivePlayersOnTeam(float theteam) +{ + entity e; + float plcount = 0; + FOR_EACH_PLAYER(e) if(e.team == theteam) if(!e.jb_isprisoned) ++plcount; + + return plcount; +} + +entity JB_FindCamera(float theteam) +{ + RandomSelection_Init(); + + entity e = world; + for(;(e = find(e, classname, "info_jailbreak_jailcamera"));) if(e.team == theteam) + RandomSelection_Add(e, 0, string_null, 1, 1); + + return RandomSelection_chosen_ent; +} + +float jb_ce_pvs, jb_ce_trace; + +void JB_ActivateCamera(float theteam) +{ + entity cam = JB_FindCamera(theteam); + + if(!cam) + { + jb_debug(strcat("Team ", ftos(theteam), " has no camera entities, fail!\n")); + return; + } + + jb_ce_pvs = cvar("sv_cullentities_pvs"); + jb_ce_trace = cvar("sv_cullentities_trace"); + + // without this we won't be able to watch them burn! + cvar_settemp("sv_cullentities_pvs", "0"); + cvar_settemp("sv_cullentities_trace", "0"); + + entity p; + FOR_EACH_REALCLIENT(p) + p.jb_roundlost = true; + + cam.active = true; + cam.SendFlags |= 2; + + entity e; + for(e = world; (e = find(e, classname, "info_jailbreak_jailcamera")); ) + if(e != cam) + { + e.active = false; + e.SendFlags |= 2; + } +} + +// +// Setup functions +// + +void JB_SetupJailSpawnpoint() +{ + if(!g_jailbreak) { remove(self); return; } + + self.classname = "info_jailbreak_jailspawn"; +} + +void JB_Jail_Touch() +{ + if(autocvar_g_nades) + if(other.classname == "nade") + { + entity own = other.realowner; + remove(other); + nades_Clear(own, false); + return; + } + + if(other.classname == "grapplinghook") + { + RemoveGrapplingHook(other.realowner); + return; + } + + if(!g_jailbreak) + return; + + if(!IS_PLAYER(other)) + return; + + if(!other.jb_isprisoned) + { + vector mymid = (self.absmin + self.absmax) * 0.5; + vector othermid = (other.absmin + other.absmax) * 0.5; + + Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * min(500, vlen(other.velocity))); + Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_JAILBREAK_NOENTRY); + return; + } + + other.jb_isprisoned = 2; + + if(SAME_TEAM(other, self)) + return; + + other.jb_jail_resettime = time + frametime * 5; +} + +void JB_SetupJail() +{ + self.classname = "jailbreak_jail"; + self.touch = JB_Jail_Touch; + EXACTTRIGGER_INIT; +} + +float jailcamera_send(entity to, float sf) +{ + WriteByte(MSG_ENTITY, ENT_CLIENT_JAILCAMERA); + WriteByte(MSG_ENTITY, sf); + + if(sf & 1) + { + WriteCoord(MSG_ENTITY, self.origin_x); + WriteCoord(MSG_ENTITY, self.origin_y); + WriteCoord(MSG_ENTITY, self.origin_z); + + WriteAngle(MSG_ENTITY, self.angles_x); + WriteAngle(MSG_ENTITY, self.angles_y); + } + + if(sf & 2) + { + WriteByte(MSG_ENTITY, self.active); + } + + return true; +} + + +void JB_SetupJailCamera() +{ + if(!g_jailbreak) { remove(self); return; } + + self.classname = "info_jailbreak_jailcamera"; + + Net_LinkEntity(self, false, 0, jailcamera_send); +} + +void JB_SetupTortureSpawnpoint() +{ + if(!g_jailbreak) { remove(self); return; } + + self.classname = "info_jailbreak_torturespawn"; + + if(!self.jb_torture_force) + self.jb_torture_force = 400; + + if(!self.jb_torture_force_jitter) + self.jb_torture_force_jitter = self.jb_torture_force * 0.1; + + if(!self.jb_torture_delay) + self.jb_torture_delay = 2; + + if(!self.jb_torture_delay_jitter) + self.jb_torture_delay_jitter = self.jb_torture_delay * 0.5; +} + +.entity sprite; +void AnimateDomPoint(); + +string JB_ControlPoint_ModelForTeam(float t) +{ + switch(t) + { + case NUM_TEAM_1: return "models/domination/dom_red.md3"; + case NUM_TEAM_2: return "models/domination/dom_blue.md3"; + case NUM_TEAM_3: return "models/domination/dom_yellow.md3"; + case NUM_TEAM_4: return "models/domination/dom_pink.md3"; + default: return "models/domination/dom_unclaimed.md3"; + } +} + +string JB_ControlPoint_WaypointForTeam(float t) +{ + switch(t) + { + case NUM_TEAM_1: return "dom-red"; + case NUM_TEAM_2: return "dom-blue"; + case NUM_TEAM_3: return "dom-yellow"; + case NUM_TEAM_4: return "dom-pink"; + default: return "dom-neut"; + } +} + +float JB_ControlPoint_Cooldown(entity e) +{ + float base, pw, f, c; + + c = e.jb_capturecount; + + if(e.team == 0) + { + base = autocvar_g_jailbreak_controlpoint_idletime_neutral; + pw = autocvar_g_jailbreak_controlpoint_idletime_neutral_power; + f = autocvar_g_jailbreak_controlpoint_idletime_neutral_factor; + } + else + { + base = autocvar_g_jailbreak_controlpoint_idletime; + pw = autocvar_g_jailbreak_controlpoint_idletime_power; + f = autocvar_g_jailbreak_controlpoint_idletime_factor; + } + + return base + pow(c, pw) * f * base; +} + +float JB_ControlPoint_InitialCooldown(entity e) { return ((e.team == 0) ? autocvar_g_jailbreak_controlpoint_idletime_neutral_initial : autocvar_g_jailbreak_controlpoint_idletime_initial); } + +void JB_ControlPoint_Activate(entity e) +{ + e.jb_active = true; + e.jb_cooldown = 0; + //e.jb_cooldown_max = 0; + setmodel(e, JB_ControlPoint_ModelForTeam(e.team)); + setsize(e, JB_CP_MIN, JB_CP_MAX); + WaypointSprite_UpdateMaxHealth(e.jb_waypoint, 0); + WaypointSprite_UpdateHealth(e.jb_waypoint, 0); +} + +void JB_ControlPoint_Deactivate(entity e, float cooldown) +{ + e.jb_cooldown_max = max(e.jb_cooldown_max, cooldown); + e.jb_cooldown = max(e.jb_cooldown, cooldown); + + jb_debug(sprintf("%e: %ds cooldown, team: %d, caps: %d\n", e, e.jb_cooldown, e.team, e.jb_capturecount)); + + if(e.jb_active && e.jb_cooldown > 0) + { + setmodel(e, "models/domination/dom_unclaimed.md3"); + setsize(e, JB_CP_MIN, JB_CP_MAX); + e.jb_active = false; + } +} + +void JB_ControlPoint_UpdateCooldownProgress(entity e) +{ + WaypointSprite_UpdateMaxHealth(e.jb_waypoint, e.jb_cooldown_max); + WaypointSprite_UpdateHealth(e.jb_waypoint, e.jb_cooldown_max - e.jb_cooldown); +} + +void JB_ControlPoint_Think() +{ + self.nextthink = time; + AnimateDomPoint(); + + if(time < game_starttime || jb_roundover) + return; + + if(self.jb_cooldown) { JB_ControlPoint_UpdateCooldownProgress(self); } + else if(!self.jb_active) { JB_ControlPoint_Activate(self); } + + if(time - self.pointupdatetime >= 0.1) + { + self.jb_unlock_progress = 0; + self.jb_capturingplayer = world; + } + + self.jb_cooldown = max(0, self.jb_cooldown - frametime); +} + +void JB_ControlPoint_SwitchTeam(entity e, float t) +{ + e.team = t; + //WaypointSprite_UpdateSprites(e.jb_waypoint, e.jb_waypoint.model1, "", e.jb_waypoint.model3); + WaypointSprite_UpdateTeamRadar(e.jb_waypoint, RADARICON_FLAG, (e.team) ? colormapPaletteColor(e.team - 1, false) : '0 1 1'); + //WaypointSprite_UpdateTextColors(e.jb_waypoint, TeamColor(e.team), e.jb_waypoint.clr2, e.jb_waypoint.clr3); +} + +void JB_TriggerTeamControlPoints(entity cp, entity player) +{ + entity oldself = self; + self = world; + + for(self = jb_worldcplist; self; self = self.jb_worldcpnext) + { + if(cp.team) + { + if(SAME_TEAM(self, cp)) + SUB_UseTargets_PreventReuse(); + } + else + { + if(self.jb_team_initial != player.team) + SUB_UseTargets_PreventReuse(); + } + } + + self = oldself; +} + +string JB_ControlPoint_Name(entity p) +{ + string clr, tm, end; + + clr = Team_ColorCode(p.team); + tm = Team_ColorName(p.team); + + end = strcat(" (Point ", chr2str(str2chr("A", 0) + p.cnt), ")"); + + if(!p.netname) + return strcat(clr, tm, " Control Point", end); + return strcat(clr, strdecolorize(p.netname), end); +} + +void JB_ControlPoint_Capture(entity player) +{ + entity e; + float pc = false; + + activator = self; + + if(!self.team || g_jailbreak_claim) + JB_TriggerTeamControlPoints(self, player); + else SUB_UseTargets(); + + FOR_EACH_PLAYER(e) + { + if(DIFF_TEAM(e, player)) + Send_Notification(NOTIF_ONE, e, MSG_CENTER, CENTER_JAILBREAK_ESCAPE, player.team); + else if(e == player) + Send_Notification(NOTIF_ONE, e, MSG_CENTER, CENTER_JAILBREAK_FREED); + + if(e.jb_isprisoned && SAME_TEAM(e, player)) + { + Send_Notification(NOTIF_ONE, e, MSG_CENTER, CENTER_JAILBREAK_FREE); + pc++; + } + + } + + Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_JAILBREAK_CAPTURE, player.netname, JB_ControlPoint_Name(self)); + + for(e = jb_worldcplist; e; e = e.jb_worldcpnext) + { + e.jb_lastmessage = time + 3; + } + + PlayerScore_Add(player, SP_SCORE, ((self.team == 0)? autocvar_g_jailbreak_score_jbreak_neutralmultiplier : 1) + * (autocvar_g_jailbreak_score_jbreak + autocvar_g_jailbreak_score_jbreak_perplayer * pc)); + PlayerScore_Add(player, SP_JB_JBREAKS, 1); + PlayerScore_Add(player, SP_JB_FREED, pc); + nades_GiveBonus(player, autocvar_g_nades_bonus_score_medium); + play2all("kh/alarm.wav"); + + if(autocvar_g_jailbreak_controlpoint_claim_noneutral) + if(self.team == 0) + return; + + JB_ControlPoint_SwitchTeam(self, player.team); +} + +void JB_ControlPoint_Touch() +{ + if(jb_roundover) + return; + + if(other.health < 1 || other.frozen) + return; + + if(gameover) + return; + + if(!IS_PLAYER(other)) + return; + + other.pointupdatetime = time; + + if(SAME_TEAM(other, self)) + { + if(time >= self.jb_lastmessage) + { + Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_JAILBREAK_WRONGTEAM); + self.jb_lastmessage = time + 1.5; + } + return; + } + + if(!self.jb_active) + { + if(time >= self.jb_lastmessage) + { + Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_JAILBREAK_NOTREADY); + self.jb_lastmessage = time + 1.5; + } + return; + } + + if(self.jb_capturingplayer && self.jb_capturingplayer != other) + { + if(time >= self.jb_lastmessage) + { + Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_JAILBREAK_TOOLATE); + self.jb_lastmessage = time + 1.5; + } + return; + } + + if(JB_TotalPlayersOnTeam(other.team) == JB_AlivePlayersOnTeam(other.team)) + { + if(time >= self.jb_lastmessage) + { + Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_JAILBREAK_TEAMALIVE); + self.jb_lastmessage = time + 1.5; + } + return; + } + + entity tmp_entity; + float capping_neutral = false; + if(self.team) + for(tmp_entity = jb_worldcplist; tmp_entity; tmp_entity = tmp_entity.jb_worldcpnext) + { + if(!tmp_entity.team) + if(tmp_entity.jb_unlock_progress) + if(SAME_TEAM(tmp_entity.jb_capturingplayer, other)) + { + capping_neutral = true; + break; + } + } + + if(!capping_neutral || !self.team) + self.jb_unlock_progress = bound(0, self.jb_unlock_progress + frametime * autocvar_g_jailbreak_controlpoint_unlock_speed, 1); + + self.pointupdatetime = time; + self.jb_capturingplayer = other; + other.jb_unlock_progress = self.jb_unlock_progress; + + if(self.jb_unlock_progress >= 1) + { + JB_ControlPoint_Capture(other); + + JB_ControlPoint_Deactivate(self, JB_ControlPoint_Cooldown(self)); + + for(tmp_entity = jb_worldcplist; tmp_entity; tmp_entity = tmp_entity.jb_worldcpnext) + { + if(tmp_entity != self) + if(SAME_TEAM(tmp_entity, other)) + JB_ControlPoint_Deactivate(tmp_entity, autocvar_g_jailbreak_controlpoint_idletime_global_own); + else if(!tmp_entity.team || DIFF_TEAM(tmp_entity, other)) + JB_ControlPoint_Deactivate(tmp_entity, autocvar_g_jailbreak_controlpoint_idletime_global); + } + + self.jb_capturecount += 1; + } +} + +void JB_ControlPoint_Reset() +{ + self.jb_capturecount = 0; + self.jb_active = true; + self.jb_cooldown = 0; + self.jb_cooldown_max = 0; + JB_ControlPoint_Deactivate(self, JB_ControlPoint_InitialCooldown(self)); + WaypointSprite_UpdateMaxHealth(self.jb_waypoint, 0); + WaypointSprite_UpdateHealth(self.jb_waypoint, 0); + JB_ControlPoint_SwitchTeam(self, autocvar_g_jailbreak_controlpoint_claim_allneutral ? 0 : self.jb_team_initial); +} + +float jb_ControlPoint_Waypoint_Customize() +{ + if(!self.owner.team) { return true; } + + entity e = WaypointSprite_getviewentity(other); + + // hide from owner's team + if(SAME_TEAM(self.owner, e)) { return false; } + + return true; +} + +void JB_SetupControlPoint() +{ + self.jb_worldcpnext = jb_worldcplist; // link control point into jb_worldcplist + jb_worldcplist = self; + + if(!g_jailbreak) { return; } // removal is done in JB_NonJBInit + + self.classname = "jailbreak_controlpoint"; + self.jb_team_initial = self.team; + + if(autocvar_g_jailbreak_controlpoint_claim_allneutral) + self.team = 0; + + setmodel(self, JB_ControlPoint_ModelForTeam(self.team)); + self.skin = 0; + + if(!self.t_width) + self.t_width = 0.02; // frame animation rate + if(!self.t_length) + self.t_length = 239; // maximum frame + + self.think = JB_ControlPoint_Think; + self.nextthink = time; + self.touch = JB_ControlPoint_Touch; + self.solid = SOLID_TRIGGER; + self.flags = FL_ITEM; + self.reset = JB_ControlPoint_Reset; + self.jb_capturecount = 0; + self.jb_active = true; + self.cnt = jb_cp_num; + self.scale = JB_CP_SCALE; + JB_ControlPoint_Deactivate(self, JB_ControlPoint_InitialCooldown(self)); + setsize(self, JB_CP_MIN, JB_CP_MAX); + setorigin(self, self.origin + '0 0 20'); + droptofloor(); + + waypoint_spawnforitem_force(self, self.origin); + self.nearestwaypointtimeout = 0; // activate waypointing again + WaypointSprite_SpawnFixed(strzone(strcat("Point ", chr2str(str2chr("A", 0) + jb_cp_num))), self.origin + JB_CP_WPOFFSET, self, jb_waypoint, RADARICON_DOMPOINT, '0 1 1'); + self.jb_waypoint.customizeentityforclient = jb_ControlPoint_Waypoint_Customize; + WaypointSprite_UpdateTeamRadar(self.jb_waypoint, RADARICON_FLAG, (self.team) ? colormapPaletteColor(self.team - 1, false) : '0 1 1'); + //WaypointSprite_UpdateTextColors(self.jb_waypoint, TeamColor(self.team), '1 0.5 0', '0 0 0'); + //WaypointSprite_UpdateSprites(self.jb_waypoint, self.jb_waypoint.model1, self.jb_waypoint.model2, ""); + + ++jb_cp_num; +} + +// mutator hooks +MUTATOR_HOOKFUNCTION(jb_OnEntityPreSpawn) +{ + switch(self.classname) + { + case "item_flag_team1": + case "item_flag_team2": + case "item_flag_team3": + case "item_flag_team4": + return true; + } + + return false; +} + +MUTATOR_HOOKFUNCTION(jb_ResetMap) +{ + jb_roundover = false; + + cvar_set("sv_cullentities_pvs", ftos(jb_ce_pvs)); + cvar_set("sv_cullentities_trace", ftos(jb_ce_trace)); + + for(self = world; (self = find(self, classname, "info_jailbreak_jailcamera")); ) + self.active = false; + + FOR_EACH_CLIENT(self) + { + if(IS_PLAYER(self)) + { + JB_Release(world); + PutClientInServer(); + } + self.player_blocked = 0; + self.weapon_blocked = false; + self.jb_roundlost = false; + } + + return true; +} + +MUTATOR_HOOKFUNCTION(jb_PlayerDies) +{ + if(jb_roundover && frag_deathtype == DEATH_FIRE) + PlayerScore_Add(frag_target, SP_KILLS, +1); // dying negates 1, so we bring it back up + + if(!round_handler_IsRoundStarted() || jb_roundover) + return false; + + if(!frag_target.jb_isprisoned) + { + if(frag_attacker == frag_target || !frag_attacker) + PlayerScore_Add(frag_target, SP_SCORE, -autocvar_g_jailbreak_penalty_death); + else if(IS_PLAYER(frag_attacker)) + { + if(DIFF_TEAM(frag_attacker, frag_target)) + { + PlayerScore_Add(frag_target, SP_SCORE, -autocvar_g_jailbreak_penalty_death); + PlayerScore_Add(frag_attacker, SP_SCORE, autocvar_g_jailbreak_score_imprison); + + float rng = autocvar_g_jailbreak_defense_range; + entity cp; + if(rng) + for(cp = jb_worldcplist; cp; cp = cp.jb_worldcpnext) + { + if(SAME_TEAM(cp, frag_attacker) || (cp.team == 0 && cp.jb_active)) + { + // Rewards control point defense if fragging nearby your team's or neutral cp. + // In case of neutral cp, it has to be active (no defense farming in the beginning of the round) + if(vlen(cp.origin - frag_target.origin) < rng) + { + Send_Notification(NOTIF_ONE, frag_attacker, MSG_CENTER, CENTER_JAILBREAK_DEFENSE); + PlayerScore_Add(frag_attacker, SP_SCORE, autocvar_g_jailbreak_score_defense); + PlayerScore_Add(frag_attacker, SP_JB_DEFENSE, 1); + nades_GiveBonus(frag_attacker, autocvar_g_nades_bonus_score_minor); + break; + } + } + } + } + else PlayerScore_Add(frag_attacker, SP_SCORE, -autocvar_g_jailbreak_penalty_teamkill); + } + + frag_target.jb_imprisoner = frag_attacker; + frag_target.respawn_flags |= RESPAWN_FORCE; + } + else + { + jb_debug(strcat("Prisoned player ", frag_target.netname, "^7 just died, should this really happen?\n")); + PutClientInServer(); + } + + return false; +} + +MUTATOR_HOOKFUNCTION(jb_PlayerSpawn) +{ + //self.jb_isprisoned = false; + + if(!round_handler_IsRoundStarted()) { self.jb_isprisoned = false; return false; } + + if(self.jb_imprisoner != world) + { + JB_Imprison(self.jb_imprisoner); + self.jb_imprisoner = world; + } + + if(JB_TotalPlayersOnTeam(self.team) - 1 > 0) // allow to spawn non-prisoned if there are no players on that team + JB_Imprison(world); + + return false; +} + +MUTATOR_HOOKFUNCTION(jb_SpectateCopy) +{ + self.stat_jb_isprisoned = other.jb_isprisoned; + self.jb_unlock_progress = other.jb_unlock_progress; + return false; +} + +MUTATOR_HOOKFUNCTION(jb_RemovePlayer) +{ + if(self.jb_isprisoned) + JB_Release(world); + + self.jb_roundlost = false; + + return false; +} + +MUTATOR_HOOKFUNCTION(jb_PlayerPreThink) +{ + if(gameover) + { + self.stat_jb_isprisoned = false; + return false; + } + + self.stat_jb_isprisoned = self.jb_isprisoned; // these are different to allow spectating + + if(!round_handler_IsRoundStarted()) + { + self.jb_isprisoned_prev = 0; + self.jb_unlock_progress = 0; + return false; + } + + float ps = min(1, self.jb_isprisoned); + if(ps != self.jb_isprisoned_prev) + { + if(!ps) + Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_JAILBREAK_FREE, self.netname); + self.jb_isprisoned_prev = ps; + } + + if(time - self.pointupdatetime >= 0.01) + self.jb_unlock_progress = 0; + + if(time - self.jb_prisontime < 0.5) + return false; + + if(self.jb_isprisoned == 1) + { + jb_debug(strcat("Warning: ", self.netname, "^7managed to leave the jail without touching the jail sector! Attempting to put them back in\n")); + JB_TeleportToJail(self, world); + } + else if(self.jb_isprisoned) + if(time > self.jb_jail_resettime) + JB_Release(world); + + return false; +} + +MUTATOR_HOOKFUNCTION(jb_PlayerDamage) +{ + if(self.jb_isprisoned && frag_deathtype != DEATH_FIRE) + frag_damage = 0; + + entity tmp_entity; + if(self.jb_unlock_progress) + for(tmp_entity = jb_worldcplist; tmp_entity; tmp_entity = tmp_entity.jb_worldcpnext) + { + if(tmp_entity.jb_capturingplayer == self) + tmp_entity.jb_unlock_progress = bound(0, tmp_entity.jb_unlock_progress - autocvar_g_jailbreak_controlpoint_unlock_damage_pushback, 1); + } + + return false; +} + +MUTATOR_HOOKFUNCTION(jb_ForbidThrowing) +{ + if(self.jb_isprisoned) + return true; + + return false; +} + +MUTATOR_HOOKFUNCTION(jb_GiveFrags) +{ + if(jb_roundover) + { + frag_score = 0; + return true; + } + return false; +} + +MUTATOR_HOOKFUNCTION(jb_GetTeamCount) +{ + ret_float = jb_teams; + return false; +} + +MUTATOR_HOOKFUNCTION(jb_AllowMobSpawning) +{ + if(self.jb_isprisoned) + { + ret_string = "You can't spawn monsters in prison!"; + return true; + } + + return false; +} + +// spawn functions +#define JB_SPAWNFUNC(e,s,t) void spawnfunc_##e() { self.team = t; s(); } + +JB_SPAWNFUNC(info_jailbreak_jailspawn_red, JB_SetupJailSpawnpoint, NUM_TEAM_1) +JB_SPAWNFUNC(info_jailbreak_jailspawn_blue, JB_SetupJailSpawnpoint, NUM_TEAM_2) +JB_SPAWNFUNC(info_jailbreak_jailspawn_yellow, JB_SetupJailSpawnpoint, NUM_TEAM_3) +JB_SPAWNFUNC(info_jailbreak_jailspawn_pink, JB_SetupJailSpawnpoint, NUM_TEAM_4) + +JB_SPAWNFUNC(func_jailbreak_jail_red, JB_SetupJail, NUM_TEAM_1) +JB_SPAWNFUNC(func_jailbreak_jail_blue, JB_SetupJail, NUM_TEAM_2) +JB_SPAWNFUNC(func_jailbreak_jail_yellow, JB_SetupJail, NUM_TEAM_3) +JB_SPAWNFUNC(func_jailbreak_jail_pink, JB_SetupJail, NUM_TEAM_4) + +JB_SPAWNFUNC(info_jailbreak_jailcamera_red, JB_SetupJailCamera, NUM_TEAM_1) +JB_SPAWNFUNC(info_jailbreak_jailcamera_blue, JB_SetupJailCamera, NUM_TEAM_2) +JB_SPAWNFUNC(info_jailbreak_jailcamera_yellow, JB_SetupJailCamera, NUM_TEAM_3) +JB_SPAWNFUNC(info_jailbreak_jailcamera_pink, JB_SetupJailCamera, NUM_TEAM_4) + +JB_SPAWNFUNC(info_jailbreak_torturespawn_red, JB_SetupTortureSpawnpoint, NUM_TEAM_1) +JB_SPAWNFUNC(info_jailbreak_torturespawn_blue, JB_SetupTortureSpawnpoint, NUM_TEAM_2) +JB_SPAWNFUNC(info_jailbreak_torturespawn_yellow, JB_SetupTortureSpawnpoint, NUM_TEAM_3) +JB_SPAWNFUNC(info_jailbreak_torturespawn_pink, JB_SetupTortureSpawnpoint, NUM_TEAM_4) + +JB_SPAWNFUNC(jailbreak_controlpoint_red, JB_SetupControlPoint, NUM_TEAM_1) +JB_SPAWNFUNC(jailbreak_controlpoint_blue, JB_SetupControlPoint, NUM_TEAM_2) +JB_SPAWNFUNC(jailbreak_controlpoint_yellow, JB_SetupControlPoint, NUM_TEAM_3) +JB_SPAWNFUNC(jailbreak_controlpoint_pink, JB_SetupControlPoint, NUM_TEAM_4) +JB_SPAWNFUNC(jailbreak_controlpoint_neutral, JB_SetupControlPoint, 0) + +// scores +void jb_ScoreRules(float teams) +{ + ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true); // SFL_SORT_PRIO_PRIMARY + ScoreInfo_SetLabel_TeamScore(ST_JB_ROUNDS, "rounds", SFL_SORT_PRIO_PRIMARY); + ScoreInfo_SetLabel_PlayerScore(SP_JB_JBREAKS, "jbs", 0); + ScoreInfo_SetLabel_PlayerScore(SP_JB_FREED, "freed", SFL_SORT_PRIO_SECONDARY); + ScoreInfo_SetLabel_PlayerScore(SP_JB_DEFENSE, "def", 0); + ScoreRules_basics_end(); +} + +// initialization +void jb_DelayedInit() +{ + entity tmp_entity; + + SUB_ForEachTarget_Init(); + for(tmp_entity = jb_worldcplist; tmp_entity; tmp_entity = tmp_entity.jb_worldcpnext) + SUB_ForEachTarget(tmp_entity, JB_AddDoor, true, tmp_entity.jb_team_initial, '0 0 0', string_null, tmp_entity); +} + +void jb_Initialize() +{ + precache_sound("kh/alarm.wav"); + + if(autocvar_g_jailbreak_teams_override >= 2) + jb_teams = autocvar_g_jailbreak_teams_override; + else + jb_teams = autocvar_g_jailbreak_teams; + + jb_teams = bound(2, jb_teams, 4); + + jb_ScoreRules(jb_teams); + + round_handler_Spawn(JB_CheckTeams, JB_CheckWinner, JB_RoundStart); + round_handler_Init(5, autocvar_g_jailbreak_warmup, autocvar_g_jailbreak_round_timelimit); + + g_jailbreak_claim = autocvar_g_jailbreak_controlpoint_claim; + + addstat(STAT_REDALIVE, AS_INT, redalive_stat); + addstat(STAT_BLUEALIVE, AS_INT, bluealive_stat); + addstat(STAT_YELLOWALIVE, AS_INT, yellowalive_stat); + addstat(STAT_PINKALIVE, AS_INT, pinkalive_stat); + addstat(STAT_CAPTURE_PROGRESS, AS_FLOAT, jb_unlock_progress); + addstat(STAT_PRISONED, AS_INT, stat_jb_isprisoned); + addstat(STAT_ROUNDLOST, AS_INT, jb_roundlost); + + g_jailbreak_jail_deathmatch = autocvar_g_jailbreak_jail_deathmatch; + InitializeEntity(world, jb_DelayedInit, INITPRIO_GAMETYPE); +} + +MUTATOR_DEFINITION(gamemode_jailbreak) +{ + MUTATOR_HOOK(OnEntityPreSpawn, jb_OnEntityPreSpawn, CBC_ORDER_FIRST); + MUTATOR_HOOK(reset_map_players, jb_ResetMap, CBC_ORDER_ANY); + MUTATOR_HOOK(PlayerDies, jb_PlayerDies, CBC_ORDER_ANY); + MUTATOR_HOOK(PlayerSpawn, jb_PlayerSpawn, CBC_ORDER_ANY); + MUTATOR_HOOK(SpectateCopy, jb_SpectateCopy, CBC_ORDER_ANY); + MUTATOR_HOOK(ClientDisconnect, jb_RemovePlayer, CBC_ORDER_ANY); + MUTATOR_HOOK(MakePlayerObserver, jb_RemovePlayer, CBC_ORDER_ANY); + MUTATOR_HOOK(PlayerPreThink, jb_PlayerPreThink, CBC_ORDER_ANY); + MUTATOR_HOOK(PlayerDamage_Calculate, jb_PlayerDamage, CBC_ORDER_ANY); + MUTATOR_HOOK(GiveFragsForKill, jb_GiveFrags, CBC_ORDER_ANY); + MUTATOR_HOOK(ForbidThrowCurrentWeapon, jb_ForbidThrowing, CBC_ORDER_ANY); + MUTATOR_HOOK(GetTeamCount, jb_GetTeamCount, CBC_ORDER_ANY); + MUTATOR_HOOK(AllowMobSpawning, jb_AllowMobSpawning, CBC_ORDER_LAST); + + MUTATOR_ONADD + { + if(time > 1) // game loads at time 1 + error("This is a game type and it cannot be added at runtime."); + jb_Initialize(); + } + + MUTATOR_ONROLLBACK_OR_REMOVE + { + // we actually cannot roll back jb_Initialize here + // BUT: we don't need to! If this gets called, adding always + // succeeds. + } + + MUTATOR_ONREMOVE + { + print("This is a game type and it cannot be removed at runtime."); + return -1; + } + + return 0; +} diff --cc qcsrc/server/mutators/gamemode_onslaught.qc index fbb4a04b0,93ed9f7d8..8cfb3cd9a --- a/qcsrc/server/mutators/gamemode_onslaught.qc +++ b/qcsrc/server/mutators/gamemode_onslaught.qc @@@ -1,82 -1,84 +1,83 @@@ +#include "../../common/effects.qh" +#include "../round_handler.qh" +#include "../controlpoint.qh" +#include "../generator.qh" + #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; -float autocvar_g_onslaught_cp_proxydecap_distance = 512; -float autocvar_g_onslaught_cp_proxydecap_dps = 100; - -void onslaught_generator_updatesprite(entity e); -void onslaught_controlpoint_updatesprite(entity e); -void onslaught_link_checkupdate(); - -.entity sprite; -.string target2; -.float iscaptured; -.float islinked; -.float isgenneighbor_red; -.float isgenneighbor_blue; -.float iscpneighbor_red; -.float iscpneighbor_blue; -.float isshielded; -.float lasthealth; -.float lastteam; -.float lastshielded; -.float lastcaptured; +float ons_CaptureShield_Customize() +{ + entity e = WaypointSprite_getviewentity(other); -entity ons_red_generator; -entity ons_blue_generator; + if(!self.enemy.isshielded && (ons_ControlPoint_Attackable(self.enemy, e.team) > 0 || self.enemy.classname != "onslaught_controlpoint")) { return false; } + if(SAME_TEAM(self, e)) { return false; } -void ons_gib_damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector vforce) -{ - self.velocity = self.velocity + vforce; + return true; } -.float giblifetime; -void ons_throwgib_think() +void ons_CaptureShield_Touch() { - float d; + if(!self.enemy.isshielded && (ons_ControlPoint_Attackable(self.enemy, other.team) > 0 || self.enemy.classname != "onslaught_controlpoint")) { return; } + if(!IS_PLAYER(other)) { return; } + if(SAME_TEAM(other, self)) { return; } - self.nextthink = time + 0.05; + vector mymid = (self.absmin + self.absmax) * 0.5; + vector othermid = (other.absmin + other.absmax) * 0.5; - d = self.giblifetime - time; + Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ons_captureshield_force); - if(d<0) + if(IS_REAL_CLIENT(other)) { - self.think = SUB_Remove; - return; + play2(other, "onslaught/damageblockedbyshield.wav"); + + if(self.enemy.classname == "onslaught_generator") + Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_ONS_GENERATOR_SHIELDED); + else + Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_ONS_CONTROLPOINT_SHIELDED); } - if(d<1) - self.alpha = d; +} - if(d>2) - if(random()<0.6) - pointparticles(particleeffectnum("onslaught_generator_gib_flame"), self.origin, '0 0 0', 1); +void ons_CaptureShield_Reset() +{ + self.colormap = self.enemy.colormap; + self.team = self.enemy.team; } -void ons_throwgib(vector v_from, vector v_to, string smodel, float f_lifetime, float b_burn) +void ons_CaptureShield_Spawn(entity generator, float is_generator) { - entity gib; + entity shield = spawn(); + + shield.enemy = generator; + shield.team = generator.team; + shield.colormap = generator.colormap; + shield.reset = ons_CaptureShield_Reset; + shield.touch = ons_CaptureShield_Touch; + shield.customizeentityforclient = ons_CaptureShield_Customize; + shield.classname = "ons_captureshield"; + shield.effects = EF_ADDITIVE; + shield.movetype = MOVETYPE_NOCLIP; + shield.solid = SOLID_TRIGGER; + shield.avelocity = '7 0 11'; + shield.scale = 1; + shield.model = ((is_generator) ? "models/onslaught/generator_shield.md3" : "models/onslaught/controlpoint_shield.md3"); + + precache_model(shield.model); + setorigin(shield, generator.origin); + setmodel(shield, shield.model); + setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs); +} - gib = spawn(); - setmodel(gib, smodel); - setorigin(gib, v_from); - gib.solid = SOLID_BBOX; - gib.movetype = MOVETYPE_BOUNCE; - gib.takedamage = DAMAGE_YES; - gib.event_damage = ons_gib_damage; - gib.health = -1; - gib.effects = EF_LOWPRECISION; - gib.flags = FL_NOTARGET; - gib.velocity = v_to; - gib.giblifetime = time + f_lifetime; +// ========== +// Junk Pile +// ========== - if (b_burn) +void ons_debug(string input) +{ + switch(autocvar_g_onslaught_debug) { - gib.think = ons_throwgib_think; - gib.nextthink = time + 0.05; + case 1: dprint(input); break; + case 2: print(input); break; } - else - SUB_SetFade(gib, gib.giblifetime, 2); } void onslaught_updatelinks() diff --cc qcsrc/server/mutators/mutator_buffs.qc index b039e95f7,942c96513..d7af10a86 --- a/qcsrc/server/mutators/mutator_buffs.qc +++ b/qcsrc/server/mutators/mutator_buffs.qc @@@ -1,6 -1,4 +1,7 @@@ +#include "../../common/effects.qh" +#include "../../common/buffs.qh" +#include "../round_handler.qh" + #include "../../common/triggers/target/music.qh" float buffs_BuffModel_Customize() { diff --cc qcsrc/server/mutators/mutator_dodging.qc index 4abdd22d1,39ecc2fde..69f3007ff --- a/qcsrc/server/mutators/mutator_dodging.qc +++ b/qcsrc/server/mutators/mutator_dodging.qc @@@ -1,6 -1,57 +1,58 @@@ + #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; .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; @@@ -20,50 -71,142 +72,161 @@@ // until it's 0. .float dodging_velocity_gain; - MUTATOR_HOOKFUNCTION(dodging_GetCvars) { - GetCvars_handleFloat(get_cvars_s, get_cvars_f, cvar_cl_dodging, "cl_dodging"); - 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 common_factor; - float new_velocity_gain; - float velocity_difference; - float clean_up_and_do_nothing; - float horiz_speed = autocvar_sv_dodging_horiz_speed; + float PM_dodging_checkpressedkeys() + { + if(!PHYS_DODGING) + return false; - if(self.frozen) - horiz_speed = autocvar_sv_dodging_horiz_speed_frozen; + float frozen_dodging = (PHYS_FROZEN(self) && PHYS_DODGING_FROZEN); + float frozen_no_doubletap = (frozen_dodging && !PHYS_DODGING_FROZEN_NODOUBLETAP); - if (self.deadflag != DEAD_NO) - return 0; + // 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; - new_velocity_gain = 0; - clean_up_and_do_nothing = 0; + makevectors(PHYS_WORLD_ANGLES(self)); - if (g_dodging == 0) - clean_up_and_do_nothing = 1; + 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; + + self.dodging_velocity_gain = PHYS_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 + 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; + ++ float frozen_dodging; ++ bool do_nothing = 0; ++ frozen_dodging = (PHYS_FROZEN(self) && PHYS_DODGING_FROZEN); + + if(!self.cvar_cl_dodging) - clean_up_and_do_nothing = 1; ++ do_nothing = 1; + + if(cvar("g_overkill")) - clean_up_and_do_nothing = 0; // always enabled in overkill ++ do_nothing = 0; // always enabled in overkill + + if(g_dodging == 2) - clean_up_and_do_nothing = 0; // forced enabled ++ do_nothing = 0; // forced enabled + - if(self.frozen) - clean_up_and_do_nothing = 0; // also enabled in freezetag ++ if(frozen_dodging) ++ do_nothing = 0; + - // when swimming, no dodging allowed.. - if (self.waterlevel >= WATERLEVEL_SWIMMING) - clean_up_and_do_nothing = 1; ++ if(self.waterlevel >= WATERLEVEL_SWIMMING) ++ do_nothing = 1; + - if (clean_up_and_do_nothing != 0) { + // when swimming, no dodging allowed.. - if (self.waterlevel >= WATERLEVEL_SWIMMING) ++ if(do_nothing != 0) + { self.dodging_action = 0; self.dodging_direction_x = 0; self.dodging_direction_y = 0; @@@ -122,155 -267,29 +287,30 @@@ 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, "cl_dodging"); + 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; - float do_nothing = 0; - frozen_dodging = (self.frozen && autocvar_sv_dodging_frozen); - frozen_no_doubletap = (frozen_dodging && !autocvar_sv_dodging_frozen_doubletap); - - float dodge_detected = 0; - if (g_dodging == 0) - do_nothing = 1; - - if(!self.cvar_cl_dodging) - do_nothing = 1; - - if(cvar("g_overkill")) - do_nothing = 0; - - if(frozen_dodging) - do_nothing = 0; - - if(g_dodging == 2) - do_nothing = 0; - - if(do_nothing != 0) - return 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) diff --cc qcsrc/server/mutators/mutator_itemeditor.qc index b9134a3bf,000000000..9abc10a94 mode 100644,000000..100644 --- a/qcsrc/server/mutators/mutator_itemeditor.qc +++ b/qcsrc/server/mutators/mutator_itemeditor.qc @@@ -1,419 -1,0 +1,421 @@@ +float ie_itemcount; +.string ie_itemname; + +float ie_enabled; + +float ie_database_loaded; + +string ie_port_string; + +vector rintvec(vector vec) +{ + vector rinted; + rinted_x = rint(vec_x); + rinted_y = rint(vec_y); + rinted_z = rint(vec_z); + return rinted; +} + +void ie_Debug(string input) +{ + switch(autocvar_g_itemeditor_debug) + { + case 1: dprint(input); break; + case 2: print(input); break; + } +} + +entity ie_SpawnItem(float database); +string ie_ItemPort_Save(entity e, float database) +{ + // save item properties, and return them as a string + string s; + entity head = e; + + if(head) + { + // ---------------- ITEM PROPERTY STORAGE: SAVE ---------------- + if(database) { ie_port_string = strcat(ie_port_string, sprintf("\"%.9v\"", rintvec(head.origin)), " "); } + ie_port_string = strcat(ie_port_string, sprintf("\"%.9f\"", head.team), " "); + ie_port_string = strcat(ie_port_string, sprintf("\"%.9f\"", head.cnt), " "); + ie_port_string = strcat(ie_port_string, sprintf("\"%.9f\"", head.noalign), " "); + ie_port_string = strcat(ie_port_string, sprintf("\"%s\"", head.ie_itemname), " "); + } + + // now apply the array to a simple string, with the ; symbol separating items + s = ""; + if(ie_port_string) { s = strcat(s, ie_port_string, "; "); ie_port_string = string_null; } + + return s; +} + +// this will be removed when we have ID based item handling +string ie_FixItemName(string itname) +{ + switch(itname) + { + case "item_armor_small": case "smallarmor": case "armor_small": case "armorshard": + return "item_armor_small"; + case "item_armor_medium": case "mediumarmor": case "armor_medium": + return "item_armor_medium"; + case "item_armor_big": case "bigarmor": case "armor_big": + return "item_armor_big"; + case "item_armor_large": case "largearmor": case "armor_large": case "megaarmor": case "ma": + return "item_armor_large"; + case "item_health_small": case "smallhealth": case "health_small": + return "item_health_small"; + case "item_health_medium": case "mediumhealth": case "health_medium": + return "item_health_medium"; + case "item_health_large": case "largehealth": case "health_large": + return "item_health_large"; + case "item_health_mega": case "megahealth": case "health_mega": case "mh": case "megahealth": + return "item_health_mega"; + case "cells": case "item_cells": case "ammo_cells": + return "item_cells"; + case "bullets": case "nails": case "item_bullets": case "item_nails": case "ammo_bullets": case "ammo_nails": + return "item_bullets"; + case "rockets": case "explosives": case "item_rockets": case "item_explosives": case "ammo_rockets": case "ammo_explosives": + return "item_rockets"; + case "strength": case "item_strength": case "powerup_strength": + return "item_strength"; + case "invincible": case "item_invincible": case "powerup_invincible": + return "item_strength"; + default: + { + float i; + for(i = WEP_FIRST; i <= WEP_LAST; ++i) + if((get_weaponinfo(i)).netname == itname || (get_weaponinfo(i)).netname == substring(itname, 7, strlen(itname))) + return ((get_weaponinfo(i)).netname == substring(itname, 7, strlen(itname)) ? strcat("weapon_", itname) : itname); + return ""; + } + } +} + ++void initialize_field_db(); ++void target_spawn_edit_entity(entity e, string msg, entity kt, entity t2, entity t3, entity t4, entity act); +void ie_SetItemType(entity e) +{ + string fixed_name = ie_FixItemName(e.ie_itemname); + if(fixed_name == "") { return; } + print("Attempting to spawn ", fixed_name, "\n"); + initialize_field_db(); + target_spawn_edit_entity(e, strcat("$ spawnfunc_", fixed_name), world, world, world, world, world); + + e.classname = "itemeditor_item"; +} + +entity ie_ItemPort_Load(string s, float database) +{ + // load item properties, and spawn a new item with them + float n; + entity e = world; + + // separate items between the ; symbols + n = tokenizebyseparator(s, "; "); + ie_port_string = argv(0); + + // now separate and apply the properties of each item + float argv_num = 0; + + tokenize_console(ie_port_string); + e = ie_SpawnItem(database); + + if(database) { setorigin(e, stov(argv(argv_num))); ++argv_num; } + e.team = stof(argv(argv_num)); ++argv_num; + e.cnt = stof(argv(argv_num)); ++argv_num; + e.noalign = stof(argv(argv_num)); ++argv_num; + e.ie_itemname = strzone(argv(argv_num)); ++argv_num; + + print(e.ie_itemname, "\n"); + ie_SetItemType(e); + + ie_port_string = string_null; // fully clear the string + + return e; +} + +void ie_Database_Save() +{ + // saves all items to the database file + entity head; + string file_name; + float file_get; + + file_name = strcat("itemeditor/storage_", autocvar_g_itemeditor_storage_name, "_", GetMapname(), ".txt"); + file_get = fopen(file_name, FILE_WRITE); + fputs(file_get, strcat("// itemeditor storage \"", autocvar_g_itemeditor_storage_name, "\" for map \"", GetMapname(), "\"")); + fputs(file_get, strcat(" containing ", ftos(ie_itemcount), " items\n")); + + for(head = world; (head = find(head, classname, "itemeditor_item")); ) + { + // use a line of text for each item, listing all properties + fputs(file_get, strcat(ie_ItemPort_Save(head, true), "\n")); + } + fclose(file_get); +} + +void ie_Database_Load() +{ + // loads all items from the database file + string file_read, file_name; + float file_get; + + file_name = strcat("itemeditor/storage_", autocvar_g_itemeditor_storage_name, "_", GetMapname(), ".txt"); + file_get = fopen(file_name, FILE_READ); + if(file_get < 0) + { + ie_Debug(strcat("^3ITEMEDITOR - Server: ^7could not find storage file ^3", file_name, "^7, no items were loaded\n")); + } + else + { + for(;;) + { + file_read = fgets(file_get); + if(file_read == "") + break; + if(substring(file_read, 0, 2) == "//") + continue; + if(substring(file_read, 0, 1) == "#") + continue; + + entity e; + e = ie_ItemPort_Load(file_read, true); + } + ie_Debug(strcat("^3ITEMEDITOR - SERVER: ^7successfully loaded storage file ^3", file_name, "\n")); + } + fclose(file_get); + + ie_database_loaded = true; +} + +void ie_Remove(entity e); +void ie_Database_Unload() +{ + entity head; + for(head = world; (head = find(head, classname, "itemeditor_item")); ) + ie_Remove(head); + ie_database_loaded = false; +} + +void ie_Think() +{ + self.nextthink = time; + + // decide if and how this item can be grabbed + if(autocvar_g_itemeditor_readonly) + self.grab = 0; // no grabbing + else + self.grab = 3; // anyone +} + +entity ie_SpawnItem(float database) +{ + entity e = spawn(); + e.classname = "itemeditor_item"; + + if(!database) + { + // set origin and direction based on player position and view angle + makevectors(self.v_angle); + WarpZone_TraceLine(self.origin + self.view_ofs, self.origin + self.view_ofs + v_forward * autocvar_g_itemeditor_spawn_distance, MOVE_NORMAL, self); + setorigin(e, trace_endpos); + } + + if(IS_REAL_CLIENT(self)) { print_to(self, "Spawned new item entity"); } + + ie_itemcount += 1; + + return e; +} + +void ie_Remove(entity e) +{ + if(e.ie_itemname) { strunzone(e.ie_itemname); e.ie_itemname = string_null; } + RemoveItem(e); + e = world; + + ie_itemcount -= 1; +} + +float ie_CheckItem(entity e) +{ + if(!e || e.classname != "itemeditor_item") { return false; } + return true; +} + +MUTATOR_HOOKFUNCTION(ie_ClientCommand) +{ + if(cmd_name == "itemeditor") + { + if(!ie_enabled || autocvar_g_itemeditor_readonly) { sprint(self, "Item editing is currently disabled\n"); return true; } + + if(argv(1) == "spawn") + { + if(!argv(2) || argv(2) == "") { sprint(self, "You must specify an item name\n"); return true; } + if(ie_itemcount >= autocvar_g_itemeditor_max) { sprint(self, "Too many items!\n"); return true; } + + string item_name = strzone(argv(2)); + if(ie_FixItemName(item_name) == "") { sprint(self, "Invalid item\n"); strunzone(item_name); return true; } + + WarpZone_TraceLine(self.origin + self.view_ofs, self.origin + self.view_ofs + v_forward * 100, MOVE_NORMAL, self); + entity e = ie_SpawnItem(false); + setorigin(e, trace_endpos); + e.ie_itemname = item_name; + ie_SetItemType(e); + return true; + } + + if(argv(1) == "remove") + { + entity e, theitem = world; + for(e = WarpZone_FindRadius(self.origin + self.view_ofs + v_forward * 50, 50, false); e; e = e.chain) + if(e.classname == "itemeditor_item") + { + print("Got one\n"); + theitem = e; + break; + } + + + if(ie_CheckItem(theitem)) + { + ie_Remove(theitem); + sprint(self, "Successfully removed an item\n"); + return true; + } + else + { + sprint(self, "Item not found\n"); + return true; + } + } + + if(argv(1) == "edit") + { + if(!argv(2)) { sprint(self, "You must specify a property edit\n"); return true; } + if(!ie_enabled) { sprint(self, "Editing is not enabled\n"); return true; } + + entity e, theitem = world; + for(e = WarpZone_FindRadius(self.origin + self.view_ofs + v_forward * 50, 50, false); e; e = e.chain) + if(e.classname == "itemeditor_item") + { + theitem = e; + break; + } + + if(ie_CheckItem(theitem)) + if(argv(3)) + switch(argv(2)) + { + case "cnt": theitem.cnt = stof(argv(3)); return true; + case "team": theitem.team = stof(argv(3)); return true; + case "noalign": case "float": theitem.noalign = stof(argv(3)); return true; + default: print_to(self, "Unknown option"); return true; + } + } + + sprint(self, "Command was not handled\n"); + return true; + } + + return false; +} + +MUTATOR_HOOKFUNCTION(ie_ServerCommand) +{ + if(cmd_name == "itemeditor") + { + switch(argv(1)) + { + case "enable": + case "start": + { + if(!ie_database_loaded) { print("Enabling editing while database is unloaded could cause chaos, stopping\n"); return true; } + ie_enabled = true; + bprint("Item editing has been enabled!\n"); + return true; + } + case "disable": + case "stop": + { + ie_enabled = false; + bprint("Item editing has been disabled!\n"); + return true; + } + case "load": + { + if(ie_itemcount > 0 || ie_database_loaded) { print("Item database has already been loaded\n"); return true; } + + ie_Database_Load(); + bprint("Item database has been loaded!\n"); + return true; + } + case "unload": + { + if(ie_itemcount <= 0 || !ie_database_loaded) { print("Item database has already been unloaded\n"); return true; } + + ie_enabled = false; // we must disable this, so as to not break stuff + ie_Database_Unload(); + bprint("Item database has been unloaded!\n"); + return true; + } + case "removeitems": + { + entity head; + for(head = world; (head = findflags(head, flags, FL_ITEM)); ) + if(head.items || head.weapon) + if((head.classname != "itemeditor_item" && head.classname != "droppedweapon") || argv(2) == "all") + RemoveItem(head); + + bprint("Regular items removed!\n"); + return true; + } + } + print("Command was not handled\n"); + return true; + } + return false; +} + +float ie_autosave_time; +MUTATOR_HOOKFUNCTION(ie_StartFrame) +{ + entity head; + for(head = world; (head = find(head, classname, "itemeditor_item")); ) + head.grab = (autocvar_g_itemeditor_readonly || !ie_enabled) ? 0 : 3; + + if(!ie_enabled) + return false; + if(!ie_database_loaded) + return false; + if(!autocvar_g_itemeditor_storage_autosave) + return false; + if(time < ie_autosave_time) + return false; + ie_autosave_time = time + autocvar_g_itemeditor_storage_autosave; + + ie_Database_Save(); + + return true; +} + +void ie_DelayedInit() +{ + ie_Database_Load(); +} + +MUTATOR_DEFINITION(mutator_itemeditor) +{ + MUTATOR_HOOK(SV_ParseClientCommand, ie_ClientCommand, CBC_ORDER_ANY); + MUTATOR_HOOK(SV_ParseServerCommand, ie_ServerCommand, CBC_ORDER_ANY); + MUTATOR_HOOK(SV_StartFrame, ie_StartFrame, CBC_ORDER_ANY); + + MUTATOR_ONADD + { + ie_autosave_time = time + autocvar_g_itemeditor_storage_autosave; // don't save the first server frame + if(autocvar_g_itemeditor_storage_autoload) + InitializeEntity(world, ie_DelayedInit, INITPRIO_LAST); + } + + return false; +} diff --cc qcsrc/server/mutators/mutator_multijump.qc index 7a837e836,f78e6e044..36f3381cd --- a/qcsrc/server/mutators/mutator_multijump.qc +++ b/qcsrc/server/mutators/mutator_multijump.qc @@@ -1,36 -1,65 +1,71 @@@ .float multijump_count; .float multijump_ready; +.float cvar_cl_multijump; - 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) ++#define PHYS_MULTIJUMP_MAXSPEED getstatf(STAT_MULTIJUMP_MAXSPEED) + + #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 ++#define PHYS_MULTIJUMP_MAXSPEED autocvar_g_multijump_maxspeed + + + .float stat_multijump; + .float stat_multijump_speed; + .float stat_multijump_add; ++.float stat_multijump_maxspeed; + + void multijump_UpdateStats() { - if(self.flags & FL_ONGROUND) + self.stat_multijump = PHYS_MULTIJUMP; + self.stat_multijump_speed = PHYS_MULTIJUMP_SPEED; + self.stat_multijump_add = PHYS_MULTIJUMP_ADD; ++ self.stat_multijump_maxspeed = PHYS_MULTIJUMP_MAXSPEED; + } + + 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); ++ addstat(STAT_MULTIJUMP_MAXSPEED, AS_FLOAT, stat_multijump_maxspeed); + } + + #endif + + void PM_multijump() + { + if(!PHYS_MULTIJUMP) { return; } + + if(IS_ONGROUND(self)) { - if (autocvar_g_multijump > 0) - self.multijump_count = 0; - else - self.multijump_count = -2; // the cvar value for infinite jumps is -1, so this needs to be smaller + self.multijump_count = 0; } - - return false; } - MUTATOR_HOOKFUNCTION(multijump_PlayerJump) + float PM_multijump_checkjump() { - if (self.cvar_cl_multijump) - if (self.flags & FL_JUMPRELEASED && !(self.flags & FL_ONGROUND)) // jump button pressed this frame and we are in midair + if(!PHYS_MULTIJUMP) { return false; } + - if (!IS_JUMP_HELD(self) && !IS_ONGROUND(self)) // jump button pressed this frame and we are in midair ++ if (!IS_JUMP_HELD(self) && !IS_ONGROUND(self) && self.cvar_cl_multijump) // 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 && vlen(self.velocity) <= autocvar_g_multijump_maxspeed) - if(!player_multijump && self.multijump_ready && (self.multijump_count < PHYS_MULTIJUMP || PHYS_MULTIJUMP == -1) && self.velocity_z > PHYS_MULTIJUMP_SPEED) ++ if(!player_multijump && self.multijump_ready && (self.multijump_count < PHYS_MULTIJUMP || PHYS_MULTIJUMP == -1) && self.velocity_z > PHYS_MULTIJUMP_SPEED && vlen(self.velocity) <= PHYS_MULTIJUMP_MAXSPEED) { - if (autocvar_g_multijump) - if (self.cvar_cl_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; @@@ -50,16 -80,19 +86,20 @@@ 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! } - if (autocvar_g_multijump > 0) - self.multijump_count += 1; ++ if (PHYS_MULTIJUMP > 0) + self.multijump_count += 1; } } self.multijump_ready = false; // require releasing and pressing the jump button again for the next jump @@@ -68,12 -101,20 +108,26 @@@ 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_GetCvars) +{ + GetCvars_handleFloat(get_cvars_s, get_cvars_f, cvar_cl_multijump, "cl_multijump"); + return false; +} + MUTATOR_HOOKFUNCTION(multijump_BuildMutatorsString) { ret_string = strcat(ret_string, ":multijump"); diff --cc qcsrc/server/mutators/mutator_nades.qc index 2d921dc9b,dc1b6bc3b..438ba9740 --- a/qcsrc/server/mutators/mutator_nades.qc +++ b/qcsrc/server/mutators/mutator_nades.qc @@@ -1,5 -1,4 +1,6 @@@ +#include "../../common/effects.qh" +#include "../round_handler.qh" + #include "../../common/triggers/target/music.qh" .entity nade_spawnloc; diff --cc qcsrc/server/mutators/mutators_include.qc index bd7e3a791,c3bdec50c..359c1bddd --- a/qcsrc/server/mutators/mutators_include.qc +++ b/qcsrc/server/mutators/mutators_include.qc @@@ -74,9 -74,9 +74,8 @@@ #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" + #include "../../common/vehicles/vehicles.qh" #endif #include "base.qc" diff --cc qcsrc/server/progs.src index 9698a6634,9cab150c3..7697b6b7a --- a/qcsrc/server/progs.src +++ b/qcsrc/server/progs.src @@@ -13,21 -13,17 +13,18 @@@ campaign.q cheats.qc cl_client.qc cl_impulse.qc - cl_physics.qc cl_player.qc +controlpoint.qc csqceffects.qc // ctf.qc // domination.qc ent_cs.qc - func_breakable.qc +generator.qc g_casings.qc g_damage.qc g_hook.qc g_models.qc g_subs.qc - g_triggers.qc -g_tetris.qc g_violence.qc g_world.qc ipban.qc @@@ -52,15 -46,8 +48,9 @@@ sv_main.q 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 +t_viewloc.qc waypointsprites.qc bot/bot.qc @@@ -100,20 -85,14 +90,23 @@@ weapons/weaponsystem.q ../common/monsters/monsters.qc ../common/monsters/spawn.qc ../common/monsters/sv_monsters.qc +../common/minigames/minigames.qc +../common/minigames/sv_minigames.qc + ../common/movetypes/include.qc ../common/nades.qc ../common/net_notice.qc ../common/notifications.qc + ../common/physics.qc ../common/playerstats.qc +../common/p2mathlib.qc ../common/test.qc +../common/turrets/targettrigger.qc +../common/turrets/sv_turrets.qc +../common/turrets/turrets.qc +../common/turrets/util.qc +../common/vehicles/vehicles_include.qc +../common/viewloc.qc + ../common/triggers/include.qc ../common/urllib.qc ../common/util.qc ../common/weapons/config.qc diff --cc qcsrc/server/race.qh index 7d163cc94,b51c1a9c7..0459cea57 --- a/qcsrc/server/race.qh +++ b/qcsrc/server/race.qh @@@ -42,6 -29,40 +42,7 @@@ float race_GetFractionalLapCount(entit 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 -} +void race_send_speedaward(float msg); +void race_send_speedaward_alltimebest(float msg); + #endif diff --cc qcsrc/server/t_items.qc index e96af453e,4c454c70e..f995f721d --- a/qcsrc/server/t_items.qc +++ b/qcsrc/server/t_items.qc @@@ -15,9 -15,9 +15,10 @@@ #include "../dpdefs/dpextensions.qh" #include "../warpzonelib/util_server.qh" #include "../common/constants.qh" + #include "../common/effects.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 --cc wtf.diff index 000000000,000000000..1ade5ccae new file mode 100644 --- /dev/null +++ b/wtf.diff @@@ -1,0 -1,0 +1,26149 @@@ ++diff --git a/defaultXonotic.cfg b/defaultXonotic.cfg ++index f66dc01..19643ec 100644 ++--- a/defaultXonotic.cfg +++++ b/defaultXonotic.cfg ++@@ -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" ++diff --git a/physicsX.cfg b/physicsX.cfg ++index e03a6d4..c109e11 100644 ++--- a/physicsX.cfg +++++ b/physicsX.cfg ++@@ -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 "" ++diff --git a/physicsX010.cfg b/physicsX010.cfg ++index 77926d0..6dadcf1 100644 ++--- a/physicsX010.cfg +++++ b/physicsX010.cfg ++@@ -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 "" ++diff --git a/physicsX07.cfg b/physicsX07.cfg ++index e2a3ef6..a3e42a9 100644 ++--- a/physicsX07.cfg +++++ b/physicsX07.cfg ++@@ -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 "" ++diff --git a/physicsXDF.cfg b/physicsXDF.cfg ++index 0acb6e9..8597e9e 100644 ++--- a/physicsXDF.cfg +++++ b/physicsXDF.cfg ++@@ -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 ++diff --git a/physicsXDFLight.cfg b/physicsXDFLight.cfg ++index cac90b4..6cb0780 100644 ++--- a/physicsXDFLight.cfg +++++ b/physicsXDFLight.cfg ++@@ -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 "" ++diff --git a/qcsrc/client/bgmscript.qc b/qcsrc/client/bgmscript.qc ++index b567190..615b571 100644 ++--- a/qcsrc/client/bgmscript.qc +++++ b/qcsrc/client/bgmscript.qc ++@@ -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" ++diff --git a/qcsrc/client/bgmscript.qh b/qcsrc/client/bgmscript.qh ++index 910ef6b..fba2876 100644 ++--- a/qcsrc/client/bgmscript.qh +++++ b/qcsrc/client/bgmscript.qh ++@@ -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); ++diff --git a/qcsrc/client/casings.qc b/qcsrc/client/casings.qc ++index 6068a79..38c5c67 100644 ++--- a/qcsrc/client/casings.qc +++++ b/qcsrc/client/casings.qc ++@@ -1,5 +1,5 @@ ++ #if defined(CSQC) ++- #include "movetypes.qh" +++ #include "../common/movetypes/movetypes.qh" ++ #include "prandom.qh" ++ #include "rubble.qh" ++ ++diff --git a/qcsrc/client/command/cl_cmd.qc b/qcsrc/client/command/cl_cmd.qc ++index 7b74d2d..0748483 100644 ++--- a/qcsrc/client/command/cl_cmd.qc +++++ b/qcsrc/client/command/cl_cmd.qc ++@@ -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 */ ++diff --git a/qcsrc/client/csqcmodel_hooks.qc b/qcsrc/client/csqcmodel_hooks.qc ++index dde4461..f1a8f64 100644 ++--- a/qcsrc/client/csqcmodel_hooks.qc +++++ b/qcsrc/client/csqcmodel_hooks.qc ++@@ -657,10 +657,10 @@ void CSQCModel_Hook_PreDraw(bool isplayer) ++ bool onground = 0; ++ if(self == csqcplayer) ++ { ++- if(self.pmove_flags & PMF_ONGROUND) +++ if(self.flags & FL_ONGROUND) ++ onground = 1; ++- self.anim_prev_pmove_flags = self.pmove_flags; ++- if(self.pmove_flags & PMF_DUCKED) +++ self.anim_prev_pmove_flags = self.flags; +++ if(self.flags & FL_DUCKED) ++ animdecide_setstate(self, self.anim_state | ANIMSTATE_DUCK, false); ++ else if(self.anim_state & ANIMSTATE_DUCK) ++ animdecide_setstate(self, self.anim_state - ANIMSTATE_DUCK, false); ++diff --git a/qcsrc/client/damage.qc b/qcsrc/client/damage.qc ++index 000ef8b..836c916 100644 ++--- a/qcsrc/client/damage.qc +++++ b/qcsrc/client/damage.qc ++@@ -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; ++ } ++ ++diff --git a/qcsrc/client/effects.qc b/qcsrc/client/effects.qc ++index 9562350..0cc1e20 100644 ++--- a/qcsrc/client/effects.qc +++++ b/qcsrc/client/effects.qc ++@@ -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 ++index a74c668..0000000 ++--- a/qcsrc/client/laser.qc +++++ /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; ++-} ++diff --git a/qcsrc/client/main.qc b/qcsrc/client/main.qc ++index 13f9545..f51674b 100644 ++--- a/qcsrc/client/main.qc +++++ b/qcsrc/client/main.qc ++@@ -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,17 @@ 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; +++ case ENT_CLIENT_TRIGGER_IMPULSE: ent_trigger_impulse(); break; ++ ++ default: ++ //error(strcat(_("unknown entity type in CSQC_Ent_Update: %d\n"), self.enttype)); ++diff --git a/qcsrc/client/miscfunctions.qc b/qcsrc/client/miscfunctions.qc ++index d74a24a..683f455 100644 ++--- a/qcsrc/client/miscfunctions.qc +++++ b/qcsrc/client/miscfunctions.qc ++@@ -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; ++@@ -349,11 +459,11 @@ void PolyDrawModel(entity e) ++ break; ++ } ++ ++-void DrawCircleClippedPic(vector centre, float radius, string pic, float f, vector rgb, float a, float drawflag) +++void DrawCircleClippedPic(vector centre, float radi, string pic, float f, vector rgb, float a, float drawflag) ++ { ++ float x, y, q, d; ++ vector ringsize, v, t; ++- ringsize = radius * '1 1 0'; +++ ringsize = radi * '1 1 0'; ++ ++ x = cos(f * 2 * M_PI); ++ y = sin(f * 2 * M_PI); ++diff --git a/qcsrc/client/miscfunctions.qh b/qcsrc/client/miscfunctions.qh ++index 6c64396..9b97b88 100644 ++--- a/qcsrc/client/miscfunctions.qh +++++ b/qcsrc/client/miscfunctions.qh ++@@ -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; ++@@ -132,7 +154,7 @@ void drawcolorcodedstring_aspect_expanding(vector pos, string text, vector sz, f ++ float PolyDrawModelSurface(entity e, float i_s); ++ void PolyDrawModel(entity e); ++ ++-void DrawCircleClippedPic(vector centre, float radius, string pic, float f, vector rgb, float a, float drawflag); +++void DrawCircleClippedPic(vector centre, float radi, string pic, float f, vector rgb, float a, float drawflag); ++ ++ const vector GETPLAYERORIGIN_ERROR = '1123581321 2357111317 3141592653'; // way out of bounds for anything on the map ++ vector getplayerorigin(int pl); ++diff --git a/qcsrc/client/movetypes.qc b/qcsrc/client/movetypes.qc ++deleted file mode 100644 ++index 2f62ae6..0000000 ++--- a/qcsrc/client/movetypes.qc +++++ /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 ++index 3294ce4..0000000 ++--- a/qcsrc/client/movetypes.qh +++++ /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 ++diff --git a/qcsrc/client/particles.qc b/qcsrc/client/particles.qc ++index 6d2ddf8..59b9b75 100644 ++--- a/qcsrc/client/particles.qc +++++ b/qcsrc/client/particles.qc ++@@ -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; ++diff --git a/qcsrc/client/particles.qh b/qcsrc/client/particles.qh ++index 12c999b..a70aef8 100644 ++--- a/qcsrc/client/particles.qh +++++ b/qcsrc/client/particles.qh ++@@ -1,6 +1,5 @@ ++ #ifndef PARTICLES_H ++ #define PARTICLES_H ++- ++ .int dphitcontentsmask; ++ ++ .int cnt; // effect number ++@@ -14,19 +13,7 @@ ++ .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 ++diff --git a/qcsrc/client/player_skeleton.qc b/qcsrc/client/player_skeleton.qc ++index 0a9ac8c..d8b0aaa 100644 ++--- a/qcsrc/client/player_skeleton.qc +++++ b/qcsrc/client/player_skeleton.qc ++@@ -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) ++ { ++diff --git a/qcsrc/client/progs.src b/qcsrc/client/progs.src ++index f80da18..a72a210 100644 ++--- a/qcsrc/client/progs.src +++++ b/qcsrc/client/progs.src ++@@ -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 ++index 662a673..0000000 ++--- a/qcsrc/client/target_music.qc +++++ /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 ++index c685214..0000000 ++--- a/qcsrc/client/target_music.qh +++++ /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 ++diff --git a/qcsrc/client/vehicles/vehicles.qc b/qcsrc/client/vehicles/vehicles.qc ++index 290385e..69afac8 100644 ++--- a/qcsrc/client/vehicles/vehicles.qc +++++ b/qcsrc/client/vehicles/vehicles.qc ++@@ -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" ++diff --git a/qcsrc/client/view.qc b/qcsrc/client/view.qc ++index ee8ef32..f7be345 100644 ++--- a/qcsrc/client/view.qc +++++ b/qcsrc/client/view.qc ++@@ -1738,6 +1738,7 @@ void CSQC_UpdateView(float w, float h) ++ UpdateDamage(); ++ UpdateCrosshair(); ++ UpdateHitsound(); +++ InitializeEntitiesRun(); ++ ++ if(NextFrameCommand) ++ { ++diff --git a/qcsrc/client/waypointsprites.qc b/qcsrc/client/waypointsprites.qc ++index 788dd87..4a8b156 100644 ++--- a/qcsrc/client/waypointsprites.qc +++++ b/qcsrc/client/waypointsprites.qc ++@@ -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 ++diff --git a/qcsrc/client/weapons/projectile.qc b/qcsrc/client/weapons/projectile.qc ++index 1d306fe..51236fd 100644 ++--- a/qcsrc/client/weapons/projectile.qc +++++ b/qcsrc/client/weapons/projectile.qc ++@@ -469,6 +469,7 @@ void Ent_Projectile() ++ if(!(self.count & 0x80)) ++ InterpolateOrigin_Note(); ++ +++ self.classname = "csqcprojectile"; ++ self.draw = Projectile_Draw; ++ self.entremove = Ent_RemoveProjectile; ++ } ++diff --git a/qcsrc/common/constants.qh b/qcsrc/common/constants.qh ++index 45a65ab..9e1355b 100644 ++--- a/qcsrc/common/constants.qh +++++ b/qcsrc/common/constants.qh ++@@ -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; +++const int ENT_CLIENT_TRIGGER_IMPULSE = 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; ++ ++diff --git a/qcsrc/common/csqcmodel_settings.qh b/qcsrc/common/csqcmodel_settings.qh ++index fdc555e..5e5ff42 100644 ++--- a/qcsrc/common/csqcmodel_settings.qh +++++ b/qcsrc/common/csqcmodel_settings.qh ++@@ -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 ++diff --git a/qcsrc/common/monsters/sv_monsters.qc b/qcsrc/common/monsters/sv_monsters.qc ++index 28430e9..3526a1f 100644 ++--- a/qcsrc/common/monsters/sv_monsters.qc +++++ b/qcsrc/common/monsters/sv_monsters.qc ++@@ -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 ++index 0000000..64d92d4 ++--- /dev/null +++++ b/qcsrc/common/movetypes/include.qc ++@@ -0,0 +1,5 @@ +++#include "push.qc" +++#include "toss.qc" +++#include "walk.qc" +++ +++#include "movetypes.qc" ++diff --git a/qcsrc/common/movetypes/include.qh b/qcsrc/common/movetypes/include.qh ++new file mode 100644 ++index 0000000..a96e595 ++--- /dev/null +++++ b/qcsrc/common/movetypes/include.qh ++@@ -0,0 +1,8 @@ +++#ifndef MOVETYPES_INCLUDE_H +++#define MOVETYPES_INCLUDE_H +++ +++#include "push.qh" +++#include "toss.qh" +++#include "walk.qh" +++ +++#endif ++diff --git a/qcsrc/common/movetypes/movetypes.qc b/qcsrc/common/movetypes/movetypes.qc ++new file mode 100644 ++index 0000000..a7351ac ++--- /dev/null +++++ b/qcsrc/common/movetypes/movetypes.qc ++@@ -0,0 +1,683 @@ +++#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_WallFriction(vector stepnormal) // SV_WallFriction +++{ +++ /*float d, i; +++ vector into, side; +++ makevectors(self.v_angle); +++ d = (stepnormal * v_forward) + 0.5; +++ +++ if(d < 0) +++ { +++ i = (stepnormal * self.move_velocity); +++ into = i * stepnormal; +++ side = self.move_velocity - into; +++ self.move_velocity_x = side.x * (1 * d); +++ self.move_velocity_y = side.y * (1 * d); +++ }*/ +++} +++ +++vector planes[MAX_CLIP_PLANES]; +++int _Movetype_FlyMove(float dt, bool applygravity, vector stepnormal, float stepheight) // SV_FlyMove +++{ +++ int blocked = 0, bumpcount; +++ int i, j, numplanes = 0; +++ float time_left = dt, grav = 0; +++ vector push; +++ vector primal_velocity, original_velocity, new_velocity = '0 0 0', restore_velocity; +++ +++ for(i = 0; i <= MAX_CLIP_PLANES; ++i) +++ planes[i] = '0 0 0'; +++ +++ grav = 0; +++ +++ restore_velocity = self.move_velocity; +++ +++ if(applygravity) +++ { +++ self.move_didgravity = 1; +++ grav = dt * (PHYS_ENTGRAVITY(self) ? PHYS_ENTGRAVITY(self) : 1) * PHYS_GRAVITY; +++ +++ if(!GAMEPLAYFIX_NOGRAVITYONGROUND || !(self.move_flags & FL_ONGROUND)) +++ { +++ if(GRAVITY_UNAFFECTED_BY_TICRATE) +++ self.move_velocity_z -= grav * 0.5; +++ else +++ self.move_velocity_z -= grav; +++ } +++ } +++ +++ original_velocity = primal_velocity = self.move_velocity; +++ +++ for(bumpcount = 0;bumpcount < MAX_CLIP_PLANES;bumpcount++) +++ { +++ if(!self.move_velocity_x && !self.move_velocity_y && !self.move_velocity_z) +++ break; +++ +++ push = self.move_velocity * time_left; +++ if(!_Movetype_PushEntity(push, false)) +++ { +++ // we got teleported by a touch function +++ // let's abort the move +++ blocked |= 8; +++ break; +++ } +++ +++ // this code is used by MOVETYPE_WALK and MOVETYPE_STEP and SV_UnstickEntity +++ // abort move if we're stuck in the world (and didn't make it out) +++ if(trace_startsolid && trace_allsolid) +++ { +++ self.move_velocity = restore_velocity; +++ return 3; +++ } +++ +++ if(trace_fraction == 1) +++ break; +++ if(trace_plane_normal_z) +++ { +++ if(trace_plane_normal_z > 0.7) +++ { +++ // floor +++ blocked |= 1; +++ +++ if(!trace_ent) +++ { +++ //dprint("_Movetype_FlyMove: !trace_ent\n"); +++ trace_ent = world; +++ } +++ +++ self.move_flags |= FL_ONGROUND; +++ self.move_groundentity = trace_ent; +++ } +++ } +++ else if(stepheight) +++ { +++ // step - handle it immediately +++ vector org; +++ vector steppush; +++ //Con_Printf("step %f %f %f : ", self.move_origin_x, PRVM_serveredictvector(ent, origin)[1], PRVM_serveredictvector(ent, origin)[2]); +++ steppush = '0 0 1' * stepheight; +++ org = self.move_origin; +++ if(!_Movetype_PushEntity(steppush, false)) +++ { +++ blocked |= 8; +++ break; +++ } +++ //Con_Printf("%f %f %f : ", self.move_origin_x, PRVM_serveredictvector(ent, origin)[1], PRVM_serveredictvector(ent, origin)[2]); +++ if(!_Movetype_PushEntity(push, false)) +++ { +++ blocked |= 8; +++ break; +++ } +++ float trace2_fraction = trace_fraction; +++ //Con_Printf("%f %f %f : ", self.move_origin_x, PRVM_serveredictvector(ent, origin)[1], PRVM_serveredictvector(ent, origin)[2]); +++ steppush = '0 0 1' * (org_z - self.move_origin_z); +++ if(!_Movetype_PushEntity(steppush, false)) +++ { +++ blocked |= 8; +++ break; +++ } +++ //Con_Printf("%f %f %f : ", self.move_origin_x, PRVM_serveredictvector(ent, origin)[1], PRVM_serveredictvector(ent, origin)[2]); +++ // accept the new position if it made some progress... +++ if(fabs(self.move_origin_x - org_x) >= 0.03125 || fabs(self.move_origin_y - org_y) >= 0.03125) +++ { +++ //Con_Printf("accepted (delta %f %f %f)\n", self.move_origin_x - org_x, PRVM_serveredictvector(ent, origin)[1] - org[1], PRVM_serveredictvector(ent, origin)[2] - org[2]); +++ trace_endpos = self.move_origin; +++ time_left *= 1 - trace2_fraction; +++ numplanes = 0; +++ continue; +++ } +++ else +++ { +++ //Con_Printf("REJECTED (delta %f %f %f)\n", self.move_origin_x - org_x, PRVM_serveredictvector(ent, origin)[1] - org[1], PRVM_serveredictvector(ent, origin)[2] - org[2]); +++ self.move_origin = org; +++ } +++ } +++ else +++ { +++ // step - return it to caller +++ blocked |= 2; +++ // save the trace for player extrafriction +++ if(stepnormal) +++ stepnormal = trace_plane_normal; +++ } +++ if(trace_fraction >= 0.001) +++ { +++ // actually covered some distance +++ original_velocity = self.move_velocity; +++ numplanes = 0; +++ } +++ +++ time_left *= 1 - trace_fraction; +++ +++ // clipped to another plane +++ if(numplanes >= MAX_CLIP_PLANES) +++ { +++ // this shouldn't really happen +++ self.move_velocity = '0 0 0'; +++ blocked = 3; +++ break; +++ } +++ +++ planes[numplanes] = trace_plane_normal; +++ numplanes++; +++ +++ // modify original_velocity so it parallels all of the clip planes +++ for (i = 0;i < numplanes;i++) +++ { +++ new_velocity = _Movetype_ClipVelocity(original_velocity, planes[i], 1); +++ for (j = 0;j < numplanes;j++) +++ { +++ if(j != i) +++ { +++ // not ok +++ if((new_velocity * planes[j]) < 0) +++ break; +++ } +++ } +++ if(j == numplanes) +++ break; +++ } +++ +++ if(i != numplanes) +++ { +++ // go along this plane +++ self.move_velocity = new_velocity; +++ } +++ else +++ { +++ // go along the crease +++ if(numplanes != 2) +++ { +++ self.move_velocity = '0 0 0'; +++ blocked = 7; +++ break; +++ } +++ vector dir; +++ dir.x = planes[0].y * planes[1].z - planes[0].z * planes[1].y; +++ dir.y = planes[0].z * planes[1].x - planes[0].x * planes[1].z; +++ dir.z = planes[0].x * planes[1].y - planes[0].y * planes[1].x; +++ // LordHavoc: thanks to taniwha of QuakeForge for pointing out this fix for slowed falling in corners +++ float ilength = sqrt((dir * dir)); +++ if(ilength) +++ ilength = 1.0 / ilength; +++ dir.x *= ilength; +++ dir.y *= ilength; +++ dir.z *= ilength; +++ float d = (dir * self.move_velocity); +++ self.move_velocity = dir * d; +++ } +++ +++ // if current velocity is against the original velocity, +++ // stop dead to avoid tiny occilations in sloping corners +++ if((self.move_velocity * primal_velocity) <= 0) +++ { +++ self.move_velocity = '0 0 0'; +++ break; +++ } +++ } +++ +++ // LordHavoc: this came from QW and allows you to get out of water more easily +++ if(GAMEPLAYFIX_EASIERWATERJUMP && (self.move_flags & FL_WATERJUMP) && !(blocked & 8)) +++ self.move_velocity = primal_velocity; +++ +++ if(applygravity) +++ { +++ if(!GAMEPLAYFIX_NOGRAVITYONGROUND || !(self.move_flags & FL_ONGROUND)) +++ { +++ if(GRAVITY_UNAFFECTED_BY_TICRATE) +++ self.move_velocity_z -= grav * 0.5f; +++ } +++ } +++ +++ return blocked; +++} +++ +++void _Movetype_CheckVelocity() // SV_CheckVelocity +++{ +++ // if(vlen(self.move_velocity) < 0.0001) +++ // self.move_velocity = '0 0 0'; +++} +++ +++bool _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 && 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 +++{ +++ int 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 oldself = self; +++ entity 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 oldself = self; +++ entity oldother = other; +++ +++ for (entity e = findradius(0.5 * (self.absmin + self.absmax), 0.5 * vlen(self.absmax - self.absmin)); e; e = e.chain) +++ { +++ if(e.move_touch && 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(bool 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 += self.move_origin; +++ ma += self.move_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(); +++} +++ +++bool _Movetype_TestEntityPosition(vector ofs) // SV_TestEntityPosition +++{ +++// vector 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; +++} +++ +++bool _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; +++ for (int 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 * 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 = self.move_origin + push; +++ int type; +++ 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, bool 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 +++{ +++ v_forward = v_right = v_up = '0 0 0'; +++ +++ float y = myangles.y * (M_PI * 2 / 360); +++ float sy = sin(y); +++ float cy = cos(y); +++ float p = myangles.x * (M_PI * 2 / 360); +++ float sp = sin(p); +++ float cp = cos(p); +++ 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) +++ { +++ float r = myangles.z * (M_PI * 2 / 360); +++ float sr = sin(r); +++ float cr = cos(r); +++ 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: +++ _Movetype_Physics_Walk(movedt); +++ 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 = 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 +++{ +++ if(tr <= 0) +++ { +++ Movetype_Physics_NoMatchServer(); +++ return; +++ } +++ +++ float dt = time - self.move_time; +++ +++ int 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 (int i = 0; i < n; ++i) +++ { +++ _Movetype_Physics_Frame(tr); +++ 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) +++ { +++ self.velocity_z -= (GRAVITY_UNAFFECTED_BY_TICRATE ? 0.5 : 1) +++ * dt +++ * (self.gravity ? self.gravity : 1) +++ * PHYS_GRAVITY; +++ } +++ +++ self.angles = self.move_angles + dt * self.avelocity; +++ +++ if(sloppy || self.move_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 && GRAVITY_UNAFFECTED_BY_TICRATE) +++ self.velocity_z -= 0.5 * dt * (self.gravity ? self.gravity : 1) * 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 ++index 0000000..88b545f ++--- /dev/null +++++ b/qcsrc/common/movetypes/movetypes.qh ++@@ -0,0 +1,96 @@ +++#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_WallFriction(vector stepnormal); +++int _Movetype_FlyMove(float dt, bool applygravity, vector stepnormal, float stepheight); +++void _Movetype_CheckVelocity(); +++void _Movetype_CheckWaterTransition(entity ent); +++float _Movetype_CheckWater(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 ++index 0000000..1f563f5 ++--- /dev/null +++++ b/qcsrc/common/movetypes/push.qc ++@@ -0,0 +1,161 @@ +++void _Movetype_PushMove(float dt) // SV_PushMove +++{ +++ 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; +++ } +++ +++ bool rotated = (self.move_angles * self.move_angles) + (self.move_avelocity * self.move_avelocity) > 0; +++ +++ vector move1 = self.move_velocity * dt; +++ vector moveangle = self.move_avelocity * dt; +++ +++ makevectors_matrix(-moveangle); +++ +++// vector pushorig = self.move_origin; +++// vector pushang = self.move_angles; +++// float 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); +++ +++ int savesolid = self.solid; +++ +++ if (self.move_movetype != MOVETYPE_FAKEPUSH) +++ { +++ for (entity 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; +++ +++ vector pivot = check.mins + 0.5 * (check.maxs - check.mins); +++ vector move; +++ if (rotated) +++ { +++ vector org = (check.move_origin - self.move_origin) + pivot; +++ vector org2; +++ org2.x = org * v_forward; +++ org2.y = org * v_right; +++ org2.z = org * v_up; +++ move = (org2 - org) + 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; +++ entity oldself = self; +++ self = check; +++ _Movetype_LinkEdict(true); +++ self = oldself; +++ continue; +++ } +++ +++ // try moving the contacted entity +++ self.solid = SOLID_NOT; +++ entity 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 oldltime = self.move_ltime; +++ float thinktime = self.move_nextthink; +++ float movetime; +++ 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 ++index 0000000..44d5442 ++--- /dev/null +++++ b/qcsrc/common/movetypes/push.qh ++@@ -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 ++index 0000000..6ed0407 ++--- /dev/null +++++ b/qcsrc/common/movetypes/toss.qc ++@@ -0,0 +1,115 @@ +++#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; +++ self.move_velocity_z -= (GRAVITY_UNAFFECTED_BY_TICRATE ? 0.5 : 1) +++ * dt +++ * (self.gravity ? self.gravity : 1) +++ * PHYS_GRAVITY; +++ } +++ +++ self.move_angles = self.move_angles + self.move_avelocity * dt; +++ +++ float movetime = dt; +++ for (int bump = 0; bump < MAX_CLIP_PLANES && movetime > 0; ++bump) +++ { +++ vector 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 bouncefac = self.move_bounce_factor; if (!bouncefac) bouncefac = 0.5; +++ float bouncestop = self.move_bounce_stopspeed; if (!bouncestop) bouncestop = 60 / 800; +++ bouncestop *= (self.gravity ? self.gravity : 1) * PHYS_GRAVITY; +++ +++ self.move_velocity = _Movetype_ClipVelocity(self.move_velocity, trace_plane_normal, 1 + bouncefac); +++ +++ float 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 && self.move_didgravity > 0 && !(self.move_flags & FL_ONGROUND)) +++ self.move_velocity_z -= 0.5 * dt * (self.gravity ? self.gravity : 1) * PHYS_GRAVITY; +++ +++ _Movetype_CheckWaterTransition(self); +++} ++diff --git a/qcsrc/common/movetypes/toss.qh b/qcsrc/common/movetypes/toss.qh ++new file mode 100644 ++index 0000000..9e11595 ++--- /dev/null +++++ b/qcsrc/common/movetypes/toss.qh ++@@ -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 ++diff --git a/qcsrc/common/movetypes/walk.qc b/qcsrc/common/movetypes/walk.qc ++new file mode 100644 ++index 0000000..cbcfc5b ++--- /dev/null +++++ b/qcsrc/common/movetypes/walk.qc ++@@ -0,0 +1,167 @@ +++void _Movetype_Physics_Walk(float dt) // SV_WalkMove +++{ +++ vector stepnormal = '0 0 0'; +++ +++ // if frametime is 0 (due to client sending the same timestamp twice), don't move +++ if (dt <= 0) +++ return; +++ +++ if (GAMEPLAYFIX_UNSTICKPLAYERS) +++ _Movetype_UnstickEntity(); +++ +++ bool applygravity = (!_Movetype_CheckWater(self) && self.move_movetype == MOVETYPE_WALK && !(self.move_flags & FL_WATERJUMP)); +++ +++ _Movetype_CheckVelocity(); +++ +++ // do a regular slide move unless it looks like you ran into a step +++ bool oldonground = (self.move_flags & FL_ONGROUND); +++ +++ vector start_origin = self.move_origin; +++ vector start_velocity = self.move_velocity; +++ +++ int clip = _Movetype_FlyMove(dt, applygravity, stepnormal, GAMEPLAYFIX_STEPMULTIPLETIMES ? PHYS_STEPHEIGHT : 0); +++ +++ if (GAMEPLAYFIX_DOWNTRACEONGROUND && !(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.move_origin + '0 0 1'; +++ vector downmove = self.move_origin - '0 0 1'; +++ int type; +++ if (self.move_movetype == MOVETYPE_FLYMISSILE) type = MOVE_MISSILE; else if (self.move_movetype == MOVETYPE_FLY_WORLDONLY) +++ type = MOVE_WORLDONLY; +++ else if (self.solid == SOLID_TRIGGER || self.solid == SOLID_NOT) +++ type = MOVE_NOMONSTERS; +++ else type = MOVE_NORMAL; +++ tracebox(upmove, self.mins, self.maxs, 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.move_flags &= ~FL_ONGROUND; +++ +++ _Movetype_CheckVelocity(); +++ _Movetype_LinkEdict(true); +++ +++ if (clip & 8) // teleport +++ return; +++ +++ if (self.move_flags & FL_WATERJUMP) +++ return; +++ +++ if (PHYS_NOSTEP) +++ return; +++ +++ vector originalmove_origin = self.move_origin; +++ vector originalmove_velocity = self.move_velocity; +++ // originalmove_clip = clip; +++ int originalmove_flags = self.move_flags; +++ entity originalmove_groundentity = self.move_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.move_movetype != MOVETYPE_FLY) +++ { +++ // return if gibbed by a trigger +++ if (self.move_movetype != MOVETYPE_WALK) +++ return; +++ +++ // return if attempting to jump while airborn (unless sv_jumpstep) +++ if (!PHYS_JUMPSTEP) +++ if (!oldonground && self.move_waterlevel == 0) +++ return; +++ } +++ +++ // try moving up and forward to go up a step +++ // back to start pos +++ self.move_origin = start_origin; +++ self.move_velocity = start_velocity; +++ +++ // move up +++ vector upmove = '0 0 1' * PHYS_STEPHEIGHT; +++ if (!_Movetype_PushEntity(upmove, true)) +++ { +++ // we got teleported when upstepping... must abort the move +++ return; +++ } +++ +++ // move forward +++ self.move_velocity_z = 0; +++ clip = _Movetype_FlyMove(dt, applygravity, stepnormal, 0); +++ self.move_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; +++ } +++ +++ _Movetype_CheckVelocity(); +++ _Movetype_LinkEdict(true); +++ +++ // check for stuckness, possibly due to the limited precision of floats +++ // in the clipping hulls +++ if (clip +++ && fabs(originalmove_origin.y - self.move_origin.y) < 0.03125 +++ && fabs(originalmove_origin.x - self.move_origin.x) < 0.03125) +++ { +++ // Con_Printf("wall\n"); +++ // stepping up didn't make any progress, revert to original move +++ self.move_origin = originalmove_origin; +++ self.move_velocity = originalmove_velocity; +++ // clip = originalmove_clip; +++ self.move_flags = originalmove_flags; +++ self.move_groundentity = originalmove_groundentity; +++ // now try to unstick if needed +++ // clip = SV_TryUnstick (ent, oldvel); +++ return; +++ } +++ +++ // Con_Printf("step - "); +++ +++ // extra friction based on view angle +++ if (clip & 2 && PHYS_WALLFRICTION) +++ _Movetype_WallFriction(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 (!GAMEPLAYFIX_STEPDOWN || self.move_waterlevel >= 3 || start_velocity.z >= (1.0 / 32.0) || !oldonground || (self.move_flags & FL_ONGROUND)) +++ { +++ return; +++ } +++ +++ // move down +++ vector downmove = '0 0 1' * (-PHYS_STEPHEIGHT + start_velocity.z * dt); +++ if (!_Movetype_PushEntity(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 +++ self.move_origin = originalmove_origin; +++ self.move_velocity = originalmove_velocity; +++ self.move_flags = originalmove_flags; +++ self.move_groundentity = originalmove_groundentity; +++ } +++ +++ _Movetype_CheckVelocity(); +++ _Movetype_LinkEdict(true); +++} ++diff --git a/qcsrc/common/movetypes/walk.qh b/qcsrc/common/movetypes/walk.qh ++new file mode 100644 ++index 0000000..10493c9 ++--- /dev/null +++++ b/qcsrc/common/movetypes/walk.qh ++@@ -0,0 +1,6 @@ +++#ifndef MOVETYPE_WALK_H +++#define MOVETYPE_WALK_H +++ +++void _Movetype_Physics_Walk(float dt); +++ +++#endif ++diff --git a/qcsrc/common/nades.qc b/qcsrc/common/nades.qc ++index 4225a19..fcdc553 100644 ++--- a/qcsrc/common/nades.qc +++++ b/qcsrc/common/nades.qc ++@@ -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" ++diff --git a/qcsrc/common/net_notice.qc b/qcsrc/common/net_notice.qc ++index bc520e5..fb9958b 100644 ++--- a/qcsrc/common/net_notice.qc +++++ b/qcsrc/common/net_notice.qc ++@@ -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 ++index 0000000..d2287ea ++--- /dev/null +++++ b/qcsrc/common/physics.qc ++@@ -0,0 +1,1762 @@ +++#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 +++ // 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)) +++ { +++#ifdef SVQC +++ if(autocvar_speedmeter) +++ dprint(strcat("landing velocity: ", vtos(self.velocity), " (abs: ", ftos(vlen(self.velocity)), ")\n")); +++#endif +++ if(self.lastground < time - 0.3) +++ { +++ self.velocity_x *= (1 - PHYS_FRICTION_ONLAND); +++ self.velocity_y *= (1 - PHYS_FRICTION_ONLAND); +++ } +++#ifdef SVQC +++ if(self.jumppadcount > 1) +++ dprint(strcat(ftos(self.jumppadcount), "x jumppad combo\n")); +++ self.jumppadcount = 0; +++#endif +++ } +++ +++ 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; +++ self.flags |= FL_WATERJUMP; +++ SET_JUMP_HELD(self); +++ self.teleport_time = time + 2; // safety net +++ } +++ } +++} +++ +++ +++#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) +++ ITEMS(self) &= ~IT_USING_JETPACK; +++ +++ if(PHYS_INPUT_BUTTON_JUMP(self) || PHYS_INPUT_BUTTON_JETPACK(self)) +++ { +++ 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 = !PHYS_JETPACK_FUEL || PHYS_AMMO_FUEL(self) || ITEMS(self) & IT_UNLIMITED_WEAPON_AMMO; +++ +++ 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; +++ ITEMS(self) &= ~IT_USING_JETPACK; +++ } +++ else if (activate && !PHYS_FROZEN(self)) +++ ITEMS(self) |= IT_USING_JETPACK; +++ } +++ else +++ { +++ self.jetpack_stopped = false; +++ ITEMS(self) &= ~IT_USING_JETPACK; +++ } +++ 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); +++#ifdef SVQC +++ if (time >= self.teleport_time) +++#endif +++ PM_Accelerate(wishdir, wishspeed, wishspeed, PHYS_ACCELERATE * maxspd_mod, 1, 0, 0, 0); +++ PM_ClientMovement_Move(); +++} +++ +++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 +++ } +++ } +++ // water acceleration +++ PM_Accelerate(wishdir, wishspeed, wishspeed, PHYS_ACCELERATE * maxspd_mod, 1, 0, 0, 0); +++ PM_ClientMovement_Move(); +++} +++ +++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); +++#ifdef SVQC +++ if (time >= self.teleport_time) +++#endif +++ // water acceleration +++ PM_Accelerate(wishdir, wishspeed, wishspeed, PHYS_ACCELERATE*maxspd_mod, 1, 0, 0, 0); +++ PM_ClientMovement_Move(); +++} +++ +++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 +++} +++ +++void PM_walk(float buttons_prev, float maxspd_mod) +++{ +++ if (!WAS_ONGROUND(self)) +++ { +++#ifdef SVQC +++ if (autocvar_speedmeter) +++ dprint(strcat("landing velocity: ", vtos(self.velocity), " (abs: ", ftos(vlen(self.velocity)), ")\n")); +++#endif +++ if (self.lastground < time - 0.3) +++ self.velocity *= (1 - PHYS_FRICTION_ONLAND); +++#ifdef SVQC +++ if (self.jumppadcount > 1) +++ dprint(strcat(ftos(self.jumppadcount), "x jumppad combo\n")); +++ self.jumppadcount = 0; +++#endif +++ } +++ +++ // 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.items = getstati(STAT_ITEMS, 0, 24); +++ +++ 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) +++ { +++ 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; +++ } +++ } +++ +++#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 || (BUFFS(self) & BUFF_FLIGHT)) +++ 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 = self.flags; +++ +++ 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.flags & FL_DUCKED) ? PMF_DUCKED : 0) | +++ (!(self.flags & FL_JUMPRELEASED) ? 0 : PMF_JUMP_HELD) | +++ ((self.flags & FL_ONGROUND) ? PMF_ONGROUND : 0); +++#endif +++} ++diff --git a/qcsrc/common/physics.qh b/qcsrc/common/physics.qh ++new file mode 100644 ++index 0000000..01b0874 ++--- /dev/null +++++ b/qcsrc/common/physics.qh ++@@ -0,0 +1,338 @@ +++#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 +++ +++ const int FL_WATERJUMP = 2048; // player jumping out of water +++ const int FL_JUMPRELEASED = 4096; // for jump debouncing +++ +++ float PM_multijump_checkjump(); +++ void PM_multijump(); +++ +++ .float watertype; +++ .int items; +++ +++// 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 GAMEPLAYFIX_DOWNTRACEONGROUND getstati(STAT_GAMEPLAYFIX_DOWNTRACEONGROUND) +++ #define GAMEPLAYFIX_STEPMULTIPLETIMES getstati(STAT_GAMEPLAYFIX_STEPMULTIPLETIMES) +++ #define GAMEPLAYFIX_UNSTICKPLAYERS getstati(STAT_GAMEPLAYFIX_UNSTICKPLAYERS) +++ #define GAMEPLAYFIX_STEPDOWN getstati(STAT_GAMEPLAYFIX_STEPDOWN) +++ +++ #define IS_DUCKED(s) !!(s.flags & FL_DUCKED) +++ #define SET_DUCKED(s) s.flags |= FL_DUCKED +++ #define UNSET_DUCKED(s) s.flags &= ~FL_DUCKED +++ +++ #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) !!(s.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 BUFFS(s) getstati(STAT_BUFFS) +++ +++ #define PHYS_AMMO_FUEL(s) getstati(STAT_FUEL) +++ +++ #define PHYS_FROZEN(s) getstati(STAT_FROZEN) +++ +++ #define PHYS_DOUBLEJUMP getstati(STAT_DOUBLEJUMP) +++ +++ #define PHYS_BUGRIGS getstati(STAT_BUGRIGS) +++ #define PHYS_BUGRIGS_ANGLE_SMOOTHING getstati(STAT_BUGRIGS_ANGLE_SMOOTHING) +++ #define PHYS_BUGRIGS_PLANAR_MOVEMENT getstati(STAT_BUGRIGS_PLANAR_MOVEMENT) +++ #define PHYS_BUGRIGS_REVERSE_SPEEDING getstati(STAT_BUGRIGS_REVERSE_SPEEDING) +++ #define PHYS_BUGRIGS_FRICTION_FLOOR getstatf(STAT_BUGRIGS_FRICTION_FLOOR) +++ #define PHYS_BUGRIGS_AIR_STEERING getstati(STAT_BUGRIGS_AIR_STEERING) +++ #define PHYS_BUGRIGS_FRICTION_BRAKE getstatf(STAT_BUGRIGS_FRICTION_BRAKE) +++ #define PHYS_BUGRIGS_ACCEL getstatf(STAT_BUGRIGS_ACCEL) +++ #define PHYS_BUGRIGS_SPEED_REF getstatf(STAT_BUGRIGS_SPEED_REF) +++ #define PHYS_BUGRIGS_SPEED_POW getstatf(STAT_BUGRIGS_SPEED_POW) +++ #define PHYS_BUGRIGS_STEER getstatf(STAT_BUGRIGS_STEER) +++ #define PHYS_BUGRIGS_FRICTION_AIR getstatf(STAT_BUGRIGS_FRICTION_AIR) +++ #define PHYS_BUGRIGS_CAR_JUMPING getstatf(STAT_BUGRIGS_CAR_JUMPING) +++ #define PHYS_BUGRIGS_REVERSE_SPINNING getstatf(STAT_BUGRIGS_REVERSE_SPINNING) +++ #define PHYS_BUGRIGS_REVERSE_STOPPING getstatf(STAT_BUGRIGS_REVERSE_STOPPING) +++ +++ #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_WALLFRICTION getstati(STAT_MOVEVARS_WALLFRICTION) +++ +++ #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) +++ +++ #define PHYS_NOSTEP getstati(STAT_NOSTEP) +++ #define PHYS_JUMPSTEP getstati(STAT_MOVEVARS_JUMPSTEP) +++ +++#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; +++ .float stat_gameplayfix_downtracesupportsongroundflag; +++ .float stat_gameplayfix_stepmultipletimes; +++ .float stat_gameplayfix_unstickplayers; +++ .float stat_gameplayfix_stepdown; +++ +++ .float stat_bugrigs; +++ .float stat_bugrigs_angle_smoothing; +++ .float stat_bugrigs_planar_movement; +++ .float stat_bugrigs_reverse_speeding; +++ .float stat_bugrigs_friction_floor; +++ .float stat_bugrigs_air_steering; +++ .float stat_bugrigs_friction_brake; +++ .float stat_bugrigs_accel; +++ .float stat_bugrigs_speed_ref; +++ .float stat_bugrigs_speed_pow; +++ .float stat_bugrigs_steer; +++ .float stat_bugrigs_friction_air; +++ .float stat_bugrigs_car_jumping; +++ .float stat_bugrigs_reverse_spinning; +++ .float stat_bugrigs_reverse_stopping; +++ +++ .float stat_nostep; +++ .float stat_jumpstep; +++ +++ #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 GAMEPLAYFIX_DOWNTRACEONGROUND cvar("sv_gameplayfix_downtracesupportsongroundflag") +++ #define GAMEPLAYFIX_STEPMULTIPLETIMES cvar("sv_gameplayfix_stepmultipletimes") +++ #define GAMEPLAYFIX_UNSTICKPLAYERS cvar("sv_gameplayfix_unstickplayers") +++ #define GAMEPLAYFIX_STEPDOWN cvar("sv_gameplayfix_stepdown") +++ +++ #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 BUFFS(s) (s).buffs +++ +++ #define PHYS_AMMO_FUEL(s) s.ammo_fuel +++ +++ #define PHYS_FROZEN(s) s.frozen +++ +++ #define PHYS_DOUBLEJUMP autocvar_sv_doublejump +++ +++ #define PHYS_BUGRIGS g_bugrigs +++ #define PHYS_BUGRIGS_ANGLE_SMOOTHING g_bugrigs_angle_smoothing +++ #define PHYS_BUGRIGS_PLANAR_MOVEMENT g_bugrigs_planar_movement +++ #define PHYS_BUGRIGS_REVERSE_SPEEDING g_bugrigs_reverse_speeding +++ #define PHYS_BUGRIGS_FRICTION_FLOOR g_bugrigs_friction_floor +++ #define PHYS_BUGRIGS_AIR_STEERING g_bugrigs_air_steering +++ #define PHYS_BUGRIGS_FRICTION_BRAKE g_bugrigs_friction_brake +++ #define PHYS_BUGRIGS_ACCEL g_bugrigs_accel +++ #define PHYS_BUGRIGS_SPEED_REF g_bugrigs_speed_ref +++ #define PHYS_BUGRIGS_SPEED_POW g_bugrigs_speed_pow +++ #define PHYS_BUGRIGS_STEER g_bugrigs_steer +++ #define PHYS_BUGRIGS_FRICTION_AIR g_bugrigs_friction_air +++ #define PHYS_BUGRIGS_CAR_JUMPING g_bugrigs_planar_movement_car_jumping +++ #define PHYS_BUGRIGS_REVERSE_SPINNING g_bugrigs_reverse_spinning +++ #define PHYS_BUGRIGS_REVERSE_STOPPING g_bugrigs_reverse_stopping +++ +++ #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_WALLFRICTION cvar("sv_wallfriction") +++ +++ #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 +++ +++ #define PHYS_NOSTEP cvar("sv_nostep") +++ #define PHYS_JUMPSTEP cvar("sv_jumpstep") +++ +++#endif +++#endif ++diff --git a/qcsrc/common/stats.qh b/qcsrc/common/stats.qh ++index 481713c..c3d67ca 100644 ++--- a/qcsrc/common/stats.qh +++++ b/qcsrc/common/stats.qh ++@@ -204,57 +204,57 @@ const int STAT_REVIVE_PROGRESS = 106; ++ // 166 empty? ++ // 167 empty? ++ // 168 empty? ++-// 169 empty? ++-// 170 empty? ++-// 171 empty? ++-// 172 empty? ++-// 173 empty? ++-// 174 empty? ++-// 175 empty? ++-// 176 empty? ++-// 177 empty? ++-// 178 empty? ++-// 179 empty? ++-// 180 empty? ++-// 181 empty? ++-// 182 empty? ++-// 183 empty? ++-// 184 empty? ++-// 185 empty? ++-// 186 empty? ++-// 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_BUGRIGS_REVERSE_STOPPING = 169; +++const int STAT_BUGRIGS_REVERSE_SPINNING = 170; +++const int STAT_BUGRIGS_CAR_JUMPING = 171; +++const int STAT_BUGRIGS_FRICTION_AIR = 172; +++const int STAT_BUGRIGS_STEER = 173; +++const int STAT_BUGRIGS_SPEED_POW = 174; +++const int STAT_BUGRIGS_SPEED_REF = 175; +++const int STAT_BUGRIGS_ACCEL = 176; +++const int STAT_BUGRIGS_FRICTION_BRAKE = 177; +++const int STAT_BUGRIGS_AIR_STEERING = 178; +++const int STAT_BUGRIGS_FRICTION_FLOOR = 179; +++const int STAT_BUGRIGS_REVERSE_SPEEDING = 180; +++const int STAT_BUGRIGS_PLANAR_MOVEMENT = 181; +++const int STAT_BUGRIGS_ANGLE_SMOOTHING = 182; +++const int STAT_BUGRIGS = 183; +++const int STAT_GAMEPLAYFIX_STEPDOWN = 184; +++const int STAT_MOVEVARS_JUMPSTEP = 185; +++const int STAT_NOSTEP = 186; +++const int STAT_GAMEPLAYFIX_UNSTICKPLAYERS = 187; +++const int STAT_GAMEPLAYFIX_STEPMULTIPLETIMES = 188; +++const int STAT_GAMEPLAYFIX_DOWNTRACEONGROUND = 189; +++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 ++index 0000000..9fb2f56 ++--- /dev/null +++++ b/qcsrc/common/triggers/func/bobbing.qc ++@@ -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 ++index 0000000..9c371b5 ++--- /dev/null +++++ b/qcsrc/common/triggers/func/breakable.qc ++@@ -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 ++index 0000000..b641d94 ++--- /dev/null +++++ b/qcsrc/common/triggers/func/breakable.qh ++@@ -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 ++index 0000000..0ed374a ++--- /dev/null +++++ b/qcsrc/common/triggers/func/button.qc ++@@ -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 ++index 0000000..6ed01b0 ++--- /dev/null +++++ b/qcsrc/common/triggers/func/conveyor.qc ++@@ -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 ++index 0000000..f272030 ++--- /dev/null +++++ b/qcsrc/common/triggers/func/conveyor.qh ++@@ -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 ++index 0000000..1a1c82b ++--- /dev/null +++++ b/qcsrc/common/triggers/func/door.qc ++@@ -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 ++index 0000000..b58091b ++--- /dev/null +++++ b/qcsrc/common/triggers/func/door.qh ++@@ -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 ++index 0000000..bdf05a0 ++--- /dev/null +++++ b/qcsrc/common/triggers/func/door_rotating.qc ++@@ -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 ++index 0000000..b188f83 ++--- /dev/null +++++ b/qcsrc/common/triggers/func/door_secret.qc ++@@ -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 ++index 0000000..19dceef ++--- /dev/null +++++ b/qcsrc/common/triggers/func/fourier.qc ++@@ -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 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 ++index 0000000..290c2e9 ++--- /dev/null +++++ b/qcsrc/common/triggers/func/include.qc ++@@ -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 ++index 0000000..624c0b5 ++--- /dev/null +++++ b/qcsrc/common/triggers/func/include.qh ++@@ -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 ++index 0000000..8b093f7 ++--- /dev/null +++++ b/qcsrc/common/triggers/func/ladder.qc ++@@ -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 ++index 0000000..11eeeda ++--- /dev/null +++++ b/qcsrc/common/triggers/func/ladder.qh ++@@ -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 ++index 0000000..7d3eff7 ++--- /dev/null +++++ b/qcsrc/common/triggers/func/pendulum.qc ++@@ -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 ++index 0000000..198083f ++--- /dev/null +++++ b/qcsrc/common/triggers/func/plat.qc ++@@ -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 ++index 0000000..e3562f4 ++--- /dev/null +++++ b/qcsrc/common/triggers/func/plat.qh ++@@ -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 ++index 0000000..773ce6a ++--- /dev/null +++++ b/qcsrc/common/triggers/func/pointparticles.qc ++@@ -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 ++index 0000000..d446e72 ++--- /dev/null +++++ b/qcsrc/common/triggers/func/pointparticles.qh ++@@ -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 ++index 0000000..deb5ee5 ++--- /dev/null +++++ b/qcsrc/common/triggers/func/rainsnow.qc ++@@ -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 ++index 0000000..5d8d923 ++--- /dev/null +++++ b/qcsrc/common/triggers/func/rainsnow.qh ++@@ -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 ++index 0000000..93d4a30 ++--- /dev/null +++++ b/qcsrc/common/triggers/func/rotating.qc ++@@ -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 ++index 0000000..30c406f ++--- /dev/null +++++ b/qcsrc/common/triggers/func/stardust.qc ++@@ -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 ++index 0000000..2a5d2fa ++--- /dev/null +++++ b/qcsrc/common/triggers/func/train.qc ++@@ -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 ++index 0000000..f69515f ++--- /dev/null +++++ b/qcsrc/common/triggers/func/train.qh ++@@ -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 ++index 0000000..521ac87 ++--- /dev/null +++++ b/qcsrc/common/triggers/func/vectormamamam.qc ++@@ -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 ++index 0000000..59da1f2 ++--- /dev/null +++++ b/qcsrc/common/triggers/include.qc ++@@ -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 ++index 0000000..138d267 ++--- /dev/null +++++ b/qcsrc/common/triggers/include.qh ++@@ -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 ++index 0000000..35ad2ce ++--- /dev/null +++++ b/qcsrc/common/triggers/misc/corner.qc ++@@ -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 ++index 0000000..62b3aae ++--- /dev/null +++++ b/qcsrc/common/triggers/misc/corner.qh ++@@ -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 ++index 0000000..bfeb865 ++--- /dev/null +++++ b/qcsrc/common/triggers/misc/follow.qc ++@@ -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 ++index 0000000..c05f8c3 ++--- /dev/null +++++ b/qcsrc/common/triggers/misc/include.qc ++@@ -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 ++index 0000000..71b2205 ++--- /dev/null +++++ b/qcsrc/common/triggers/misc/include.qh ++@@ -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 ++index 0000000..c67eb32 ++--- /dev/null +++++ b/qcsrc/common/triggers/misc/laser.qc ++@@ -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 ++index 0000000..a77c4c5 ++--- /dev/null +++++ b/qcsrc/common/triggers/misc/laser.qh ++@@ -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 ++index 0000000..b80ce82 ++--- /dev/null +++++ b/qcsrc/common/triggers/misc/teleport_dest.qc ++@@ -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 ++index 0000000..b837d7c ++--- /dev/null +++++ b/qcsrc/common/triggers/platforms.qc ++@@ -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 ++index 0000000..e0a581b ++--- /dev/null +++++ b/qcsrc/common/triggers/platforms.qh ++@@ -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 ++index 0000000..f21455d ++--- /dev/null +++++ b/qcsrc/common/triggers/subs.qc ++@@ -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 ++index 0000000..ea8ef30 ++--- /dev/null +++++ b/qcsrc/common/triggers/subs.qh ++@@ -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 ++index 0000000..1ec8cc9 ++--- /dev/null +++++ b/qcsrc/common/triggers/target/changelevel.qc ++@@ -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 ++index 0000000..c53ea6d ++--- /dev/null +++++ b/qcsrc/common/triggers/target/include.qc ++@@ -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 ++index 0000000..4e44b97 ++--- /dev/null +++++ b/qcsrc/common/triggers/target/include.qh ++@@ -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 ++index 0000000..1430cab ++--- /dev/null +++++ b/qcsrc/common/triggers/target/location.qc ++@@ -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 ++index 0000000..136288b ++--- /dev/null +++++ b/qcsrc/common/triggers/target/music.qc ++@@ -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 ++index 0000000..8d014c8 ++--- /dev/null +++++ b/qcsrc/common/triggers/target/music.qh ++@@ -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 ++index 0000000..7b18355 ++--- /dev/null +++++ b/qcsrc/common/triggers/target/spawn.qc ++@@ -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 ++index 0000000..7be8b91 ++--- /dev/null +++++ b/qcsrc/common/triggers/target/speaker.qc ++@@ -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 ++index 0000000..c173d80 ++--- /dev/null +++++ b/qcsrc/common/triggers/target/voicescript.qc ++@@ -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 ++index 0000000..5e91d76 ++--- /dev/null +++++ b/qcsrc/common/triggers/teleporters.qc ++@@ -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 ++index 0000000..bdd24dd ++--- /dev/null +++++ b/qcsrc/common/triggers/teleporters.qh ++@@ -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 ++index 0000000..bf1d9b2 ++--- /dev/null +++++ b/qcsrc/common/triggers/trigger/counter.qc ++@@ -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 ++index 0000000..b01efe3 ++--- /dev/null +++++ b/qcsrc/common/triggers/trigger/delay.qc ++@@ -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 ++index 0000000..cd5fdff ++--- /dev/null +++++ b/qcsrc/common/triggers/trigger/disablerelay.qc ++@@ -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 ++index 0000000..12d8a59 ++--- /dev/null +++++ b/qcsrc/common/triggers/trigger/flipflop.qc ++@@ -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 ++index 0000000..3ad419d ++--- /dev/null +++++ b/qcsrc/common/triggers/trigger/gamestart.qc ++@@ -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 ++index 0000000..a709a1f ++--- /dev/null +++++ b/qcsrc/common/triggers/trigger/gravity.qc ++@@ -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 ++index 0000000..6d68610 ++--- /dev/null +++++ b/qcsrc/common/triggers/trigger/heal.qc ++@@ -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 ++index 0000000..d39c32c ++--- /dev/null +++++ b/qcsrc/common/triggers/trigger/hurt.qc ++@@ -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 ++index 0000000..89ca2e0 ++--- /dev/null +++++ b/qcsrc/common/triggers/trigger/impulse.qc ++@@ -0,0 +1,194 @@ +++// 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; +++#ifdef SVQC +++ UpdateCSQCProjectile(other); +++#endif +++} +++ +++// 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); +++#ifdef SVQC +++ UpdateCSQCProjectile(other); +++#endif +++} +++ +++// 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; +++#ifdef SVQC +++ UpdateCSQCProjectile(other); +++#endif +++} +++ +++/*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). +++*/ +++#ifdef SVQC +++bool trigger_impulse_send(entity to, int sf) +++{ +++ WriteByte(MSG_ENTITY, ENT_CLIENT_TRIGGER_IMPULSE); +++ +++ WriteCoord(MSG_ENTITY, self.radius); +++ WriteCoord(MSG_ENTITY, self.strength); +++ WriteByte(MSG_ENTITY, self.falloff); +++ WriteByte(MSG_ENTITY, self.active); +++ +++ trigger_common_write(true); +++ +++ return true; +++} +++ +++void trigger_impulse_link() +++{ +++ Net_LinkEntity(self, 0, false, trigger_impulse_send); +++} +++ +++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; +++ } +++ } +++ +++ trigger_impulse_link(); +++} +++#elif defined(CSQC) +++void ent_trigger_impulse() +++{ +++ self.radius = ReadCoord(); +++ self.strength = ReadCoord(); +++ self.falloff = ReadByte(); +++ self.active = ReadByte(); +++ +++ trigger_common_read(true); +++ +++ +++ self.classname = "trigger_impulse"; +++ self.solid = SOLID_TRIGGER; +++ self.entremove = trigger_remove_generic; +++ self.draw = trigger_draw_generic; +++ self.drawmask = MASK_NORMAL; +++ self.move_time = time; +++ +++ if(self.radius) { self.trigger_touch = trigger_impulse_touch3; } +++ else if(self.target) { self.trigger_touch = trigger_impulse_touch1; } +++ else { self.trigger_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 ++index 0000000..a4c248d ++--- /dev/null +++++ b/qcsrc/common/triggers/trigger/impulse.qh ++@@ -0,0 +1,14 @@ +++#ifndef TRIGGER_IMPULSE_H +++#define TRIGGER_IMPULSE_H +++ +++// tZorks trigger impulse / gravity +++.float radius; +++.float falloff; +++.float strength; +++.float lastpushtime; +++ +++#ifdef CSQC +++void ent_trigger_impulse(); +++#endif +++ +++#endif ++diff --git a/qcsrc/common/triggers/trigger/include.qc b/qcsrc/common/triggers/trigger/include.qc ++new file mode 100644 ++index 0000000..1498634 ++--- /dev/null +++++ b/qcsrc/common/triggers/trigger/include.qc ++@@ -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 ++index 0000000..1601143 ++--- /dev/null +++++ b/qcsrc/common/triggers/trigger/include.qh ++@@ -0,0 +1,11 @@ +++#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" +++#include "impulse.qh" +++ +++#endif ++diff --git a/qcsrc/common/triggers/trigger/jumppads.qc b/qcsrc/common/triggers/trigger/jumppads.qc ++new file mode 100644 ++index 0000000..61cd92b ++--- /dev/null +++++ b/qcsrc/common/triggers/trigger/jumppads.qc ++@@ -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 ++index 0000000..bb0f475 ++--- /dev/null +++++ b/qcsrc/common/triggers/trigger/jumppads.qh ++@@ -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 ++index 0000000..ec27d4b ++--- /dev/null +++++ b/qcsrc/common/triggers/trigger/keylock.qc ++@@ -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 ++index 0000000..b21145d ++--- /dev/null +++++ b/qcsrc/common/triggers/trigger/keylock.qh ++@@ -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 ++index 0000000..f14b75c ++--- /dev/null +++++ b/qcsrc/common/triggers/trigger/magicear.qc ++@@ -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 ++index 0000000..45ce761 ++--- /dev/null +++++ b/qcsrc/common/triggers/trigger/monoflop.qc ++@@ -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 ++index 0000000..ae73aaf ++--- /dev/null +++++ b/qcsrc/common/triggers/trigger/multi.qc ++@@ -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 ++index 0000000..df14a51 ++--- /dev/null +++++ b/qcsrc/common/triggers/trigger/multi.qh ++@@ -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 ++index 0000000..02a258e ++--- /dev/null +++++ b/qcsrc/common/triggers/trigger/multivibrator.qc ++@@ -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 ++index 0000000..e037028 ++--- /dev/null +++++ b/qcsrc/common/triggers/trigger/relay.qc ++@@ -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 ++index 0000000..83c0103 ++--- /dev/null +++++ b/qcsrc/common/triggers/trigger/relay_activators.qc ++@@ -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 ++index 0000000..ade56c1 ++--- /dev/null +++++ b/qcsrc/common/triggers/trigger/relay_if.qc ++@@ -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 ++index 0000000..8a77cef ++--- /dev/null +++++ b/qcsrc/common/triggers/trigger/relay_teamcheck.qc ++@@ -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 ++index 0000000..1902b7e ++--- /dev/null +++++ b/qcsrc/common/triggers/trigger/secret.qc ++@@ -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 ++index 0000000..c09da6b ++--- /dev/null +++++ b/qcsrc/common/triggers/trigger/secret.qh ++@@ -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 ++index 0000000..8013f3b ++--- /dev/null +++++ b/qcsrc/common/triggers/trigger/swamp.qc ++@@ -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 ++index 0000000..86b1431 ++--- /dev/null +++++ b/qcsrc/common/triggers/trigger/swamp.qh ++@@ -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 ++index 0000000..5ff5fcf ++--- /dev/null +++++ b/qcsrc/common/triggers/trigger/teleport.qc ++@@ -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 ++index 0000000..fdffbb9 ++--- /dev/null +++++ b/qcsrc/common/triggers/triggers.qc ++@@ -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 ++index 0000000..c50d95b ++--- /dev/null +++++ b/qcsrc/common/triggers/triggers.qh ++@@ -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 ++diff --git a/qcsrc/common/util.qc b/qcsrc/common/util.qc ++index 99fecda..53a7e3a 100644 ++--- a/qcsrc/common/util.qc +++++ b/qcsrc/common/util.qc ++@@ -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; ++ } ++ ++diff --git a/qcsrc/common/weapons/w_porto.qc b/qcsrc/common/weapons/w_porto.qc ++index 0010449..569a15d 100644 ++--- a/qcsrc/common/weapons/w_porto.qc +++++ b/qcsrc/common/weapons/w_porto.qc ++@@ -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) ++diff --git a/qcsrc/common/weapons/weapons.qc b/qcsrc/common/weapons/weapons.qc ++index 55fcc0e..f85ff90 100644 ++--- a/qcsrc/common/weapons/weapons.qc +++++ b/qcsrc/common/weapons/weapons.qc ++@@ -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) ++diff --git a/qcsrc/csqcmodellib/cl_player.qc b/qcsrc/csqcmodellib/cl_player.qc ++index 1f3017a..1f5f225 100644 ++--- a/qcsrc/csqcmodellib/cl_player.qc +++++ b/qcsrc/csqcmodellib/cl_player.qc ++@@ -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 ++@@ -104,12 +105,12 @@ void CSQCPlayer_Unpredict() ++ self.origin = csqcplayer_origin; ++ self.velocity = csqcplayer_velocity; ++ csqcplayer_moveframe = csqcplayer_sequence+1; //+1 because the recieved frame has the move already done (server side) ++- self.pmove_flags = player_pmflags; +++ self.flags = player_pmflags; ++ } ++ ++ void CSQCPlayer_SetMinsMaxs() ++ { ++- if(self.pmove_flags & PMF_DUCKED) +++ if(self.flags & FL_DUCKED) ++ { ++ self.mins = PL_CROUCH_MIN; ++ self.maxs = PL_CROUCH_MAX; ++@@ -125,13 +126,24 @@ void CSQCPlayer_SetMinsMaxs() ++ ++ void CSQCPlayer_SavePrediction() ++ { ++- player_pmflags = self.pmove_flags; +++ player_pmflags = self.flags; ++ csqcplayer_origin = self.origin; ++ csqcplayer_velocity = self.velocity; ++ csqcplayer_sequence = servercommandframe; ++ 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++; ++ } ++@@ -200,15 +212,15 @@ void CSQCPlayer_SetCamera() ++ ++ // get crouch state from the server ++ if(getstati(STAT_VIEWHEIGHT) == PL_VIEW_OFS_z) ++- self.pmove_flags &= ~PMF_DUCKED; +++ self.flags &= ~FL_DUCKED; ++ else if(getstati(STAT_VIEWHEIGHT) == PL_CROUCH_VIEW_OFS_z) ++- self.pmove_flags |= PMF_DUCKED; +++ self.flags |= FL_DUCKED; ++ ++ // get onground state from the server ++ if(pmove_onground) ++- self.pmove_flags |= PMF_ONGROUND; +++ self.flags |= FL_ONGROUND; ++ else ++- self.pmove_flags &= ~PMF_ONGROUND; +++ self.flags &= ~FL_ONGROUND; ++ ++ CSQCPlayer_SetMinsMaxs(); ++ ++@@ -232,21 +244,21 @@ void CSQCPlayer_SetCamera() ++ v = v0; ++ csqcplayer_status = CSQCPLAYERSTATUS_PREDICTED; ++ CSQCPlayer_PredictTo(servercommandframe + 1, false); ++- CSQCPlayer_SetPredictionError(self.origin - o, self.velocity - v, pmove_onground - !!(self.pmove_flags & PMF_ONGROUND)); +++ CSQCPlayer_SetPredictionError(self.origin - o, self.velocity - v, pmove_onground - !!(self.flags & FL_ONGROUND)); ++ self.origin = o; ++ self.velocity = v; ++ ++ // get crouch state from the server ++ if(getstati(STAT_VIEWHEIGHT) == PL_VIEW_OFS_z) ++- self.pmove_flags &= ~PMF_DUCKED; +++ self.flags &= ~FL_DUCKED; ++ else if(getstati(STAT_VIEWHEIGHT) == PL_CROUCH_VIEW_OFS_z) ++- self.pmove_flags |= PMF_DUCKED; +++ self.flags |= FL_DUCKED; ++ ++ // get onground state from the server ++ if(pmove_onground) ++- self.pmove_flags |= PMF_ONGROUND; +++ self.flags |= FL_ONGROUND; ++ else ++- self.pmove_flags &= ~PMF_ONGROUND; +++ self.flags &= ~FL_ONGROUND; ++ ++ CSQCPlayer_SavePrediction(); ++ } ++@@ -255,9 +267,9 @@ void CSQCPlayer_SetCamera() ++ #ifdef CSQCMODEL_SERVERSIDE_CROUCH ++ // get crouch state from the server (LAG) ++ if(getstati(STAT_VIEWHEIGHT) == PL_VIEW_OFS_z) ++- self.pmove_flags &= ~PMF_DUCKED; +++ self.flags &= ~FL_DUCKED; ++ else if(getstati(STAT_VIEWHEIGHT) == PL_CROUCH_VIEW_OFS_z) ++- self.pmove_flags |= PMF_DUCKED; +++ self.flags |= FL_DUCKED; ++ #endif ++ ++ CSQCPlayer_SetMinsMaxs(); ++diff --git a/qcsrc/csqcmodellib/cl_player.qh b/qcsrc/csqcmodellib/cl_player.qh ++index 55e63cf..e427e6c 100644 ++--- a/qcsrc/csqcmodellib/cl_player.qh +++++ b/qcsrc/csqcmodellib/cl_player.qh ++@@ -34,6 +34,8 @@ const int PMF_JUMP_HELD = 1; ++ //const int PMF_DUCKED = 4; ++ //const int PMF_ONGROUND = 8; ++ +++const int FL_DUCKED = 524288; +++ ++ void CSQCPlayer_SetCamera(); ++ float CSQCPlayer_PreUpdate(); ++ float CSQCPlayer_PostUpdate(); ++diff --git a/qcsrc/dpdefs/csprogsdefs.qh b/qcsrc/dpdefs/csprogsdefs.qh ++index 4effe64..cd4f8d7 100644 ++--- a/qcsrc/dpdefs/csprogsdefs.qh +++++ b/qcsrc/dpdefs/csprogsdefs.qh ++@@ -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 ++diff --git a/qcsrc/server/autocvars.qh b/qcsrc/server/autocvars.qh ++index bcb9d28..e595e47 100644 ++--- a/qcsrc/server/autocvars.qh +++++ b/qcsrc/server/autocvars.qh ++@@ -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; ++diff --git a/qcsrc/server/bot/havocbot/havocbot.qc b/qcsrc/server/bot/havocbot/havocbot.qc ++index 0ecafaf..7ba2e16 100644 ++--- a/qcsrc/server/bot/havocbot/havocbot.qc +++++ b/qcsrc/server/bot/havocbot/havocbot.qc ++@@ -2,6 +2,7 @@ ++ #include "role_onslaught.qc" ++ #include "role_keyhunt.qc" ++ #include "roles.qc" +++#include "../../../common/triggers/trigger/jumppads.qh" ++ ++ void havocbot_ai() ++ { ++diff --git a/qcsrc/server/campaign.qc b/qcsrc/server/campaign.qc ++index 2c091d5..6508559 100644 ++--- a/qcsrc/server/campaign.qc +++++ b/qcsrc/server/campaign.qc ++@@ -121,7 +121,6 @@ void CampaignPreInit() ++ strunzone(title); ++ } ++ ++-string GetMapname(); ++ void CampaignPostInit() ++ { ++ // now some sanity checks ++diff --git a/qcsrc/server/cheats.qc b/qcsrc/server/cheats.qc ++index 657ec4d..b8eaf6e 100644 ++--- a/qcsrc/server/cheats.qc +++++ b/qcsrc/server/cheats.qc ++@@ -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) ++@@ -12,12 +12,15 @@ ++ #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 ++diff --git a/qcsrc/server/cl_client.qc b/qcsrc/server/cl_client.qc ++index 3bc5cc7..a49268d 100644 ++--- a/qcsrc/server/cl_client.qc +++++ b/qcsrc/server/cl_client.qc ++@@ -9,7 +9,6 @@ ++ #include "portals.qh" ++ #include "teamplay.qh" ++ #include "playerdemo.qh" ++-#include "secret.qh" ++ ++ #include "bot/bot.qh" ++ #include "bot/navigation.qh" ++@@ -18,6 +17,11 @@ ++ #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 ++index 3373423..0000000 ++--- a/qcsrc/server/cl_physics.qc +++++ /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; ++-} ++diff --git a/qcsrc/server/cl_player.qc b/qcsrc/server/cl_player.qc ++index 49d1c1b..49ae14a 100644 ++--- a/qcsrc/server/cl_player.qc +++++ b/qcsrc/server/cl_player.qc ++@@ -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) ++diff --git a/qcsrc/server/constants.qh b/qcsrc/server/constants.qh ++index 1d4cc55..c1def6b 100644 ++--- a/qcsrc/server/constants.qh +++++ b/qcsrc/server/constants.qh ++@@ -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 ++diff --git a/qcsrc/server/defs.qh b/qcsrc/server/defs.qh ++index 822c516..c430029 100644 ++--- a/qcsrc/server/defs.qh +++++ b/qcsrc/server/defs.qh ++@@ -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 ++index be6104f..0000000 ++--- a/qcsrc/server/func_breakable.qc +++++ /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(); ++-} ++diff --git a/qcsrc/server/g_damage.qc b/qcsrc/server/g_damage.qc ++index d170cf4..14f74d0 100644 ++--- a/qcsrc/server/g_damage.qc +++++ b/qcsrc/server/g_damage.qc ++@@ -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 ++diff --git a/qcsrc/server/g_hook.qc b/qcsrc/server/g_hook.qc ++index 310833e..05032c2 100644 ++--- a/qcsrc/server/g_hook.qc +++++ b/qcsrc/server/g_hook.qc ++@@ -15,6 +15,7 @@ ++ #include "command/common.qh" ++ #include "g_hook.qh" ++ #include "round_handler.qh" +++ #include "weapons/common.qh" ++ #endif ++ ++ /*============================================ ++diff --git a/qcsrc/server/g_models.qc b/qcsrc/server/g_models.qc ++index 6e1c06d..95b83f3 100644 ++--- a/qcsrc/server/g_models.qc +++++ b/qcsrc/server/g_models.qc ++@@ -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" ++diff --git a/qcsrc/server/g_subs.qc b/qcsrc/server/g_subs.qc ++index 74a8cf7..d42cc67 100644 ++--- a/qcsrc/server/g_subs.qc +++++ b/qcsrc/server/g_subs.qc ++@@ -1,7 +1,5 @@ ++ #include "g_subs.qh" ++ ++-void SUB_NullThink(void) { } ++- ++ void spawnfunc_info_null (void) ++ { ++ remove(self); ++@@ -54,380 +52,6 @@ void updateanim(entity e) ++ ++ /* ++ ================== ++-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 ++ ++ unused but required by the engine ++diff --git a/qcsrc/server/g_subs.qh b/qcsrc/server/g_subs.qh ++index 04477e6..d62bbb6 100644 ++--- a/qcsrc/server/g_subs.qh +++++ b/qcsrc/server/g_subs.qh ++@@ -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 ++index 9bb1e06..0000000 ++--- a/qcsrc/server/g_triggers.qc +++++ /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 ++index 35c39f2..0000000 ++--- a/qcsrc/server/g_triggers.qh +++++ /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 ++diff --git a/qcsrc/server/g_world.qc b/qcsrc/server/g_world.qc ++index 04f8ff7..0076440 100644 ++--- a/qcsrc/server/g_world.qc +++++ b/qcsrc/server/g_world.qc ++@@ -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); ++diff --git a/qcsrc/server/item_key.qc b/qcsrc/server/item_key.qc ++index f489309..a130b82 100644 ++--- a/qcsrc/server/item_key.qc +++++ b/qcsrc/server/item_key.qc ++@@ -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; ++-}; ++- ++- ++diff --git a/qcsrc/server/item_key.qh b/qcsrc/server/item_key.qh ++index 6ab5605..4a2acd8 100644 ++--- a/qcsrc/server/item_key.qh +++++ b/qcsrc/server/item_key.qh ++@@ -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 ++diff --git a/qcsrc/server/miscfunctions.qc b/qcsrc/server/miscfunctions.qc ++index d998ea7..2ce0540 100644 ++--- a/qcsrc/server/miscfunctions.qc +++++ b/qcsrc/server/miscfunctions.qc ++@@ -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; ++diff --git a/qcsrc/server/miscfunctions.qh b/qcsrc/server/miscfunctions.qh ++index f19f53f..fc2bf4b 100644 ++--- a/qcsrc/server/miscfunctions.qh +++++ b/qcsrc/server/miscfunctions.qh ++@@ -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"; ++diff --git a/qcsrc/server/mutators/gamemode_assault.qc b/qcsrc/server/mutators/gamemode_assault.qc ++index 49deac0..de09322 100644 ++--- a/qcsrc/server/mutators/gamemode_assault.qc +++++ b/qcsrc/server/mutators/gamemode_assault.qc ++@@ -1,3 +1,5 @@ +++#include "../../common/triggers/subs.qh" +++ ++ // random functions ++ void assault_objective_use() ++ { ++diff --git a/qcsrc/server/mutators/gamemode_assault.qh b/qcsrc/server/mutators/gamemode_assault.qh ++index 330707e..1266492 100644 ++--- a/qcsrc/server/mutators/gamemode_assault.qh +++++ b/qcsrc/server/mutators/gamemode_assault.qh ++@@ -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 ++diff --git a/qcsrc/server/mutators/gamemode_ctf.qc b/qcsrc/server/mutators/gamemode_ctf.qc ++index 2e6eb4f..4c94e49 100644 ++--- a/qcsrc/server/mutators/gamemode_ctf.qc +++++ b/qcsrc/server/mutators/gamemode_ctf.qc ++@@ -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)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 ++diff --git a/qcsrc/server/mutators/gamemode_ctf.qh b/qcsrc/server/mutators/gamemode_ctf.qh ++index 0e5930d..5d57dce 100644 ++--- a/qcsrc/server/mutators/gamemode_ctf.qh +++++ b/qcsrc/server/mutators/gamemode_ctf.qh ++@@ -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; ++diff --git a/qcsrc/server/mutators/gamemode_domination.qc b/qcsrc/server/mutators/gamemode_domination.qc ++index dd6e8b2..314221a 100644 ++--- a/qcsrc/server/mutators/gamemode_domination.qc +++++ b/qcsrc/server/mutators/gamemode_domination.qc ++@@ -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) ++diff --git a/qcsrc/server/mutators/gamemode_onslaught.qc b/qcsrc/server/mutators/gamemode_onslaught.qc ++index d1f20f7..93ed9f7 100644 ++--- a/qcsrc/server/mutators/gamemode_onslaught.qc +++++ b/qcsrc/server/mutators/gamemode_onslaught.qc ++@@ -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; ++diff --git a/qcsrc/server/mutators/mutator_buffs.qc b/qcsrc/server/mutators/mutator_buffs.qc ++index b2f800a..942c965 100644 ++--- a/qcsrc/server/mutators/mutator_buffs.qc +++++ b/qcsrc/server/mutators/mutator_buffs.qc ++@@ -1,3 +1,5 @@ +++#include "../../common/triggers/target/music.qh" +++ ++ float buffs_BuffModel_Customize() ++ { ++ entity player, myowner; ++diff --git a/qcsrc/server/mutators/mutator_dodging.qc b/qcsrc/server/mutators/mutator_dodging.qc ++index ad52d3e..39ecc2f 100644 ++--- a/qcsrc/server/mutators/mutator_dodging.qc +++++ b/qcsrc/server/mutators/mutator_dodging.qc ++@@ -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; ++@@ -20,233 +71,225 @@ ++ // 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 ++diff --git a/qcsrc/server/mutators/mutator_multijump.qc b/qcsrc/server/mutators/mutator_multijump.qc ++index d36f3ec..f78e6e0 100644 ++--- a/qcsrc/server/mutators/mutator_multijump.qc +++++ b/qcsrc/server/mutators/mutator_multijump.qc ++@@ -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 ++diff --git a/qcsrc/server/mutators/mutator_nades.qc b/qcsrc/server/mutators/mutator_nades.qc ++index 3932ce4..dc1b6bc 100644 ++--- a/qcsrc/server/mutators/mutator_nades.qc +++++ b/qcsrc/server/mutators/mutator_nades.qc ++@@ -1,3 +1,5 @@ +++#include "../../common/triggers/target/music.qh" +++ ++ .entity nade_spawnloc; ++ ++ void nade_timer_think() ++diff --git a/qcsrc/server/mutators/mutator_overkill.qc b/qcsrc/server/mutators/mutator_overkill.qc ++index 6818b45..1d151fc 100644 ++--- a/qcsrc/server/mutators/mutator_overkill.qc +++++ b/qcsrc/server/mutators/mutator_overkill.qc ++@@ -64,7 +64,6 @@ MUTATOR_HOOKFUNCTION(ok_PlayerDamage_SplitHealthArmor) ++ MUTATOR_HOOKFUNCTION(ok_PlayerDies) ++ { ++ entity oldself = self; ++- entity targ = ((frag_attacker) ? frag_attacker : frag_target); ++ ++ if(self.flags & FL_MONSTER) ++ { ++@@ -81,7 +80,7 @@ MUTATOR_HOOKFUNCTION(ok_PlayerDies) ++ self.gravity = 1; ++ self.reset = SUB_Remove; ++ setorigin(self, frag_target.origin + '0 0 32'); ++- self.velocity = '0 0 200' + normalize(targ.origin - self.origin) * 500; +++ self.velocity = '0 0 200' + normalize(frag_attacker.origin - self.origin) * 500; ++ self.classname = "droppedweapon"; // hax ++ SUB_SetFade(self, time + 5, 1); ++ self = oldself; ++diff --git a/qcsrc/server/mutators/mutators_include.qc b/qcsrc/server/mutators/mutators_include.qc ++index a0170e4..c3bdec5 100644 ++--- a/qcsrc/server/mutators/mutators_include.qc +++++ b/qcsrc/server/mutators/mutators_include.qc ++@@ -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" ++diff --git a/qcsrc/server/progs.src b/qcsrc/server/progs.src ++index 85e75cd..9cab150 100644 ++--- a/qcsrc/server/progs.src +++++ b/qcsrc/server/progs.src ++@@ -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 ++diff --git a/qcsrc/server/race.qc b/qcsrc/server/race.qc ++index bb7f0ec..a42d240 100644 ++--- a/qcsrc/server/race.qc +++++ b/qcsrc/server/race.qc ++@@ -1,4 +1,5 @@ ++ #include "race.qh" +++#include "../common/triggers/subs.qh" ++ ++ void W_Porto_Fail(float failhard); ++ ++diff --git a/qcsrc/server/race.qh b/qcsrc/server/race.qh ++index bfdc9be..b51c1a9 100644 ++--- a/qcsrc/server/race.qh +++++ b/qcsrc/server/race.qh ++@@ -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 ++index 30fbedc..0000000 ++--- a/qcsrc/server/secret.qc +++++ /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 ++index 6e6ae0b..0000000 ++--- a/qcsrc/server/secret.qh +++++ /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 ++diff --git a/qcsrc/server/spawnpoints.qc b/qcsrc/server/spawnpoints.qc ++index 253c70f..aa5b09e 100644 ++--- a/qcsrc/server/spawnpoints.qc +++++ b/qcsrc/server/spawnpoints.qc ++@@ -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" ++diff --git a/qcsrc/server/t_halflife.qc b/qcsrc/server/t_halflife.qc ++index 681c5dc..f041dd3 100644 ++--- a/qcsrc/server/t_halflife.qc +++++ b/qcsrc/server/t_halflife.qc ++@@ -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; ++-} ++- ++diff --git a/qcsrc/server/t_items.qc b/qcsrc/server/t_items.qc ++index 03af802..4c454c7 100644 ++--- a/qcsrc/server/t_items.qc +++++ b/qcsrc/server/t_items.qc ++@@ -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 ++index 92af1cd..0000000 ++--- a/qcsrc/server/t_jumppads.qc +++++ /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 ++index 9d66e98..0000000 ++--- a/qcsrc/server/t_jumppads.qh +++++ /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 ++index 5b9433a..0000000 ++--- a/qcsrc/server/t_plats.qc +++++ /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 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 ++index b63e7ff..0000000 ++--- a/qcsrc/server/t_swamp.qc +++++ /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 ++index 46df0eb..0000000 ++--- a/qcsrc/server/t_teleporters.qc +++++ /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 ++index 03e3c3f..0000000 ++--- a/qcsrc/server/t_teleporters.qh +++++ /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 ++index 94bc92f..0000000 ++--- a/qcsrc/server/target_music.qc +++++ /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 ++index b4b9b18..0000000 ++--- a/qcsrc/server/target_spawn.qc +++++ /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; ++-} ++diff --git a/qcsrc/server/tturrets/system/system_main.qc b/qcsrc/server/tturrets/system/system_main.qc ++index f2b0c56..4c3a523 100644 ++--- a/qcsrc/server/tturrets/system/system_main.qc +++++ b/qcsrc/server/tturrets/system/system_main.qc ++@@ -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) ++diff --git a/qcsrc/server/tturrets/system/system_misc.qc b/qcsrc/server/tturrets/system/system_misc.qc ++index 7875106..eab1e1a 100644 ++--- a/qcsrc/server/tturrets/system/system_misc.qc +++++ b/qcsrc/server/tturrets/system/system_misc.qc ++@@ -229,7 +229,6 @@ void turrets_precash() ++ ++ ++ #ifdef TURRET_DEBUG ++-void SUB_Remove(); ++ void marker_think() ++ { ++ if(self.cnt) ++diff --git a/qcsrc/server/vehicles/racer.qc b/qcsrc/server/vehicles/racer.qc ++index 87dfe52..06760b0 100644 ++--- a/qcsrc/server/vehicles/racer.qc +++++ b/qcsrc/server/vehicles/racer.qc ++@@ -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(); ++ ++diff --git a/qcsrc/server/vehicles/vehicles.qc b/qcsrc/server/vehicles/vehicles.qc ++index 96d54e3..5f639a8 100644 ++--- a/qcsrc/server/vehicles/vehicles.qc +++++ b/qcsrc/server/vehicles/vehicles.qc ++@@ -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; ++diff --git a/qcsrc/warpzonelib/common.qc b/qcsrc/warpzonelib/common.qc ++index aa0de91..a817e82 100644 ++--- a/qcsrc/warpzonelib/common.qc +++++ b/qcsrc/warpzonelib/common.qc ++@@ -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; +++} ++diff --git a/qcsrc/warpzonelib/common.qh b/qcsrc/warpzonelib/common.qh ++index 7742db8..76a77ab 100644 ++--- a/qcsrc/warpzonelib/common.qh +++++ b/qcsrc/warpzonelib/common.qh ++@@ -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 ++diff --git a/qcsrc/warpzonelib/server.qc b/qcsrc/warpzonelib/server.qc ++index 5d529c1..8e25b98 100644 ++--- a/qcsrc/warpzonelib/server.qc +++++ b/qcsrc/warpzonelib/server.qc ++@@ -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" ++diff --git a/qcsrc/warpzonelib/util_server.qc b/qcsrc/warpzonelib/util_server.qc ++index 79cff01..4cb0617 100644 ++--- a/qcsrc/warpzonelib/util_server.qc +++++ b/qcsrc/warpzonelib/util_server.qc ++@@ -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() ++ { ++diff --git a/qcsrc/warpzonelib/util_server.qh b/qcsrc/warpzonelib/util_server.qh ++index 38df7fe..0a42d7d 100644 ++--- a/qcsrc/warpzonelib/util_server.qh +++++ b/qcsrc/warpzonelib/util_server.qh ++@@ -3,5 +3,7 @@ ++ ++ float WarpZoneLib_MoveOutOfSolid(entity e); ++ float WarpZoneLib_ExactTrigger_Touch(); +++#ifdef SVQC ++ void WarpZoneLib_ExactTrigger_Init(); ++ #endif +++#endif