From: TimePath Date: Sat, 20 Aug 2016 13:44:16 +0000 (+1000) Subject: Cleanup common mutators: remove IMPLEMENTATION macro X-Git-Tag: xonotic-v0.8.2~688^2~8 X-Git-Url: https://de.git.xonotic.org/?p=xonotic%2Fxonotic-data.pk3dir.git;a=commitdiff_plain;h=b0210c08a287ad4631e16ddf3a83a0b0c546283f Cleanup common mutators: remove IMPLEMENTATION macro --- diff --git a/qcsrc/client/commands/_mod.inc b/qcsrc/client/commands/_mod.inc index 49fd90b32a..dcfc6ecea5 100644 --- a/qcsrc/client/commands/_mod.inc +++ b/qcsrc/client/commands/_mod.inc @@ -1,2 +1,4 @@ // generated file; do not modify -#include +#ifdef CSQC + #include +#endif diff --git a/qcsrc/client/commands/_mod.qh b/qcsrc/client/commands/_mod.qh index 9e5aa99743..7829965a23 100644 --- a/qcsrc/client/commands/_mod.qh +++ b/qcsrc/client/commands/_mod.qh @@ -1,2 +1,4 @@ // generated file; do not modify -#include +#ifdef CSQC + #include +#endif diff --git a/qcsrc/client/view.qc b/qcsrc/client/view.qc index 8070d0c440..86b2db13f1 100644 --- a/qcsrc/client/view.qc +++ b/qcsrc/client/view.qc @@ -15,7 +15,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/qcsrc/common/campaign_file.qh b/qcsrc/common/campaign_file.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/campaign_file.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/campaign_setup.qh b/qcsrc/common/campaign_setup.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/campaign_setup.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/effects/effectinfo.qh b/qcsrc/common/effects/effectinfo.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/effects/effectinfo.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/effects/qc/casings.qh b/qcsrc/common/effects/qc/casings.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/effects/qc/casings.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/effects/qc/damageeffects.qh b/qcsrc/common/effects/qc/damageeffects.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/effects/qc/damageeffects.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/effects/qc/globalsound.qc b/qcsrc/common/effects/qc/globalsound.qc index 573b52f22c..d60b2e8a65 100644 --- a/qcsrc/common/effects/qc/globalsound.qc +++ b/qcsrc/common/effects/qc/globalsound.qc @@ -6,7 +6,7 @@ #include #ifdef SVQC - #include + #include #endif REGISTER_NET_TEMP(globalsound) diff --git a/qcsrc/common/gamemodes/_all.inc b/qcsrc/common/gamemodes/_all.inc index 8bc63f720a..21f512ae27 100644 --- a/qcsrc/common/gamemodes/_all.inc +++ b/qcsrc/common/gamemodes/_all.inc @@ -1,2 +1,4 @@ #include "_all.qh" #include "_mod.inc" + +#include "gamemode/_all.inc" diff --git a/qcsrc/common/gamemodes/_all.qh b/qcsrc/common/gamemodes/_all.qh index 947026dd59..8aed6ca804 100644 --- a/qcsrc/common/gamemodes/_all.qh +++ b/qcsrc/common/gamemodes/_all.qh @@ -1,2 +1,4 @@ #pragma once #include "_mod.qh" + +#include "gamemode/_all.qh" diff --git a/qcsrc/common/gamemodes/_mod.inc b/qcsrc/common/gamemodes/_mod.inc index 0b779498b1..98fb4815c1 100644 --- a/qcsrc/common/gamemodes/_mod.inc +++ b/qcsrc/common/gamemodes/_mod.inc @@ -1,2 +1 @@ // generated file; do not modify -#include diff --git a/qcsrc/common/gamemodes/_mod.qh b/qcsrc/common/gamemodes/_mod.qh index a7b7a54af4..98fb4815c1 100644 --- a/qcsrc/common/gamemodes/_mod.qh +++ b/qcsrc/common/gamemodes/_mod.qh @@ -1,2 +1 @@ // generated file; do not modify -#include diff --git a/qcsrc/common/gamemodes/all.inc b/qcsrc/common/gamemodes/all.inc deleted file mode 100644 index bcdcd7c4c0..0000000000 --- a/qcsrc/common/gamemodes/all.inc +++ /dev/null @@ -1,2 +0,0 @@ -#include "gamemode/nexball/module.inc" -#include "gamemode/onslaught/module.inc" diff --git a/qcsrc/common/gamemodes/all.qc b/qcsrc/common/gamemodes/all.qc deleted file mode 100644 index f0fc7195a8..0000000000 --- a/qcsrc/common/gamemodes/all.qc +++ /dev/null @@ -1,5 +0,0 @@ -#include "all.qh" - -#define IMPLEMENTATION -#include "all.inc" -#undef IMPLEMENTATION diff --git a/qcsrc/common/gamemodes/all.qh b/qcsrc/common/gamemodes/all.qh deleted file mode 100644 index 62ba61696e..0000000000 --- a/qcsrc/common/gamemodes/all.qh +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef GAMEMODES_ALL_H -#define GAMEMODES_ALL_H - -#include "all.inc" - -#endif diff --git a/qcsrc/common/gamemodes/gamemode/_all.inc b/qcsrc/common/gamemodes/gamemode/_all.inc new file mode 100644 index 0000000000..7c51f951b6 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/_all.inc @@ -0,0 +1,5 @@ +#include "_all.qh" +#include "_mod.inc" + +#include "nexball/_mod.inc" +#include "onslaught/_mod.inc" diff --git a/qcsrc/common/gamemodes/gamemode/_all.qh b/qcsrc/common/gamemodes/gamemode/_all.qh new file mode 100644 index 0000000000..dd16f04dd7 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/_all.qh @@ -0,0 +1,5 @@ +#pragma once +#include "_mod.qh" + +#include "nexball/_mod.qh" +#include "onslaught/_mod.qh" diff --git a/qcsrc/common/gamemodes/gamemode/nexball/module.inc b/qcsrc/common/gamemodes/gamemode/nexball/module.inc deleted file mode 100644 index 0d809629de..0000000000 --- a/qcsrc/common/gamemodes/gamemode/nexball/module.inc +++ /dev/null @@ -1,2 +0,0 @@ -#include "nexball.qc" -#include "weapon.qc" diff --git a/qcsrc/common/gamemodes/gamemode/nexball/nexball.qc b/qcsrc/common/gamemodes/gamemode/nexball/nexball.qc index 0f0ecb4719..9c6c9f0803 100644 --- a/qcsrc/common/gamemodes/gamemode/nexball/nexball.qc +++ b/qcsrc/common/gamemodes/gamemode/nexball/nexball.qc @@ -1,6 +1,5 @@ #include "nexball.qh" -#ifdef IMPLEMENTATION #ifdef CSQC int autocvar_cl_eventchase_nexball = 1; @@ -1157,4 +1156,3 @@ REGISTER_MUTATOR(nb, g_nexball) } #endif -#endif diff --git a/qcsrc/common/gamemodes/gamemode/nexball/nexball.qh b/qcsrc/common/gamemodes/gamemode/nexball/nexball.qh index 9dd8042be3..53797d2bc6 100644 --- a/qcsrc/common/gamemodes/gamemode/nexball/nexball.qh +++ b/qcsrc/common/gamemodes/gamemode/nexball/nexball.qh @@ -1,5 +1,4 @@ -#ifndef GAMEMODE_NEXBALL_H -#define GAMEMODE_NEXBALL_H +#pragma once #ifdef SVQC //EF_BRIGHTFIELD|EF_BRIGHTLIGHT|EF_DIMLIGHT|EF_BLUE|EF_RED|EF_FLAME @@ -35,4 +34,3 @@ float nb_teams; .float teamtime; #endif -#endif diff --git a/qcsrc/common/gamemodes/gamemode/nexball/weapon.qc b/qcsrc/common/gamemodes/gamemode/nexball/weapon.qc index c6aa4be215..f207263535 100644 --- a/qcsrc/common/gamemodes/gamemode/nexball/weapon.qc +++ b/qcsrc/common/gamemodes/gamemode/nexball/weapon.qc @@ -1,12 +1 @@ -#ifndef GAMEMODE_NEXBALL_WEAPON_H -#define GAMEMODE_NEXBALL_WEAPON_H - -CLASS(BallStealer, PortoLaunch) -/* flags */ ATTRIB(BallStealer, spawnflags, int, WEP_TYPE_OTHER | WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED); -/* impulse */ ATTRIB(BallStealer, impulse, int, 0); -/* refname */ ATTRIB(BallStealer, netname, string, "ballstealer"); -/* wepname */ ATTRIB(BallStealer, m_name, string, _("Ball Stealer")); -ENDCLASS(BallStealer) -REGISTER_WEAPON(NEXBALL, NEW(BallStealer)); - -#endif +#include "weapon.qh" diff --git a/qcsrc/common/gamemodes/gamemode/nexball/weapon.qh b/qcsrc/common/gamemodes/gamemode/nexball/weapon.qh new file mode 100644 index 0000000000..73b887260c --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/nexball/weapon.qh @@ -0,0 +1,9 @@ +#pragma once + +CLASS(BallStealer, PortoLaunch) +/* flags */ ATTRIB(BallStealer, spawnflags, int, WEP_TYPE_OTHER | WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED); +/* impulse */ ATTRIB(BallStealer, impulse, int, 0); +/* refname */ ATTRIB(BallStealer, netname, string, "ballstealer"); +/* wepname */ ATTRIB(BallStealer, m_name, string, _("Ball Stealer")); +ENDCLASS(BallStealer) +REGISTER_WEAPON(NEXBALL, NEW(BallStealer)); diff --git a/qcsrc/common/gamemodes/gamemode/onslaught/_mod.inc b/qcsrc/common/gamemodes/gamemode/onslaught/_mod.inc index 334eb561ee..ca8c83c19a 100644 --- a/qcsrc/common/gamemodes/gamemode/onslaught/_mod.inc +++ b/qcsrc/common/gamemodes/gamemode/onslaught/_mod.inc @@ -1,6 +1,19 @@ // generated file; do not modify -#include -#include +#include +#ifdef CSQC + #include +#endif +#ifdef SVQC + #include +#endif +#include +#ifdef CSQC + #include +#endif +#ifdef SVQC + #include +#endif #include -#include -#include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/gamemodes/gamemode/onslaught/_mod.qh b/qcsrc/common/gamemodes/gamemode/onslaught/_mod.qh index e895495581..bb95416b31 100644 --- a/qcsrc/common/gamemodes/gamemode/onslaught/_mod.qh +++ b/qcsrc/common/gamemodes/gamemode/onslaught/_mod.qh @@ -1,6 +1,19 @@ // generated file; do not modify -#include -#include +#include +#ifdef CSQC + #include +#endif +#ifdef SVQC + #include +#endif +#include +#ifdef CSQC + #include +#endif +#ifdef SVQC + #include +#endif #include -#include -#include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/gamemodes/gamemode/onslaught/cl_controlpoint.qh b/qcsrc/common/gamemodes/gamemode/onslaught/cl_controlpoint.qh index 15586ea5dc..d5437338e3 100644 --- a/qcsrc/common/gamemodes/gamemode/onslaught/cl_controlpoint.qh +++ b/qcsrc/common/gamemodes/gamemode/onslaught/cl_controlpoint.qh @@ -1,10 +1,7 @@ -#ifndef CLIENT_CONTROLPOINT_H -#define CLIENT_CONTROLPOINT_H +#pragma once const vector CPICON_MIN = '-32 -32 -9'; const vector CPICON_MAX = '32 32 25'; const int CPSF_STATUS = 4; const int CPSF_SETUP = 8; - -#endif diff --git a/qcsrc/common/gamemodes/gamemode/onslaught/cl_generator.qh b/qcsrc/common/gamemodes/gamemode/onslaught/cl_generator.qh index 3c0cf28697..49524687f4 100644 --- a/qcsrc/common/gamemodes/gamemode/onslaught/cl_generator.qh +++ b/qcsrc/common/gamemodes/gamemode/onslaught/cl_generator.qh @@ -1,9 +1,7 @@ -#ifndef CLIENT_GENERATOR_H -#define CLIENT_GENERATOR_H +#pragma once + const vector GENERATOR_MIN = '-52 -52 -14'; const vector GENERATOR_MAX = '52 52 75'; const int GSF_STATUS = 4; const int GSF_SETUP = 8; - -#endif diff --git a/qcsrc/common/gamemodes/gamemode/onslaught/controlpoint.qc b/qcsrc/common/gamemodes/gamemode/onslaught/controlpoint.qc new file mode 100644 index 0000000000..b21f5fd228 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/onslaught/controlpoint.qc @@ -0,0 +1 @@ +#include "controlpoint.qh" diff --git a/qcsrc/common/gamemodes/gamemode/onslaught/controlpoint.qh b/qcsrc/common/gamemodes/gamemode/onslaught/controlpoint.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/onslaught/controlpoint.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/gamemodes/gamemode/onslaught/generator.qc b/qcsrc/common/gamemodes/gamemode/onslaught/generator.qc new file mode 100644 index 0000000000..f9415f619f --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/onslaught/generator.qc @@ -0,0 +1 @@ +#include "generator.qh" diff --git a/qcsrc/common/gamemodes/gamemode/onslaught/generator.qh b/qcsrc/common/gamemodes/gamemode/onslaught/generator.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/onslaught/generator.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/gamemodes/gamemode/onslaught/module.inc b/qcsrc/common/gamemodes/gamemode/onslaught/module.inc deleted file mode 100644 index fee33b14e2..0000000000 --- a/qcsrc/common/gamemodes/gamemode/onslaught/module.inc +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef ONS_CONSTANTS - #define ONS_CONSTANTS - REGISTER_NET_LINKED(ENT_CLIENT_GENERATOR) - REGISTER_NET_LINKED(ENT_CLIENT_CONTROLPOINT_ICON) -#endif - -#if defined(SVQC) - #include "onslaught.qc" - #ifndef IMPLEMENTATION - #include "sv_controlpoint.qh" - #include "sv_generator.qh" - #else - #include "sv_controlpoint.qc" - #include "sv_generator.qc" - #endif -#elif defined(CSQC) - #ifndef IMPLEMENTATION - #include "cl_controlpoint.qh" - #include "cl_generator.qh" - #else - #include "cl_controlpoint.qc" - #include "cl_generator.qc" - #endif -#endif diff --git a/qcsrc/common/gamemodes/gamemode/onslaught/onslaught.qc b/qcsrc/common/gamemodes/gamemode/onslaught/onslaught.qc index 713c5d7de0..36926b754b 100644 --- a/qcsrc/common/gamemodes/gamemode/onslaught/onslaught.qc +++ b/qcsrc/common/gamemodes/gamemode/onslaught/onslaught.qc @@ -1,2258 +1 @@ -#ifndef GAMEMODE_ONSLAUGHT_H -#define GAMEMODE_ONSLAUGHT_H - -float autocvar_g_onslaught_point_limit; -void ons_Initialize(); - -REGISTER_MUTATOR(ons, false) -{ - MUTATOR_ONADD - { - if (time > 1) // game loads at time 1 - error("This is a game type and it cannot be added at runtime."); - ons_Initialize(); - - ActivateTeamplay(); - SetLimits(autocvar_g_onslaught_point_limit, autocvar_leadlimit_override, autocvar_timelimit_override, -1); - have_team_spawns = -1; // request team spawns - } - - MUTATOR_ONROLLBACK_OR_REMOVE - { - // we actually cannot roll back ons_Initialize here - // BUT: we don't need to! If this gets called, adding always - // succeeds. - } - - MUTATOR_ONREMOVE - { - LOG_INFO("This is a game type and it cannot be removed at runtime."); - return -1; - } - - return false; -} - -#ifdef SVQC - -.entity ons_toucher; // player who touched the control point - -// control point / generator constants -const float ONS_CP_THINKRATE = 0.2; -const float GEN_THINKRATE = 1; -#define CPGEN_SPAWN_OFFSET ('0 0 1' * (PL_MAX_CONST.z - 13)) -const vector CPGEN_WAYPOINT_OFFSET = ('0 0 128'); -const vector CPICON_OFFSET = ('0 0 96'); - -// list of generators on the map -entity ons_worldgeneratorlist; -.entity ons_worldgeneratornext; -.entity ons_stalegeneratornext; - -// list of control points on the map -entity ons_worldcplist; -.entity ons_worldcpnext; -.entity ons_stalecpnext; - -// list of links on the map -entity ons_worldlinklist; -.entity ons_worldlinknext; -.entity ons_stalelinknext; - -// definitions -.entity sprite; -.string target2; -.int iscaptured; -.int islinked; -.int isshielded; -.float lasthealth; -.int lastteam; -.int lastshielded; -.int lastcaptured; - -.bool waslinked; - -bool ons_stalemate; - -.float teleport_antispam; - -.bool ons_roundlost = _STAT(ROUNDLOST); - -// waypoint sprites -.entity bot_basewaypoint; // generator waypointsprite - -.bool isgenneighbor[17]; -.bool iscpneighbor[17]; -float ons_notification_time[17]; - -.float ons_overtime_damagedelay; - -.vector ons_deathloc; - -.entity ons_spawn_by; - -// declarations for functions used outside gamemode_onslaught.qc -void ons_Generator_UpdateSprite(entity e); -void ons_ControlPoint_UpdateSprite(entity e); -bool ons_ControlPoint_Attackable(entity cp, int teamnumber); - -// CaptureShield: Prevent capturing or destroying control point/generator if it is not available yet -float ons_captureshield_force; // push force of the shield - -// bot player logic -const int HAVOCBOT_ONS_ROLE_NONE = 0; -const int HAVOCBOT_ONS_ROLE_DEFENSE = 2; -const int HAVOCBOT_ONS_ROLE_ASSISTANT = 4; -const int HAVOCBOT_ONS_ROLE_OFFENSE = 8; - -.entity havocbot_ons_target; - -.int havocbot_role_flags; -.float havocbot_attack_time; - -void havocbot_role_ons_defense(entity this); -void havocbot_role_ons_offense(entity this); -void havocbot_role_ons_assistant(entity this); - -void havocbot_ons_reset_role(entity this); -void havocbot_goalrating_items(entity this, float ratingscale, vector org, float sradius); -void havocbot_goalrating_enemyplayers(entity this, float ratingscale, vector org, float sradius); - -// score rule declarations -const int ST_ONS_CAPS = 1; - -#endif -#endif - -#ifdef IMPLEMENTATION - -#include "sv_controlpoint.qh" -#include "sv_generator.qh" - -bool g_onslaught; - -float autocvar_g_onslaught_teleport_wait; -bool autocvar_g_onslaught_spawn_at_controlpoints; -bool 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; -float autocvar_g_onslaught_spawn_at_controlpoints_chance = 0.5; -float autocvar_g_onslaught_spawn_at_controlpoints_random; -float autocvar_g_onslaught_spawn_at_generator_chance; -float autocvar_g_onslaught_spawn_at_generator_random; -float autocvar_g_onslaught_cp_buildhealth; -float autocvar_g_onslaught_cp_buildtime; -float autocvar_g_onslaught_cp_health; -float autocvar_g_onslaught_cp_regen; -float autocvar_g_onslaught_gen_health; -float autocvar_g_onslaught_shield_force = 100; -float autocvar_g_onslaught_allow_vehicle_touch; -float autocvar_g_onslaught_round_timelimit; -float autocvar_g_onslaught_warmup; -float autocvar_g_onslaught_teleport_radius; -float autocvar_g_onslaught_spawn_choose; -float autocvar_g_onslaught_click_radius; - -void FixSize(entity e); - -// ======================= -// CaptureShield Functions -// ======================= - -bool ons_CaptureShield_Customize(entity this, entity client) -{ - entity e = WaypointSprite_getviewentity(client); - - if(!this.enemy.isshielded && (ons_ControlPoint_Attackable(this.enemy, e.team) > 0 || this.enemy.classname != "onslaught_controlpoint")) { return false; } - if(SAME_TEAM(this, e)) { return false; } - - return true; -} - -void ons_CaptureShield_Touch(entity this, entity toucher) -{ - if(!this.enemy.isshielded && (ons_ControlPoint_Attackable(this.enemy, toucher.team) > 0 || this.enemy.classname != "onslaught_controlpoint")) { return; } - if(!IS_PLAYER(toucher)) { return; } - if(SAME_TEAM(toucher, this)) { return; } - - vector mymid = (this.absmin + this.absmax) * 0.5; - vector theirmid = (toucher.absmin + toucher.absmax) * 0.5; - - Damage(toucher, this, this, 0, DEATH_HURTTRIGGER.m_id, mymid, normalize(theirmid - mymid) * ons_captureshield_force); - - if(IS_REAL_CLIENT(toucher)) - { - play2(toucher, SND(ONS_DAMAGEBLOCKEDBYSHIELD)); - - if(this.enemy.classname == "onslaught_generator") - Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_ONS_GENERATOR_SHIELDED); - else - Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_ONS_CONTROLPOINT_SHIELDED); - } -} - -void ons_CaptureShield_Reset(entity this) -{ - this.colormap = this.enemy.colormap; - this.team = this.enemy.team; -} - -void ons_CaptureShield_Spawn(entity generator, bool is_generator) -{ - entity shield = new(ons_captureshield); - - shield.enemy = generator; - shield.team = generator.team; - shield.colormap = generator.colormap; - shield.reset = ons_CaptureShield_Reset; - settouch(shield, ons_CaptureShield_Touch); - setcefc(shield, ons_CaptureShield_Customize); - shield.effects = EF_ADDITIVE; - set_movetype(shield, 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); -} - - -// ========== -// Junk Pile -// ========== - -void setmodel_fixsize(entity e, Model m) -{ - setmodel(e, m); - FixSize(e); -} - -void onslaught_updatelinks() -{ - entity l; - // first check if the game has ended - LOG_DEBUG("--- updatelinks ---"); - // mark generators as being shielded and networked - for(l = ons_worldgeneratorlist; l; l = l.ons_worldgeneratornext) - { - if (l.iscaptured) - LOG_DEBUG(etos(l), " (generator) belongs to team ", ftos(l.team)); - else - LOG_DEBUG(etos(l), " (generator) is destroyed"); - l.islinked = l.iscaptured; - l.isshielded = l.iscaptured; - l.sprite.SendFlags |= 16; - } - // mark points as shielded and not networked - for(l = ons_worldcplist; l; l = l.ons_worldcpnext) - { - l.islinked = false; - l.isshielded = true; - int i; - for(i = 0; i < 17; ++i) { l.isgenneighbor[i] = false; l.iscpneighbor[i] = false; } - LOG_DEBUG(etos(l), " (point) belongs to team ", ftos(l.team)); - l.sprite.SendFlags |= 16; - } - // flow power outward from the generators through the network - bool stop = false; - while (!stop) - { - stop = true; - for(l = ons_worldlinklist; l; l = l.ons_worldlinknext) - { - // if both points are captured by the same team, and only one of - // them is powered, mark the other one as powered as well - if (l.enemy.iscaptured && l.goalentity.iscaptured) - if (l.enemy.islinked != l.goalentity.islinked) - if(SAME_TEAM(l.enemy, l.goalentity)) - { - if (!l.goalentity.islinked) - { - stop = false; - l.goalentity.islinked = true; - LOG_DEBUG(etos(l), " (link) is marking ", etos(l.goalentity), " (point) because its team matches ", etos(l.enemy), " (point)"); - } - else if (!l.enemy.islinked) - { - stop = false; - l.enemy.islinked = true; - LOG_DEBUG(etos(l), " (link) is marking ", etos(l.enemy), " (point) because its team matches ", etos(l.goalentity), " (point)"); - } - } - } - } - // now that we know which points are powered we can mark their neighbors - // as unshielded if team differs - for(l = ons_worldlinklist; l; l = l.ons_worldlinknext) - { - if (l.goalentity.islinked) - { - if(DIFF_TEAM(l.goalentity, l.enemy)) - { - LOG_DEBUG(etos(l), " (link) is unshielding ", etos(l.enemy), " (point) because its team does not match ", etos(l.goalentity), " (point)"); - l.enemy.isshielded = false; - } - if(l.goalentity.classname == "onslaught_generator") - l.enemy.isgenneighbor[l.goalentity.team] = true; - else - l.enemy.iscpneighbor[l.goalentity.team] = true; - } - if (l.enemy.islinked) - { - if(DIFF_TEAM(l.goalentity, l.enemy)) - { - LOG_DEBUG(etos(l), " (link) is unshielding ", etos(l.goalentity), " (point) because its team does not match ", etos(l.enemy), " (point)"); - l.goalentity.isshielded = false; - } - if(l.enemy.classname == "onslaught_generator") - l.goalentity.isgenneighbor[l.enemy.team] = true; - else - l.goalentity.iscpneighbor[l.enemy.team] = true; - } - } - // now update the generators - for(l = ons_worldgeneratorlist; l; l = l.ons_worldgeneratornext) - { - if (l.isshielded) - { - LOG_DEBUG(etos(l), " (generator) is shielded"); - l.takedamage = DAMAGE_NO; - l.bot_attack = false; - } - else - { - LOG_DEBUG(etos(l), " (generator) is not shielded"); - l.takedamage = DAMAGE_AIM; - l.bot_attack = true; - } - - ons_Generator_UpdateSprite(l); - } - // now update the takedamage and alpha variables on control point icons - for(l = ons_worldcplist; l; l = l.ons_worldcpnext) - { - if (l.isshielded) - { - LOG_DEBUG(etos(l), " (point) is shielded"); - if (l.goalentity) - { - l.goalentity.takedamage = DAMAGE_NO; - l.goalentity.bot_attack = false; - } - } - else - { - LOG_DEBUG(etos(l), " (point) is not shielded"); - if (l.goalentity) - { - l.goalentity.takedamage = DAMAGE_AIM; - l.goalentity.bot_attack = true; - } - } - ons_ControlPoint_UpdateSprite(l); - } - FOREACH_ENTITY_CLASS("ons_captureshield", true, - { - it.team = it.enemy.team; - it.colormap = it.enemy.colormap; - }); -} - - -// =================== -// Main Link Functions -// =================== - -bool ons_Link_Send(entity this, entity to, int sendflags) -{ - WriteHeader(MSG_ENTITY, ENT_CLIENT_RADARLINK); - WriteByte(MSG_ENTITY, sendflags); - if(sendflags & 1) - { - WriteCoord(MSG_ENTITY, this.goalentity.origin_x); - WriteCoord(MSG_ENTITY, this.goalentity.origin_y); - WriteCoord(MSG_ENTITY, this.goalentity.origin_z); - } - if(sendflags & 2) - { - WriteCoord(MSG_ENTITY, this.enemy.origin_x); - WriteCoord(MSG_ENTITY, this.enemy.origin_y); - WriteCoord(MSG_ENTITY, this.enemy.origin_z); - } - if(sendflags & 4) - { - WriteByte(MSG_ENTITY, this.clientcolors); // which is goalentity's color + enemy's color * 16 - } - return true; -} - -void ons_Link_CheckUpdate(entity this) -{ - // TODO check if the two sides have moved (currently they won't move anyway) - float cc = 0, cc1 = 0, cc2 = 0; - - if(this.goalentity.islinked || this.goalentity.iscaptured) { cc1 = (this.goalentity.team - 1) * 0x01; } - if(this.enemy.islinked || this.enemy.iscaptured) { cc2 = (this.enemy.team - 1) * 0x10; } - - cc = cc1 + cc2; - - if(cc != this.clientcolors) - { - this.clientcolors = cc; - this.SendFlags |= 4; - } - - this.nextthink = time; -} - -void ons_DelayedLinkSetup(entity this) -{ - this.goalentity = find(NULL, targetname, this.target); - this.enemy = find(NULL, targetname, this.target2); - if(!this.goalentity) { objerror(this, "can not find target\n"); } - if(!this.enemy) { objerror(this, "can not find target2\n"); } - - LOG_DEBUG(etos(this.goalentity), " linked with ", etos(this.enemy)); - this.SendFlags |= 3; - setthink(this, ons_Link_CheckUpdate); - this.nextthink = time; -} - - -// ============================= -// Main Control Point Functions -// ============================= - -int ons_ControlPoint_CanBeLinked(entity cp, int teamnumber) -{ - if(cp.isgenneighbor[teamnumber]) { return 2; } - if(cp.iscpneighbor[teamnumber]) { return 1; } - - return 0; -} - -int ons_ControlPoint_Attackable(entity cp, int teamnumber) - // -2: SAME TEAM, attackable by enemy! - // -1: SAME TEAM! - // 0: off limits - // 1: attack it - // 2: touch it - // 3: attack it (HIGH PRIO) - // 4: touch it (HIGH PRIO) -{ - int a; - - if(cp.isshielded) - { - return 0; - } - else if(cp.goalentity) - { - // if there's already an icon built, nothing happens - if(cp.team == teamnumber) - { - a = ons_ControlPoint_CanBeLinked(cp, teamnumber); - if(a) // attackable by enemy? - return -2; // EMERGENCY! - return -1; - } - // we know it can be linked, so no need to check - // but... - a = ons_ControlPoint_CanBeLinked(cp, teamnumber); - if(a == 2) // near our generator? - return 3; // EMERGENCY! - return 1; - } - else - { - // free point - if(ons_ControlPoint_CanBeLinked(cp, teamnumber)) - { - a = ons_ControlPoint_CanBeLinked(cp, teamnumber); // why was this here NUM_TEAM_1 + NUM_TEAM_2 - t - if(a == 2) - return 4; // GET THIS ONE NOW! - else - return 2; // TOUCH ME - } - } - return 0; -} - -void ons_ControlPoint_Icon_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force) -{ - if(damage <= 0) { return; } - - if (this.owner.isshielded) - { - // this is protected by a shield, so ignore the damage - if (time > this.pain_finished) - if (IS_PLAYER(attacker)) - { - play2(attacker, SND(ONS_DAMAGEBLOCKEDBYSHIELD)); - this.pain_finished = time + 1; - attacker.typehitsound += 1; // play both sounds (shield is way too quiet) - } - - return; - } - - if(IS_PLAYER(attacker)) - if(time - ons_notification_time[this.team] > 10) - { - play2team(this.team, SND(ONS_CONTROLPOINT_UNDERATTACK)); - ons_notification_time[this.team] = time; - } - - this.health = this.health - damage; - if(this.owner.iscaptured) - WaypointSprite_UpdateHealth(this.owner.sprite, this.health); - else - WaypointSprite_UpdateBuildFinished(this.owner.sprite, time + (this.max_health - this.health) / (this.count / ONS_CP_THINKRATE)); - this.pain_finished = time + 1; - // particles on every hit - pointparticles(EFFECT_SPARKS, hitloc, force*-1, 1); - //sound on every hit - if (random() < 0.5) - sound(this, CH_TRIGGER, SND_ONS_HIT1, VOL_BASE+0.3, ATTEN_NORM); - else - sound(this, CH_TRIGGER, SND_ONS_HIT2, VOL_BASE+0.3, ATTEN_NORM); - - if (this.health < 0) - { - sound(this, CH_TRIGGER, SND_GRENADE_IMPACT, VOL_BASE, ATTEN_NORM); - pointparticles(EFFECT_ROCKET_EXPLODE, this.origin, '0 0 0', 1); - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(this.team, INFO_ONSLAUGHT_CPDESTROYED), this.owner.message, attacker.netname); - - PlayerScore_Add(attacker, SP_ONS_TAKES, 1); - PlayerScore_Add(attacker, SP_SCORE, 10); - - this.owner.goalentity = NULL; - this.owner.islinked = false; - this.owner.iscaptured = false; - this.owner.team = 0; - this.owner.colormap = 1024; - - WaypointSprite_UpdateMaxHealth(this.owner.sprite, 0); - - onslaught_updatelinks(); - - // Use targets now (somebody make sure this is in the right place..) - SUB_UseTargets(this.owner, this, NULL); - - this.owner.waslinked = this.owner.islinked; - if(this.owner.model != "models/onslaught/controlpoint_pad.md3") - setmodel_fixsize(this.owner, MDL_ONS_CP_PAD1); - //setsize(this, '-32 -32 0', '32 32 8'); - - delete(this); - } - - this.SendFlags |= CPSF_STATUS; -} - -void ons_ControlPoint_Icon_Think(entity this) -{ - this.nextthink = time + ONS_CP_THINKRATE; - - if(autocvar_g_onslaught_cp_proxydecap) - { - int _enemy_count = 0; - int _friendly_count = 0; - - FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it), { - if(vdist(it.origin - this.origin, <, autocvar_g_onslaught_cp_proxydecap_distance)) - { - if(SAME_TEAM(it, this)) - ++_friendly_count; - else - ++_enemy_count; - } - }); - - _friendly_count = _friendly_count * (autocvar_g_onslaught_cp_proxydecap_dps * ONS_CP_THINKRATE); - _enemy_count = _enemy_count * (autocvar_g_onslaught_cp_proxydecap_dps * ONS_CP_THINKRATE); - - this.health = bound(0, this.health + (_friendly_count - _enemy_count), this.max_health); - this.SendFlags |= CPSF_STATUS; - if(this.health <= 0) - { - ons_ControlPoint_Icon_Damage(this, this, this, 1, 0, this.origin, '0 0 0'); - return; - } - } - - if (time > this.pain_finished + 5) - { - if(this.health < this.max_health) - { - this.health = this.health + this.count; - if (this.health >= this.max_health) - this.health = this.max_health; - WaypointSprite_UpdateHealth(this.owner.sprite, this.health); - } - } - - if(this.owner.islinked != this.owner.waslinked) - { - // unteam the spawnpoint if needed - int t = this.owner.team; - if(!this.owner.islinked) - this.owner.team = 0; - - SUB_UseTargets(this.owner, this, NULL); - - this.owner.team = t; - - this.owner.waslinked = this.owner.islinked; - } - - // damaged fx - if(random() < 0.6 - this.health / this.max_health) - { - Send_Effect(EFFECT_ELECTRIC_SPARKS, this.origin + randompos('-10 -10 -20', '10 10 20'), '0 0 0', 1); - - if(random() > 0.8) - sound(this, CH_PAIN, SND_ONS_SPARK1, VOL_BASE, ATTEN_NORM); - else if (random() > 0.5) - sound(this, CH_PAIN, SND_ONS_SPARK2, VOL_BASE, ATTEN_NORM); - } -} - -void ons_ControlPoint_Icon_BuildThink(entity this) -{ - int a; - - this.nextthink = time + ONS_CP_THINKRATE; - - // only do this if there is power - a = ons_ControlPoint_CanBeLinked(this.owner, this.owner.team); - if(!a) - return; - - this.health = this.health + this.count; - - this.SendFlags |= CPSF_STATUS; - - if (this.health >= this.max_health) - { - this.health = this.max_health; - this.count = autocvar_g_onslaught_cp_regen * ONS_CP_THINKRATE; // slow repair rate from now on - setthink(this, ons_ControlPoint_Icon_Think); - sound(this, CH_TRIGGER, SND_ONS_CONTROLPOINT_BUILT, VOL_BASE, ATTEN_NORM); - this.owner.iscaptured = true; - this.solid = SOLID_BBOX; - - Send_Effect(EFFECT_CAP(this.owner.team), this.owner.origin, '0 0 0', 1); - - WaypointSprite_UpdateMaxHealth(this.owner.sprite, this.max_health); - WaypointSprite_UpdateHealth(this.owner.sprite, this.health); - - if(IS_PLAYER(this.owner.ons_toucher)) - { - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ONSLAUGHT_CAPTURE, this.owner.ons_toucher.netname, this.owner.message); - Send_Notification(NOTIF_ALL_EXCEPT, this.owner.ons_toucher, MSG_CENTER, APP_TEAM_NUM(this.owner.ons_toucher.team, CENTER_ONS_CAPTURE), this.owner.message); - Send_Notification(NOTIF_ONE, this.owner.ons_toucher, MSG_CENTER, CENTER_ONS_CAPTURE, this.owner.message); - PlayerScore_Add(this.owner.ons_toucher, SP_ONS_CAPS, 1); - PlayerTeamScore_AddScore(this.owner.ons_toucher, 10); - } - - this.owner.ons_toucher = NULL; - - onslaught_updatelinks(); - - // Use targets now (somebody make sure this is in the right place..) - SUB_UseTargets(this.owner, this, NULL); - - this.SendFlags |= CPSF_SETUP; - } - if(this.owner.model != MDL_ONS_CP_PAD2.model_str()) - setmodel_fixsize(this.owner, MDL_ONS_CP_PAD2); - - if(random() < 0.9 - this.health / this.max_health) - Send_Effect(EFFECT_RAGE, this.origin + 10 * randomvec(), '0 0 -1', 1); -} - -void onslaught_controlpoint_icon_link(entity e, void(entity this) spawnproc); - -void ons_ControlPoint_Icon_Spawn(entity cp, entity player) -{ - entity e = new(onslaught_controlpoint_icon); - - setsize(e, CPICON_MIN, CPICON_MAX); - setorigin(e, cp.origin + CPICON_OFFSET); - - e.owner = cp; - e.max_health = autocvar_g_onslaught_cp_health; - e.health = autocvar_g_onslaught_cp_buildhealth; - e.solid = SOLID_NOT; - e.takedamage = DAMAGE_AIM; - e.bot_attack = true; - e.event_damage = ons_ControlPoint_Icon_Damage; - e.team = player.team; - e.colormap = 1024 + (e.team - 1) * 17; - e.count = (e.max_health - e.health) * ONS_CP_THINKRATE / autocvar_g_onslaught_cp_buildtime; // how long it takes to build - - sound(e, CH_TRIGGER, SND_ONS_CONTROLPOINT_BUILD, VOL_BASE, ATTEN_NORM); - - cp.goalentity = e; - cp.team = e.team; - cp.colormap = e.colormap; - - Send_Effect(EFFECT_FLAG_TOUCH(player.team), e.origin, '0 0 0', 1); - - WaypointSprite_UpdateBuildFinished(cp.sprite, time + (e.max_health - e.health) / (e.count / ONS_CP_THINKRATE)); - WaypointSprite_UpdateRule(cp.sprite,cp.team,SPRITERULE_TEAMPLAY); - cp.sprite.SendFlags |= 16; - - onslaught_controlpoint_icon_link(e, ons_ControlPoint_Icon_BuildThink); -} - -entity ons_ControlPoint_Waypoint(entity e) -{ - if(e.team) - { - int a = ons_ControlPoint_Attackable(e, e.team); - - if(a == -2) { return WP_OnsCPDefend; } // defend now - if(a == -1 || a == 1 || a == 2) { return WP_OnsCP; } // touch - if(a == 3 || a == 4) { return WP_OnsCPAttack; } // attack - } - else - return WP_OnsCP; - - return WP_Null; -} - -void ons_ControlPoint_UpdateSprite(entity e) -{ - entity s1 = ons_ControlPoint_Waypoint(e); - WaypointSprite_UpdateSprites(e.sprite, s1, s1, s1); - - bool sh; - sh = !(ons_ControlPoint_CanBeLinked(e, NUM_TEAM_1) || ons_ControlPoint_CanBeLinked(e, NUM_TEAM_2) || ons_ControlPoint_CanBeLinked(e, NUM_TEAM_3) || ons_ControlPoint_CanBeLinked(e, NUM_TEAM_4)); - - if(e.lastteam != e.team + 2 || e.lastshielded != sh || e.iscaptured != e.lastcaptured) - { - if(e.iscaptured) // don't mess up build bars! - { - if(sh) - { - WaypointSprite_UpdateMaxHealth(e.sprite, 0); - } - else - { - WaypointSprite_UpdateMaxHealth(e.sprite, e.goalentity.max_health); - WaypointSprite_UpdateHealth(e.sprite, e.goalentity.health); - } - } - if(e.lastshielded) - { - if(e.team) - WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, 0.5 * colormapPaletteColor(e.team - 1, false)); - else - WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, '0.5 0.5 0.5'); - } - else - { - if(e.team) - WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, colormapPaletteColor(e.team - 1, false)); - else - WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, '0.75 0.75 0.75'); - } - WaypointSprite_Ping(e.sprite); - - e.lastteam = e.team + 2; - e.lastshielded = sh; - e.lastcaptured = e.iscaptured; - } -} - -void ons_ControlPoint_Touch(entity this, entity toucher) -{ - int attackable; - - if(IS_VEHICLE(toucher) && toucher.owner) - if(autocvar_g_onslaught_allow_vehicle_touch) - toucher = toucher.owner; - else - return; - - if(!IS_PLAYER(toucher)) { return; } - if(STAT(FROZEN, toucher)) { return; } - if(IS_DEAD(toucher)) { return; } - - if ( SAME_TEAM(this,toucher) ) - if ( this.iscaptured ) - { - if(time <= toucher.teleport_antispam) - Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_ONS_TELEPORT_ANTISPAM, rint(toucher.teleport_antispam - time)); - else - Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_ONS_TELEPORT); - } - - attackable = ons_ControlPoint_Attackable(this, toucher.team); - if(attackable != 2 && attackable != 4) - return; - // we've verified that this player has a legitimate claim to this point, - // so start building the captured point icon (which only captures this - // point if it successfully builds without being destroyed first) - ons_ControlPoint_Icon_Spawn(this, toucher); - - this.ons_toucher = toucher; - - onslaught_updatelinks(); -} - -void ons_ControlPoint_Think(entity this) -{ - this.nextthink = time + ONS_CP_THINKRATE; - CSQCMODEL_AUTOUPDATE(this); -} - -void ons_ControlPoint_Reset(entity this) -{ - if(this.goalentity) - delete(this.goalentity); - - this.goalentity = NULL; - this.team = 0; - this.colormap = 1024; - this.iscaptured = false; - this.islinked = false; - this.isshielded = true; - setthink(this, ons_ControlPoint_Think); - this.ons_toucher = NULL; - this.nextthink = time + ONS_CP_THINKRATE; - setmodel_fixsize(this, MDL_ONS_CP_PAD1); - - WaypointSprite_UpdateMaxHealth(this.sprite, 0); - WaypointSprite_UpdateRule(this.sprite,this.team,SPRITERULE_TEAMPLAY); - - onslaught_updatelinks(); - - SUB_UseTargets(this, this, NULL); // to reset the structures, playerspawns etc. - - CSQCMODEL_AUTOUPDATE(this); -} - -void ons_DelayedControlPoint_Setup(entity this) -{ - onslaught_updatelinks(); - - // captureshield setup - ons_CaptureShield_Spawn(this, false); - - CSQCMODEL_AUTOINIT(this); -} - -void ons_ControlPoint_Setup(entity cp) -{ - // main setup - cp.ons_worldcpnext = ons_worldcplist; // link control point into ons_worldcplist - ons_worldcplist = cp; - - cp.netname = "Control point"; - cp.team = 0; - cp.solid = SOLID_BBOX; - set_movetype(cp, MOVETYPE_NONE); - settouch(cp, ons_ControlPoint_Touch); - setthink(cp, ons_ControlPoint_Think); - cp.nextthink = time + ONS_CP_THINKRATE; - cp.reset = ons_ControlPoint_Reset; - cp.colormap = 1024; - cp.iscaptured = false; - cp.islinked = false; - cp.isshielded = true; - - if(cp.message == "") { cp.message = "a"; } - - // appearence - setmodel_fixsize(cp, MDL_ONS_CP_PAD1); - - // control point placement - if((cp.spawnflags & 1) || cp.noalign) // don't drop to floor, just stay at fixed location - { - cp.noalign = true; - set_movetype(cp, MOVETYPE_NONE); - } - else // drop to floor, automatically find a platform and set that as spawn origin - { - setorigin(cp, cp.origin + '0 0 20'); - cp.noalign = false; - droptofloor(cp); - set_movetype(cp, MOVETYPE_TOSS); - } - - // waypointsprites - WaypointSprite_SpawnFixed(WP_Null, cp.origin + CPGEN_WAYPOINT_OFFSET, cp, sprite, RADARICON_NONE); - WaypointSprite_UpdateRule(cp.sprite, cp.team, SPRITERULE_TEAMPLAY); - - InitializeEntity(cp, ons_DelayedControlPoint_Setup, INITPRIO_SETLOCATION); -} - - -// ========================= -// Main Generator Functions -// ========================= - -entity ons_Generator_Waypoint(entity e) -{ - if (e.isshielded) - return WP_OnsGenShielded; - return WP_OnsGen; -} - -void ons_Generator_UpdateSprite(entity e) -{ - entity s1 = ons_Generator_Waypoint(e); - WaypointSprite_UpdateSprites(e.sprite, s1, s1, s1); - - if(e.lastteam != e.team + 2 || e.lastshielded != e.isshielded) - { - e.lastteam = e.team + 2; - e.lastshielded = e.isshielded; - if(e.lastshielded) - { - if(e.team) - WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, 0.5 * colormapPaletteColor(e.team - 1, false)); - else - WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, '0.5 0.5 0.5'); - } - else - { - if(e.team) - WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, colormapPaletteColor(e.team - 1, false)); - else - WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, '0.75 0.75 0.75'); - } - WaypointSprite_Ping(e.sprite); - } -} - -void ons_GeneratorDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force) -{ - if(damage <= 0) { return; } - if(warmup_stage || gameover) { return; } - if(!round_handler_IsRoundStarted()) { return; } - - if (attacker != this) - { - if (this.isshielded) - { - // this is protected by a shield, so ignore the damage - if (time > this.pain_finished) - if (IS_PLAYER(attacker)) - { - play2(attacker, SND(ONS_DAMAGEBLOCKEDBYSHIELD)); - attacker.typehitsound += 1; - this.pain_finished = time + 1; - } - return; - } - if (time > this.pain_finished) - { - this.pain_finished = time + 10; - FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it) && SAME_TEAM(it, this), Send_Notification(NOTIF_ONE, it, MSG_CENTER, CENTER_GENERATOR_UNDERATTACK)); - play2team(this.team, SND(ONS_GENERATOR_UNDERATTACK)); - } - } - this.health = this.health - damage; - WaypointSprite_UpdateHealth(this.sprite, this.health); - // choose an animation frame based on health - this.frame = 10 * bound(0, (1 - this.health / this.max_health), 1); - // see if the generator is still functional, or dying - if (this.health > 0) - { - this.lasthealth = this.health; - } - else - { - if (attacker == this) - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(this.team, INFO_ONSLAUGHT_GENDESTROYED_OVERTIME)); - else - { - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(this.team, INFO_ONSLAUGHT_GENDESTROYED)); - PlayerScore_Add(attacker, SP_SCORE, 100); - } - this.iscaptured = false; - this.islinked = false; - this.isshielded = false; - this.takedamage = DAMAGE_NO; // can't be hurt anymore - this.event_damage = func_null; // won't do anything if hurt - this.count = 0; // reset counter - setthink(this, func_null); - this.nextthink = 0; - //this.think(); // do the first explosion now - - WaypointSprite_UpdateMaxHealth(this.sprite, 0); - WaypointSprite_Ping(this.sprite); - //WaypointSprite_Kill(this.sprite); // can't do this yet, code too poor - - onslaught_updatelinks(); - } - - // Throw some flaming gibs on damage, more damage = more chance for gib - if(random() < damage/220) - { - sound(this, CH_TRIGGER, SND_ROCKET_IMPACT, VOL_BASE, ATTEN_NORM); - } - else - { - // particles on every hit - Send_Effect(EFFECT_SPARKS, hitloc, force * -1, 1); - - //sound on every hit - if (random() < 0.5) - sound(this, CH_TRIGGER, SND_ONS_HIT1, VOL_BASE, ATTEN_NORM); - else - sound(this, CH_TRIGGER, SND_ONS_HIT2, VOL_BASE, ATTEN_NORM); - } - - this.SendFlags |= GSF_STATUS; -} - -void ons_GeneratorThink(entity this) -{ - this.nextthink = time + GEN_THINKRATE; - if (!gameover) - { - if(!this.isshielded && this.wait < time) - { - this.wait = time + 5; - FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), { - if(SAME_TEAM(it, this)) - { - Send_Notification(NOTIF_ONE, it, MSG_CENTER, CENTER_ONS_NOTSHIELDED_TEAM); - soundto(MSG_ONE, it, CHAN_AUTO, SND(KH_ALARM), VOL_BASE, ATTEN_NONE); // FIXME: unique sound? - } - else - Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_TEAM_NUM(this.team, CENTER_ONS_NOTSHIELDED)); - }); - } - } -} - -void ons_GeneratorReset(entity this) -{ - this.team = this.team_saved; - this.lasthealth = this.max_health = this.health = autocvar_g_onslaught_gen_health; - this.takedamage = DAMAGE_AIM; - this.bot_attack = true; - this.iscaptured = true; - this.islinked = true; - this.isshielded = true; - this.event_damage = ons_GeneratorDamage; - setthink(this, ons_GeneratorThink); - this.nextthink = time + GEN_THINKRATE; - - Net_LinkEntity(this, false, 0, generator_send); - - this.SendFlags = GSF_SETUP; // just incase - this.SendFlags |= GSF_STATUS; - - WaypointSprite_UpdateMaxHealth(this.sprite, this.max_health); - WaypointSprite_UpdateHealth(this.sprite, this.health); - WaypointSprite_UpdateRule(this.sprite,this.team,SPRITERULE_TEAMPLAY); - - onslaught_updatelinks(); -} - -void ons_DelayedGeneratorSetup(entity this) -{ - // bot waypoints - waypoint_spawnforitem_force(this, this.origin); - this.nearestwaypointtimeout = 0; // activate waypointing again - this.bot_basewaypoint = this.nearestwaypoint; - - // captureshield setup - ons_CaptureShield_Spawn(this, true); - - onslaught_updatelinks(); - - Net_LinkEntity(this, false, 0, generator_send); -} - - -void onslaught_generator_touch(entity this, entity toucher) -{ - if ( IS_PLAYER(toucher) ) - if ( SAME_TEAM(this,toucher) ) - if ( this.iscaptured ) - { - Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_ONS_TELEPORT); - } -} - -void ons_GeneratorSetup(entity gen) // called when spawning a generator entity on the map as a spawnfunc -{ - // declarations - int teamnumber = gen.team; - - // main setup - gen.ons_worldgeneratornext = ons_worldgeneratorlist; // link generator into ons_worldgeneratorlist - ons_worldgeneratorlist = gen; - - gen.netname = sprintf("%s generator", Team_ColoredFullName(teamnumber)); - gen.classname = "onslaught_generator"; - gen.solid = SOLID_BBOX; - gen.team_saved = teamnumber; - set_movetype(gen, MOVETYPE_NONE); - gen.lasthealth = gen.max_health = gen.health = autocvar_g_onslaught_gen_health; - gen.takedamage = DAMAGE_AIM; - gen.bot_attack = true; - gen.event_damage = ons_GeneratorDamage; - gen.reset = ons_GeneratorReset; - setthink(gen, ons_GeneratorThink); - gen.nextthink = time + GEN_THINKRATE; - gen.iscaptured = true; - gen.islinked = true; - gen.isshielded = true; - settouch(gen, onslaught_generator_touch); - - // appearence - // model handled by CSQC - setsize(gen, GENERATOR_MIN, GENERATOR_MAX); - setorigin(gen, (gen.origin + CPGEN_SPAWN_OFFSET)); - gen.colormap = 1024 + (teamnumber - 1) * 17; - - // generator placement - droptofloor(gen); - - // waypointsprites - WaypointSprite_SpawnFixed(WP_Null, gen.origin + CPGEN_WAYPOINT_OFFSET, gen, sprite, RADARICON_NONE); - WaypointSprite_UpdateRule(gen.sprite, gen.team, SPRITERULE_TEAMPLAY); - WaypointSprite_UpdateMaxHealth(gen.sprite, gen.max_health); - WaypointSprite_UpdateHealth(gen.sprite, gen.health); - - InitializeEntity(gen, ons_DelayedGeneratorSetup, INITPRIO_SETLOCATION); -} - - -// =============== -// Round Handler -// =============== - -int total_generators; -void Onslaught_count_generators() -{ - entity e; - total_generators = redowned = blueowned = yellowowned = pinkowned = 0; - for(e = ons_worldgeneratorlist; e; e = e.ons_worldgeneratornext) - { - ++total_generators; - redowned += (e.team == NUM_TEAM_1 && e.health > 0); - blueowned += (e.team == NUM_TEAM_2 && e.health > 0); - yellowowned += (e.team == NUM_TEAM_3 && e.health > 0); - pinkowned += (e.team == NUM_TEAM_4 && e.health > 0); - } -} - -int Onslaught_GetWinnerTeam() -{ - int winner_team = 0; - if(redowned > 0) - winner_team = NUM_TEAM_1; - if(blueowned > 0) - { - if(winner_team) return 0; - winner_team = NUM_TEAM_2; - } - if(yellowowned > 0) - { - if(winner_team) return 0; - winner_team = NUM_TEAM_3; - } - if(pinkowned > 0) - { - if(winner_team) return 0; - winner_team = NUM_TEAM_4; - } - if(winner_team) - return winner_team; - return -1; // no generators left? -} - -void nades_Clear(entity e); - -#define ONS_OWNED_GENERATORS() ((redowned > 0) + (blueowned > 0) + (yellowowned > 0) + (pinkowned > 0)) -#define ONS_OWNED_GENERATORS_OK() (ONS_OWNED_GENERATORS() > 1) -bool Onslaught_CheckWinner() -{ - if ((autocvar_timelimit && time > game_starttime + autocvar_timelimit * 60) || (round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)) - { - ons_stalemate = true; - - if (!wpforenemy_announced) - { - Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_OVERTIME_CONTROLPOINT); - sound(NULL, CH_INFO, SND_ONS_GENERATOR_DECAY, VOL_BASE, ATTEN_NONE); - - wpforenemy_announced = true; - } - - entity tmp_entity; // temporary entity - float d; - for(tmp_entity = ons_worldgeneratorlist; tmp_entity; tmp_entity = tmp_entity.ons_worldgeneratornext) if(time >= tmp_entity.ons_overtime_damagedelay) - { - // tmp_entity.max_health / 300 gives 5 minutes of overtime. - // control points reduce the overtime duration. - d = 1; - entity e; - for(e = ons_worldcplist; e; e = e.ons_worldcpnext) - { - if(DIFF_TEAM(e, tmp_entity)) - if(e.islinked) - d = d + 1; - } - - if(autocvar_g_campaign && autocvar__campaign_testrun) - d = d * tmp_entity.max_health; - else - d = d * tmp_entity.max_health / max(30, 60 * autocvar_timelimit_suddendeath); - - Damage(tmp_entity, tmp_entity, tmp_entity, d, DEATH_HURTTRIGGER.m_id, tmp_entity.origin, '0 0 0'); - - tmp_entity.sprite.SendFlags |= 16; - - tmp_entity.ons_overtime_damagedelay = time + 1; - } - } - else { wpforenemy_announced = false; ons_stalemate = false; } - - Onslaught_count_generators(); - - if(ONS_OWNED_GENERATORS_OK()) - return 0; - - int winner_team = Onslaught_GetWinnerTeam(); - - if(winner_team > 0) - { - Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN)); - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN)); - TeamScore_AddToTeam(winner_team, ST_ONS_CAPS, +1); - } - else if(winner_team == -1) - { - Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED); - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED); - } - - ons_stalemate = false; - - play2all(SND(CTF_CAPTURE(winner_team))); - - round_handler_Init(7, autocvar_g_onslaught_warmup, autocvar_g_onslaught_round_timelimit); - - FOREACH_CLIENT(IS_PLAYER(it), { - it.ons_roundlost = true; - it.player_blocked = true; - - nades_Clear(it); - }); - - return 1; -} - -bool Onslaught_CheckPlayers() -{ - return 1; -} - -void Onslaught_RoundStart() -{ - entity tmp_entity; - FOREACH_CLIENT(IS_PLAYER(it), it.player_blocked = false); - - for(tmp_entity = ons_worldcplist; tmp_entity; tmp_entity = tmp_entity.ons_worldcpnext) - tmp_entity.sprite.SendFlags |= 16; - - for(tmp_entity = ons_worldgeneratorlist; tmp_entity; tmp_entity = tmp_entity.ons_worldgeneratornext) - tmp_entity.sprite.SendFlags |= 16; -} - - -// ================ -// Bot player logic -// ================ - -// NOTE: LEGACY CODE, needs to be re-written! - -void havocbot_goalrating_ons_offenseitems(entity this, float ratingscale, vector org, float sradius) -{ - bool needarmor = false, needweapons = false; - - // Needs armor/health? - if(this.health<100) - needarmor = true; - - // Needs weapons? - int c = 0; - FOREACH(Weapons, it != WEP_Null, { - if(this.weapons & (it.m_wepset)) - if(++c >= 4) - break; - }); - - if(c<4) - needweapons = true; - - if(!needweapons && !needarmor) - return; - - LOG_DEBUG(this.netname, " needs weapons ", ftos(needweapons)); - LOG_DEBUG(this.netname, " needs armor ", ftos(needarmor)); - - // See what is around - FOREACH_ENTITY_FLOAT(bot_pickup, true, - { - // gather health and armor only - if (it.solid) - if ( ((it.health || it.armorvalue) && needarmor) || (it.weapons && needweapons ) ) - if (vdist(it.origin - org, <, sradius)) - { - int t = it.bot_pickupevalfunc(this, it); - if (t > 0) - navigation_routerating(this, it, t * ratingscale, 500); - } - }); -} - -void havocbot_role_ons_setrole(entity this, int role) -{ - LOG_DEBUG(this.netname," switched to "); - switch(role) - { - case HAVOCBOT_ONS_ROLE_DEFENSE: - LOG_DEBUG("defense"); - this.havocbot_role = havocbot_role_ons_defense; - this.havocbot_role_flags = HAVOCBOT_ONS_ROLE_DEFENSE; - this.havocbot_role_timeout = 0; - break; - case HAVOCBOT_ONS_ROLE_ASSISTANT: - LOG_DEBUG("assistant"); - this.havocbot_role = havocbot_role_ons_assistant; - this.havocbot_role_flags = HAVOCBOT_ONS_ROLE_ASSISTANT; - this.havocbot_role_timeout = 0; - break; - case HAVOCBOT_ONS_ROLE_OFFENSE: - LOG_DEBUG("offense"); - this.havocbot_role = havocbot_role_ons_offense; - this.havocbot_role_flags = HAVOCBOT_ONS_ROLE_OFFENSE; - this.havocbot_role_timeout = 0; - break; - } - LOG_DEBUG(""); -} - -void havocbot_goalrating_ons_controlpoints_attack(entity this, float ratingscale) -{ - entity cp, cp1, cp2, best, wp; - float radius, bestvalue; - int c; - bool found; - - // Filter control points - for(cp2 = ons_worldcplist; cp2; cp2 = cp2.ons_worldcpnext) - { - cp2.wpcost = c = 0; - cp2.wpconsidered = false; - - if(cp2.isshielded) - continue; - - // Ignore owned controlpoints - if(!(cp2.isgenneighbor[this.team] || cp2.iscpneighbor[this.team])) - continue; - - // Count team mates interested in this control point - // (easier and cleaner than keeping counters per cp and teams) - FOREACH_CLIENT(IS_PLAYER(it), { - if(SAME_TEAM(it, this)) - if(it.havocbot_role_flags & HAVOCBOT_ONS_ROLE_OFFENSE) - if(it.havocbot_ons_target == cp2) - ++c; - }); - - // NOTE: probably decrease the cost of attackable control points - cp2.wpcost = c; - cp2.wpconsidered = true; - } - - // We'll consider only the best case - bestvalue = 99999999999; - cp = NULL; - for(cp1 = ons_worldcplist; cp1; cp1 = cp1.ons_worldcpnext) - { - if (!cp1.wpconsidered) - continue; - - if(cp1.wpcost this.havocbot_role_timeout) - { - havocbot_ons_reset_role(this); - return; - } - - if(this.havocbot_attack_time>time) - return; - - if (this.bot_strategytime < time) - { - navigation_goalrating_start(this); - havocbot_goalrating_enemyplayers(this, 20000, this.origin, 650); - if(!havocbot_goalrating_ons_generator_attack(this, 20000)) - havocbot_goalrating_ons_controlpoints_attack(this, 20000); - havocbot_goalrating_ons_offenseitems(this, 10000, this.origin, 10000); - navigation_goalrating_end(this); - - this.bot_strategytime = time + autocvar_bot_ai_strategyinterval; - } -} - -void havocbot_role_ons_assistant(entity this) -{ - havocbot_ons_reset_role(this); -} - -void havocbot_role_ons_defense(entity this) -{ - havocbot_ons_reset_role(this); -} - -void havocbot_ons_reset_role(entity this) -{ - if(IS_DEAD(this)) - return; - - this.havocbot_ons_target = NULL; - - // TODO: Defend control points or generator if necessary - - havocbot_role_ons_setrole(this, HAVOCBOT_ONS_ROLE_OFFENSE); -} - - -/* - * Find control point or generator owned by the same team self which is nearest to pos - * if max_dist is positive, only control points within this range will be considered - */ -entity ons_Nearest_ControlPoint(entity this, vector pos, float max_dist) -{ - entity closest_target = NULL; - FOREACH_ENTITY_CLASS("onslaught_controlpoint", true, - { - if(SAME_TEAM(it, this)) - if(it.iscaptured) - if(max_dist <= 0 || vdist(it.origin - pos, <=, max_dist)) - if(vlen2(it.origin - pos) <= vlen2(closest_target.origin - pos) || closest_target == NULL) - closest_target = it; - }); - FOREACH_ENTITY_CLASS("onslaught_generator", true, - { - if(SAME_TEAM(it, this)) - if(max_dist <= 0 || vdist(it.origin - pos, <, max_dist)) - if(vlen2(it.origin - pos) <= vlen2(closest_target.origin - pos) || closest_target == NULL) - closest_target = it; - }); - - return closest_target; -} - -/* - * Find control point or generator owned by the same team self which is nearest to pos - * if max_dist is positive, only control points within this range will be considered - * This function only check distances on the XY plane, disregarding Z - */ -entity ons_Nearest_ControlPoint_2D(entity this, vector pos, float max_dist) -{ - entity closest_target = NULL; - vector delta; - float smallest_distance = 0, distance; - - FOREACH_ENTITY_CLASS("onslaught_controlpoint", true, - { - delta = it.origin - pos; - delta_z = 0; - distance = vlen(delta); - - if(SAME_TEAM(it, this)) - if(it.iscaptured) - if(max_dist <= 0 || distance <= max_dist) - if(closest_target == NULL || distance <= smallest_distance ) - { - closest_target = it; - smallest_distance = distance; - } - }); - FOREACH_ENTITY_CLASS("onslaught_generator", true, - { - delta = it.origin - pos; - delta_z = 0; - distance = vlen(delta); - - if(SAME_TEAM(it, this)) - if(max_dist <= 0 || distance <= max_dist) - if(closest_target == NULL || distance <= smallest_distance ) - { - closest_target = it; - smallest_distance = distance; - } - }); - - return closest_target; -} -/** - * find the number of control points and generators in the same team as this - */ -int ons_Count_SelfControlPoints(entity this) -{ - int n = 0; - FOREACH_ENTITY_CLASS("onslaught_controlpoint", true, - { - if(SAME_TEAM(it, this)) - if(it.iscaptured) - n++; - }); - FOREACH_ENTITY_CLASS("onslaught_generator", true, - { - if(SAME_TEAM(it, this)) - n++; - }); - return n; -} - -/** - * Teleport player to a random position near tele_target - * if tele_effects is true, teleport sound+particles are created - * return false on failure - */ -bool ons_Teleport(entity player, entity tele_target, float range, bool tele_effects) -{ - if ( !tele_target ) - return false; - - int i; - vector loc; - float theta; - // narrow the range for each iteration to increase chances that a spawnpoint - // can be found even if there's little room around the control point - float iteration_scale = 1; - for(i = 0; i < 16; ++i) - { - iteration_scale -= i / 16; - theta = random() * 2 * M_PI; - loc_y = sin(theta); - loc_x = cos(theta); - loc_z = 0; - loc *= random() * range * iteration_scale; - - loc += tele_target.origin + '0 0 128' * iteration_scale; - - tracebox(loc, STAT(PL_MIN, NULL), STAT(PL_MAX, NULL), loc, MOVE_NORMAL, player); - if(trace_fraction == 1.0 && !trace_startsolid) - { - traceline(tele_target.origin, loc, MOVE_NOMONSTERS, tele_target); // double check to make sure we're not spawning outside the NULL - if(trace_fraction == 1.0 && !trace_startsolid) - { - if ( tele_effects ) - { - Send_Effect(EFFECT_TELEPORT, player.origin, '0 0 0', 1); - sound (player, CH_TRIGGER, SND_TELEPORT, VOL_BASE, ATTEN_NORM); - } - setorigin(player, loc); - player.angles = '0 1 0' * ( theta * RAD2DEG + 180 ); - makevectors(player.angles); - player.fixangle = true; - player.teleport_antispam = time + autocvar_g_onslaught_teleport_wait; - - if ( tele_effects ) - Send_Effect(EFFECT_TELEPORT, player.origin + v_forward * 32, '0 0 0', 1); - return true; - } - } - } - - return false; -} - -// ============== -// Hook Functions -// ============== - -MUTATOR_HOOKFUNCTION(ons, reset_map_global) -{ - FOREACH_CLIENT(IS_PLAYER(it), { - it.ons_roundlost = false; - it.ons_deathloc = '0 0 0'; - PutClientInServer(it); - }); - return false; -} - -MUTATOR_HOOKFUNCTION(ons, ClientDisconnect) -{ - entity player = M_ARGV(0, entity); - - player.ons_deathloc = '0 0 0'; -} - -MUTATOR_HOOKFUNCTION(ons, MakePlayerObserver) -{ - entity player = M_ARGV(0, entity); - - player.ons_deathloc = '0 0 0'; -} - -MUTATOR_HOOKFUNCTION(ons, PlayerSpawn) -{ - entity player = M_ARGV(0, entity); - - if(!round_handler_IsRoundStarted()) - { - player.player_blocked = true; - return false; - } - - entity l; - for(l = ons_worldgeneratorlist; l; l = l.ons_worldgeneratornext) - { - l.sprite.SendFlags |= 16; - } - for(l = ons_worldcplist; l; l = l.ons_worldcpnext) - { - l.sprite.SendFlags |= 16; - } - - if(ons_stalemate) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_OVERTIME_CONTROLPOINT); } - - if ( autocvar_g_onslaught_spawn_choose ) - if ( player.ons_spawn_by ) - if ( ons_Teleport(player,player.ons_spawn_by,autocvar_g_onslaught_teleport_radius,false) ) - { - player.ons_spawn_by = NULL; - return false; - } - - if(autocvar_g_onslaught_spawn_at_controlpoints) - if(random() <= autocvar_g_onslaught_spawn_at_controlpoints_chance) - { - float random_target = autocvar_g_onslaught_spawn_at_controlpoints_random; - entity tmp_entity, closest_target = NULL; - vector spawn_loc = player.ons_deathloc; - - // new joining player or round reset, don't bother checking - if(spawn_loc == '0 0 0') { return false; } - - if(random_target) { RandomSelection_Init(); } - - for(tmp_entity = ons_worldcplist; tmp_entity; tmp_entity = tmp_entity.ons_worldcpnext) - { - if(SAME_TEAM(tmp_entity, player)) - if(random_target) - RandomSelection_Add(tmp_entity, 0, string_null, 1, 1); - else if(vlen2(tmp_entity.origin - spawn_loc) <= vlen2(closest_target.origin - spawn_loc) || closest_target == NULL) - closest_target = tmp_entity; - } - - if(random_target) { closest_target = RandomSelection_chosen_ent; } - - if(closest_target) - { - float i; - vector loc; - float iteration_scale = 1; - for(i = 0; i < 10; ++i) - { - iteration_scale -= i / 10; - loc = closest_target.origin + '0 0 96' * iteration_scale; - loc += ('0 1 0' * random()) * 128 * iteration_scale; - tracebox(loc, STAT(PL_MIN, NULL), STAT(PL_MAX, NULL), loc, MOVE_NORMAL, player); - if(trace_fraction == 1.0 && !trace_startsolid) - { - traceline(closest_target.origin, loc, MOVE_NOMONSTERS, closest_target); // double check to make sure we're not spawning outside the NULL - if(trace_fraction == 1.0 && !trace_startsolid) - { - setorigin(player, loc); - player.angles = normalize(loc - closest_target.origin) * RAD2DEG; - return false; - } - } - } - } - } - - if(autocvar_g_onslaught_spawn_at_generator) - if(random() <= autocvar_g_onslaught_spawn_at_generator_chance) - { - float random_target = autocvar_g_onslaught_spawn_at_generator_random; - entity tmp_entity, closest_target = NULL; - vector spawn_loc = player.ons_deathloc; - - // new joining player or round reset, don't bother checking - if(spawn_loc == '0 0 0') { return false; } - - if(random_target) { RandomSelection_Init(); } - - for(tmp_entity = ons_worldgeneratorlist; tmp_entity; tmp_entity = tmp_entity.ons_worldgeneratornext) - { - if(random_target) - RandomSelection_Add(tmp_entity, 0, string_null, 1, 1); - else - { - if(SAME_TEAM(tmp_entity, player)) - if(vlen2(tmp_entity.origin - spawn_loc) <= vlen2(closest_target.origin - spawn_loc) || closest_target == NULL) - closest_target = tmp_entity; - } - } - - if(random_target) { closest_target = RandomSelection_chosen_ent; } - - if(closest_target) - { - float i; - vector loc; - float iteration_scale = 1; - for(i = 0; i < 10; ++i) - { - iteration_scale -= i / 10; - loc = closest_target.origin + '0 0 128' * iteration_scale; - loc += ('0 1 0' * random()) * 256 * iteration_scale; - tracebox(loc, STAT(PL_MIN, NULL), STAT(PL_MAX, NULL), loc, MOVE_NORMAL, player); - if(trace_fraction == 1.0 && !trace_startsolid) - { - traceline(closest_target.origin, loc, MOVE_NOMONSTERS, closest_target); // double check to make sure we're not spawning outside the NULL - if(trace_fraction == 1.0 && !trace_startsolid) - { - setorigin(player, loc); - player.angles = normalize(loc - closest_target.origin) * RAD2DEG; - return false; - } - } - } - } - } - - return false; -} - -MUTATOR_HOOKFUNCTION(ons, PlayerDies) -{ - entity frag_target = M_ARGV(2, entity); - - frag_target.ons_deathloc = frag_target.origin; - entity l; - for(l = ons_worldgeneratorlist; l; l = l.ons_worldgeneratornext) - { - l.sprite.SendFlags |= 16; - } - for(l = ons_worldcplist; l; l = l.ons_worldcpnext) - { - l.sprite.SendFlags |= 16; - } - - if ( autocvar_g_onslaught_spawn_choose ) - if ( ons_Count_SelfControlPoints(frag_target) > 1 ) - stuffcmd(frag_target, "qc_cmd_cl hud clickradar\n"); - - return false; -} - -MUTATOR_HOOKFUNCTION(ons, MonsterMove) -{ - entity mon = M_ARGV(0, entity); - - entity e = find(NULL, targetname, mon.target); - if (e != NULL) - mon.team = e.team; -} - -void ons_MonsterSpawn_Delayed(entity this) -{ - entity own = this.owner; - - if(!own) { delete(this); return; } - - if(own.targetname) - { - entity e = find(NULL, target, own.targetname); - if(e != NULL) - { - own.team = e.team; - - own.use(own, e, NULL); - } - } - - delete(this); -} - -MUTATOR_HOOKFUNCTION(ons, MonsterSpawn) -{ - entity mon = M_ARGV(0, entity); - - entity e = spawn(); - e.owner = mon; - InitializeEntity(e, ons_MonsterSpawn_Delayed, INITPRIO_FINDTARGET); -} - -void ons_TurretSpawn_Delayed(entity this) -{ - entity own = this.owner; - - if(!own) { delete(this); return; } - - if(own.targetname) - { - entity e = find(NULL, target, own.targetname); - if(e != NULL) - { - own.team = e.team; - own.active = ACTIVE_NOT; - - own.use(own, e, NULL); - } - } - - delete(this); -} - -MUTATOR_HOOKFUNCTION(ons, TurretSpawn) -{ - entity turret = M_ARGV(0, entity); - - entity e = spawn(); - e.owner = turret; - InitializeEntity(e, ons_TurretSpawn_Delayed, INITPRIO_FINDTARGET); - - return false; -} - -MUTATOR_HOOKFUNCTION(ons, HavocBot_ChooseRole) -{ - entity bot = M_ARGV(0, entity); - - havocbot_ons_reset_role(bot); - return true; -} - -MUTATOR_HOOKFUNCTION(ons, GetTeamCount) -{ - // onslaught is special - for(entity tmp_entity = ons_worldgeneratorlist; tmp_entity; tmp_entity = tmp_entity.ons_worldgeneratornext) - { - switch(tmp_entity.team) - { - case NUM_TEAM_1: c1 = 0; break; - case NUM_TEAM_2: c2 = 0; break; - case NUM_TEAM_3: c3 = 0; break; - case NUM_TEAM_4: c4 = 0; break; - } - } - - return true; -} - -MUTATOR_HOOKFUNCTION(ons, SpectateCopy) -{ - entity spectatee = M_ARGV(0, entity); - entity client = M_ARGV(1, entity); - - client.ons_roundlost = spectatee.ons_roundlost; // make spectators see it too -} - -MUTATOR_HOOKFUNCTION(ons, SV_ParseClientCommand) -{ - if(MUTATOR_RETURNVALUE) // command was already handled? - return false; - - entity player = M_ARGV(0, entity); - string cmd_name = M_ARGV(1, string); - int cmd_argc = M_ARGV(2, int); - - if ( cmd_name == "ons_spawn" ) - { - vector pos = player.origin; - if(cmd_argc > 1) - pos_x = stof(argv(1)); - if(cmd_argc > 2) - pos_y = stof(argv(2)); - if(cmd_argc > 3) - pos_z = stof(argv(3)); - - if ( IS_PLAYER(player) ) - { - if ( !STAT(FROZEN, player) ) - { - entity source_point = ons_Nearest_ControlPoint(player, player.origin, autocvar_g_onslaught_teleport_radius); - - if ( !source_point && player.health > 0 ) - { - sprint(player, "\nYou need to be next to a control point\n"); - return true; - } - - - entity closest_target = ons_Nearest_ControlPoint_2D(player, pos, autocvar_g_onslaught_click_radius); - - if ( closest_target == NULL ) - { - sprint(player, "\nNo control point found\n"); - return true; - } - - if ( player.health <= 0 ) - { - player.ons_spawn_by = closest_target; - player.respawn_flags = player.respawn_flags | RESPAWN_FORCE; - } - else - { - if ( source_point == closest_target ) - { - sprint(player, "\nTeleporting to the same point\n"); - return true; - } - - if ( !ons_Teleport(player,closest_target,autocvar_g_onslaught_teleport_radius,true) ) - sprint(player, "\nUnable to teleport there\n"); - } - - return true; - } - - sprint(player, "\nNo teleportation for you\n"); - } - - return true; - } - return false; -} - -MUTATOR_HOOKFUNCTION(ons, PlayerUseKey) -{ - if(MUTATOR_RETURNVALUE || gameover) { return false; } - - entity player = M_ARGV(0, entity); - - if((time > player.teleport_antispam) && (!IS_DEAD(player)) && !player.vehicle) - { - entity source_point = ons_Nearest_ControlPoint(player, player.origin, autocvar_g_onslaught_teleport_radius); - if ( source_point ) - { - stuffcmd(player, "qc_cmd_cl hud clickradar\n"); - return true; - } - } -} - -MUTATOR_HOOKFUNCTION(ons, PlayHitsound) -{ - entity frag_victim = M_ARGV(0, entity); - - return (frag_victim.classname == "onslaught_generator" && !frag_victim.isshielded) - || (frag_victim.classname == "onslaught_controlpoint_icon" && !frag_victim.owner.isshielded); -} - -MUTATOR_HOOKFUNCTION(ons, SendWaypoint) -{ - entity wp = M_ARGV(0, entity); - entity to = M_ARGV(1, entity); - int sf = M_ARGV(2, int); - int wp_flag = M_ARGV(3, int); - - if(sf & 16) - { - if(wp.owner.classname == "onslaught_controlpoint") - { - entity wp_owner = wp.owner; - entity e = WaypointSprite_getviewentity(to); - if(SAME_TEAM(e, wp_owner) && wp_owner.goalentity.health >= wp_owner.goalentity.max_health) { wp_flag |= 2; } - if(!ons_ControlPoint_Attackable(wp_owner, e.team)) { wp_flag |= 2; } - } - if(wp.owner.classname == "onslaught_generator") - { - entity wp_owner = wp.owner; - if(wp_owner.isshielded && wp_owner.health >= wp_owner.max_health) { wp_flag |= 2; } - if(wp_owner.health <= 0) { wp_flag |= 2; } - } - } - - M_ARGV(3, int) = wp_flag; -} - -MUTATOR_HOOKFUNCTION(ons, TurretValidateTarget) -{ - entity turret_target = M_ARGV(1, entity); - - if(substring(turret_target.classname, 0, 10) == "onslaught_") // don't attack onslaught targets, that's the player's job! - { - M_ARGV(3, float) = -3; - return true; - } - - return false; -} - -MUTATOR_HOOKFUNCTION(ons, TurretThink) -{ - entity turret = M_ARGV(0, entity); - - // ONS uses somewhat backwards linking. - if(turret.target) - { - entity e = find(NULL, targetname, turret.target); - if (e != NULL) - turret.team = e.team; - } - - if(turret.team != turret.tur_head.team) - turret_respawn(turret); -} - - -// ========== -// Spawnfuncs -// ========== - -/*QUAKED spawnfunc_onslaught_link (0 .5 .8) (-16 -16 -16) (16 16 16) - Link between control points. - - This entity targets two different spawnfunc_onslaught_controlpoint or spawnfunc_onslaught_generator entities, and suppresses shielding on both if they are owned by different teams. - -keys: -"target" - first control point. -"target2" - second control point. - */ -spawnfunc(onslaught_link) -{ - if(!g_onslaught) { delete(this); return; } - - if (this.target == "" || this.target2 == "") - objerror(this, "target and target2 must be set\n"); - - this.ons_worldlinknext = ons_worldlinklist; // link into ons_worldlinklist - ons_worldlinklist = this; - - InitializeEntity(this, ons_DelayedLinkSetup, INITPRIO_FINDTARGET); - Net_LinkEntity(this, false, 0, ons_Link_Send); -} - -/*QUAKED spawnfunc_onslaught_controlpoint (0 .5 .8) (-32 -32 0) (32 32 128) - Control point. Be sure to give this enough clearance so that the shootable part has room to exist - - This should link to an spawnfunc_onslaught_controlpoint entity or spawnfunc_onslaught_generator entity. - -keys: -"targetname" - name that spawnfunc_onslaught_link entities will use to target this. -"target" - target any entities that are tied to this control point, such as vehicles and buildable structure entities. -"message" - name of this control point (should reflect the location in the map, such as "center bridge", "north tower", etc) - */ - -spawnfunc(onslaught_controlpoint) -{ - if(!g_onslaught) { delete(this); return; } - - ons_ControlPoint_Setup(this); -} - -/*QUAKED spawnfunc_onslaught_generator (0 .5 .8) (-32 -32 -24) (32 32 64) - Base generator. - - spawnfunc_onslaught_link entities can target this. - -keys: -"team" - team that owns this generator (5 = red, 14 = blue, etc), MUST BE SET. -"targetname" - name that spawnfunc_onslaught_link entities will use to target this. - */ -spawnfunc(onslaught_generator) -{ - if(!g_onslaught) { delete(this); return; } - if(!this.team) { objerror(this, "team must be set"); } - - ons_GeneratorSetup(this); -} - -// scoreboard setup -void ons_ScoreRules() -{ - CheckAllowedTeams(NULL); - int teams = 0; - if(c1 >= 0) teams |= BIT(0); - if(c2 >= 0) teams |= BIT(1); - if(c3 >= 0) teams |= BIT(2); - if(c4 >= 0) teams |= BIT(3); - ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true); - ScoreInfo_SetLabel_TeamScore (ST_ONS_CAPS, "destroyed", SFL_SORT_PRIO_PRIMARY); - ScoreInfo_SetLabel_PlayerScore(SP_ONS_CAPS, "caps", SFL_SORT_PRIO_SECONDARY); - ScoreInfo_SetLabel_PlayerScore(SP_ONS_TAKES, "takes", 0); - ScoreRules_basics_end(); -} - -void ons_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up -{ - ons_ScoreRules(); - - round_handler_Spawn(Onslaught_CheckPlayers, Onslaught_CheckWinner, Onslaught_RoundStart); - round_handler_Init(5, autocvar_g_onslaught_warmup, autocvar_g_onslaught_round_timelimit); -} - -void ons_Initialize() -{ - g_onslaught = true; - ons_captureshield_force = autocvar_g_onslaught_shield_force; - - InitializeEntity(NULL, ons_DelayedInit, INITPRIO_GAMETYPE); -} - -#endif +#include "onslaught.qh" diff --git a/qcsrc/common/gamemodes/gamemode/onslaught/onslaught.qh b/qcsrc/common/gamemodes/gamemode/onslaught/onslaught.qh new file mode 100644 index 0000000000..be9b8203c6 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/onslaught/onslaught.qh @@ -0,0 +1,4 @@ +#pragma once + +REGISTER_NET_LINKED(ENT_CLIENT_GENERATOR) +REGISTER_NET_LINKED(ENT_CLIENT_CONTROLPOINT_ICON) diff --git a/qcsrc/common/gamemodes/gamemode/onslaught/sv_controlpoint.qh b/qcsrc/common/gamemodes/gamemode/onslaught/sv_controlpoint.qh index d76f0ea069..d5437338e3 100644 --- a/qcsrc/common/gamemodes/gamemode/onslaught/sv_controlpoint.qh +++ b/qcsrc/common/gamemodes/gamemode/onslaught/sv_controlpoint.qh @@ -1,10 +1,7 @@ -#ifndef CONTROLPOINT_H -#define CONTROLPOINT_H +#pragma once const vector CPICON_MIN = '-32 -32 -9'; const vector CPICON_MAX = '32 32 25'; const int CPSF_STATUS = 4; const int CPSF_SETUP = 8; - -#endif diff --git a/qcsrc/common/gamemodes/gamemode/onslaught/sv_generator.qh b/qcsrc/common/gamemodes/gamemode/onslaught/sv_generator.qh index 003c2b1d68..8356689e20 100644 --- a/qcsrc/common/gamemodes/gamemode/onslaught/sv_generator.qh +++ b/qcsrc/common/gamemodes/gamemode/onslaught/sv_generator.qh @@ -1,5 +1,5 @@ -#ifndef GENERATOR_H -#define GENERATOR_H +#pragma once + const vector GENERATOR_MIN = '-52 -52 -14'; const vector GENERATOR_MAX = '52 52 75'; @@ -7,4 +7,3 @@ const int GSF_STATUS = 4; const int GSF_SETUP = 8; bool generator_send(entity this, entity to, int sf); -#endif diff --git a/qcsrc/common/gamemodes/gamemode/onslaught/sv_onslaught.qc b/qcsrc/common/gamemodes/gamemode/onslaught/sv_onslaught.qc new file mode 100644 index 0000000000..9551e9d85e --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/onslaught/sv_onslaught.qc @@ -0,0 +1,2128 @@ +#include "sv_controlpoint.qh" +#include "sv_generator.qh" + +bool g_onslaught; + +float autocvar_g_onslaught_teleport_wait; +bool autocvar_g_onslaught_spawn_at_controlpoints; +bool 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; +float autocvar_g_onslaught_spawn_at_controlpoints_chance = 0.5; +float autocvar_g_onslaught_spawn_at_controlpoints_random; +float autocvar_g_onslaught_spawn_at_generator_chance; +float autocvar_g_onslaught_spawn_at_generator_random; +float autocvar_g_onslaught_cp_buildhealth; +float autocvar_g_onslaught_cp_buildtime; +float autocvar_g_onslaught_cp_health; +float autocvar_g_onslaught_cp_regen; +float autocvar_g_onslaught_gen_health; +float autocvar_g_onslaught_shield_force = 100; +float autocvar_g_onslaught_allow_vehicle_touch; +float autocvar_g_onslaught_round_timelimit; +float autocvar_g_onslaught_warmup; +float autocvar_g_onslaught_teleport_radius; +float autocvar_g_onslaught_spawn_choose; +float autocvar_g_onslaught_click_radius; + +void FixSize(entity e); + +// ======================= +// CaptureShield Functions +// ======================= + +bool ons_CaptureShield_Customize(entity this, entity client) +{ + entity e = WaypointSprite_getviewentity(client); + + if(!this.enemy.isshielded && (ons_ControlPoint_Attackable(this.enemy, e.team) > 0 || this.enemy.classname != "onslaught_controlpoint")) { return false; } + if(SAME_TEAM(this, e)) { return false; } + + return true; +} + +void ons_CaptureShield_Touch(entity this, entity toucher) +{ + if(!this.enemy.isshielded && (ons_ControlPoint_Attackable(this.enemy, toucher.team) > 0 || this.enemy.classname != "onslaught_controlpoint")) { return; } + if(!IS_PLAYER(toucher)) { return; } + if(SAME_TEAM(toucher, this)) { return; } + + vector mymid = (this.absmin + this.absmax) * 0.5; + vector theirmid = (toucher.absmin + toucher.absmax) * 0.5; + + Damage(toucher, this, this, 0, DEATH_HURTTRIGGER.m_id, mymid, normalize(theirmid - mymid) * ons_captureshield_force); + + if(IS_REAL_CLIENT(toucher)) + { + play2(toucher, SND(ONS_DAMAGEBLOCKEDBYSHIELD)); + + if(this.enemy.classname == "onslaught_generator") + Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_ONS_GENERATOR_SHIELDED); + else + Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_ONS_CONTROLPOINT_SHIELDED); + } +} + +void ons_CaptureShield_Reset(entity this) +{ + this.colormap = this.enemy.colormap; + this.team = this.enemy.team; +} + +void ons_CaptureShield_Spawn(entity generator, bool is_generator) +{ + entity shield = new(ons_captureshield); + + shield.enemy = generator; + shield.team = generator.team; + shield.colormap = generator.colormap; + shield.reset = ons_CaptureShield_Reset; + settouch(shield, ons_CaptureShield_Touch); + setcefc(shield, ons_CaptureShield_Customize); + shield.effects = EF_ADDITIVE; + set_movetype(shield, 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); +} + + +// ========== +// Junk Pile +// ========== + +void setmodel_fixsize(entity e, Model m) +{ + setmodel(e, m); + FixSize(e); +} + +void onslaught_updatelinks() +{ + entity l; + // first check if the game has ended + LOG_DEBUG("--- updatelinks ---"); + // mark generators as being shielded and networked + for(l = ons_worldgeneratorlist; l; l = l.ons_worldgeneratornext) + { + if (l.iscaptured) + LOG_DEBUG(etos(l), " (generator) belongs to team ", ftos(l.team)); + else + LOG_DEBUG(etos(l), " (generator) is destroyed"); + l.islinked = l.iscaptured; + l.isshielded = l.iscaptured; + l.sprite.SendFlags |= 16; + } + // mark points as shielded and not networked + for(l = ons_worldcplist; l; l = l.ons_worldcpnext) + { + l.islinked = false; + l.isshielded = true; + int i; + for(i = 0; i < 17; ++i) { l.isgenneighbor[i] = false; l.iscpneighbor[i] = false; } + LOG_DEBUG(etos(l), " (point) belongs to team ", ftos(l.team)); + l.sprite.SendFlags |= 16; + } + // flow power outward from the generators through the network + bool stop = false; + while (!stop) + { + stop = true; + for(l = ons_worldlinklist; l; l = l.ons_worldlinknext) + { + // if both points are captured by the same team, and only one of + // them is powered, mark the other one as powered as well + if (l.enemy.iscaptured && l.goalentity.iscaptured) + if (l.enemy.islinked != l.goalentity.islinked) + if(SAME_TEAM(l.enemy, l.goalentity)) + { + if (!l.goalentity.islinked) + { + stop = false; + l.goalentity.islinked = true; + LOG_DEBUG(etos(l), " (link) is marking ", etos(l.goalentity), " (point) because its team matches ", etos(l.enemy), " (point)"); + } + else if (!l.enemy.islinked) + { + stop = false; + l.enemy.islinked = true; + LOG_DEBUG(etos(l), " (link) is marking ", etos(l.enemy), " (point) because its team matches ", etos(l.goalentity), " (point)"); + } + } + } + } + // now that we know which points are powered we can mark their neighbors + // as unshielded if team differs + for(l = ons_worldlinklist; l; l = l.ons_worldlinknext) + { + if (l.goalentity.islinked) + { + if(DIFF_TEAM(l.goalentity, l.enemy)) + { + LOG_DEBUG(etos(l), " (link) is unshielding ", etos(l.enemy), " (point) because its team does not match ", etos(l.goalentity), " (point)"); + l.enemy.isshielded = false; + } + if(l.goalentity.classname == "onslaught_generator") + l.enemy.isgenneighbor[l.goalentity.team] = true; + else + l.enemy.iscpneighbor[l.goalentity.team] = true; + } + if (l.enemy.islinked) + { + if(DIFF_TEAM(l.goalentity, l.enemy)) + { + LOG_DEBUG(etos(l), " (link) is unshielding ", etos(l.goalentity), " (point) because its team does not match ", etos(l.enemy), " (point)"); + l.goalentity.isshielded = false; + } + if(l.enemy.classname == "onslaught_generator") + l.goalentity.isgenneighbor[l.enemy.team] = true; + else + l.goalentity.iscpneighbor[l.enemy.team] = true; + } + } + // now update the generators + for(l = ons_worldgeneratorlist; l; l = l.ons_worldgeneratornext) + { + if (l.isshielded) + { + LOG_DEBUG(etos(l), " (generator) is shielded"); + l.takedamage = DAMAGE_NO; + l.bot_attack = false; + } + else + { + LOG_DEBUG(etos(l), " (generator) is not shielded"); + l.takedamage = DAMAGE_AIM; + l.bot_attack = true; + } + + ons_Generator_UpdateSprite(l); + } + // now update the takedamage and alpha variables on control point icons + for(l = ons_worldcplist; l; l = l.ons_worldcpnext) + { + if (l.isshielded) + { + LOG_DEBUG(etos(l), " (point) is shielded"); + if (l.goalentity) + { + l.goalentity.takedamage = DAMAGE_NO; + l.goalentity.bot_attack = false; + } + } + else + { + LOG_DEBUG(etos(l), " (point) is not shielded"); + if (l.goalentity) + { + l.goalentity.takedamage = DAMAGE_AIM; + l.goalentity.bot_attack = true; + } + } + ons_ControlPoint_UpdateSprite(l); + } + FOREACH_ENTITY_CLASS("ons_captureshield", true, + { + it.team = it.enemy.team; + it.colormap = it.enemy.colormap; + }); +} + + +// =================== +// Main Link Functions +// =================== + +bool ons_Link_Send(entity this, entity to, int sendflags) +{ + WriteHeader(MSG_ENTITY, ENT_CLIENT_RADARLINK); + WriteByte(MSG_ENTITY, sendflags); + if(sendflags & 1) + { + WriteCoord(MSG_ENTITY, this.goalentity.origin_x); + WriteCoord(MSG_ENTITY, this.goalentity.origin_y); + WriteCoord(MSG_ENTITY, this.goalentity.origin_z); + } + if(sendflags & 2) + { + WriteCoord(MSG_ENTITY, this.enemy.origin_x); + WriteCoord(MSG_ENTITY, this.enemy.origin_y); + WriteCoord(MSG_ENTITY, this.enemy.origin_z); + } + if(sendflags & 4) + { + WriteByte(MSG_ENTITY, this.clientcolors); // which is goalentity's color + enemy's color * 16 + } + return true; +} + +void ons_Link_CheckUpdate(entity this) +{ + // TODO check if the two sides have moved (currently they won't move anyway) + float cc = 0, cc1 = 0, cc2 = 0; + + if(this.goalentity.islinked || this.goalentity.iscaptured) { cc1 = (this.goalentity.team - 1) * 0x01; } + if(this.enemy.islinked || this.enemy.iscaptured) { cc2 = (this.enemy.team - 1) * 0x10; } + + cc = cc1 + cc2; + + if(cc != this.clientcolors) + { + this.clientcolors = cc; + this.SendFlags |= 4; + } + + this.nextthink = time; +} + +void ons_DelayedLinkSetup(entity this) +{ + this.goalentity = find(NULL, targetname, this.target); + this.enemy = find(NULL, targetname, this.target2); + if(!this.goalentity) { objerror(this, "can not find target\n"); } + if(!this.enemy) { objerror(this, "can not find target2\n"); } + + LOG_DEBUG(etos(this.goalentity), " linked with ", etos(this.enemy)); + this.SendFlags |= 3; + setthink(this, ons_Link_CheckUpdate); + this.nextthink = time; +} + + +// ============================= +// Main Control Point Functions +// ============================= + +int ons_ControlPoint_CanBeLinked(entity cp, int teamnumber) +{ + if(cp.isgenneighbor[teamnumber]) { return 2; } + if(cp.iscpneighbor[teamnumber]) { return 1; } + + return 0; +} + +int ons_ControlPoint_Attackable(entity cp, int teamnumber) + // -2: SAME TEAM, attackable by enemy! + // -1: SAME TEAM! + // 0: off limits + // 1: attack it + // 2: touch it + // 3: attack it (HIGH PRIO) + // 4: touch it (HIGH PRIO) +{ + int a; + + if(cp.isshielded) + { + return 0; + } + else if(cp.goalentity) + { + // if there's already an icon built, nothing happens + if(cp.team == teamnumber) + { + a = ons_ControlPoint_CanBeLinked(cp, teamnumber); + if(a) // attackable by enemy? + return -2; // EMERGENCY! + return -1; + } + // we know it can be linked, so no need to check + // but... + a = ons_ControlPoint_CanBeLinked(cp, teamnumber); + if(a == 2) // near our generator? + return 3; // EMERGENCY! + return 1; + } + else + { + // free point + if(ons_ControlPoint_CanBeLinked(cp, teamnumber)) + { + a = ons_ControlPoint_CanBeLinked(cp, teamnumber); // why was this here NUM_TEAM_1 + NUM_TEAM_2 - t + if(a == 2) + return 4; // GET THIS ONE NOW! + else + return 2; // TOUCH ME + } + } + return 0; +} + +void ons_ControlPoint_Icon_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force) +{ + if(damage <= 0) { return; } + + if (this.owner.isshielded) + { + // this is protected by a shield, so ignore the damage + if (time > this.pain_finished) + if (IS_PLAYER(attacker)) + { + play2(attacker, SND(ONS_DAMAGEBLOCKEDBYSHIELD)); + this.pain_finished = time + 1; + attacker.typehitsound += 1; // play both sounds (shield is way too quiet) + } + + return; + } + + if(IS_PLAYER(attacker)) + if(time - ons_notification_time[this.team] > 10) + { + play2team(this.team, SND(ONS_CONTROLPOINT_UNDERATTACK)); + ons_notification_time[this.team] = time; + } + + this.health = this.health - damage; + if(this.owner.iscaptured) + WaypointSprite_UpdateHealth(this.owner.sprite, this.health); + else + WaypointSprite_UpdateBuildFinished(this.owner.sprite, time + (this.max_health - this.health) / (this.count / ONS_CP_THINKRATE)); + this.pain_finished = time + 1; + // particles on every hit + pointparticles(EFFECT_SPARKS, hitloc, force*-1, 1); + //sound on every hit + if (random() < 0.5) + sound(this, CH_TRIGGER, SND_ONS_HIT1, VOL_BASE+0.3, ATTEN_NORM); + else + sound(this, CH_TRIGGER, SND_ONS_HIT2, VOL_BASE+0.3, ATTEN_NORM); + + if (this.health < 0) + { + sound(this, CH_TRIGGER, SND_GRENADE_IMPACT, VOL_BASE, ATTEN_NORM); + pointparticles(EFFECT_ROCKET_EXPLODE, this.origin, '0 0 0', 1); + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(this.team, INFO_ONSLAUGHT_CPDESTROYED), this.owner.message, attacker.netname); + + PlayerScore_Add(attacker, SP_ONS_TAKES, 1); + PlayerScore_Add(attacker, SP_SCORE, 10); + + this.owner.goalentity = NULL; + this.owner.islinked = false; + this.owner.iscaptured = false; + this.owner.team = 0; + this.owner.colormap = 1024; + + WaypointSprite_UpdateMaxHealth(this.owner.sprite, 0); + + onslaught_updatelinks(); + + // Use targets now (somebody make sure this is in the right place..) + SUB_UseTargets(this.owner, this, NULL); + + this.owner.waslinked = this.owner.islinked; + if(this.owner.model != "models/onslaught/controlpoint_pad.md3") + setmodel_fixsize(this.owner, MDL_ONS_CP_PAD1); + //setsize(this, '-32 -32 0', '32 32 8'); + + delete(this); + } + + this.SendFlags |= CPSF_STATUS; +} + +void ons_ControlPoint_Icon_Think(entity this) +{ + this.nextthink = time + ONS_CP_THINKRATE; + + if(autocvar_g_onslaught_cp_proxydecap) + { + int _enemy_count = 0; + int _friendly_count = 0; + + FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it), { + if(vdist(it.origin - this.origin, <, autocvar_g_onslaught_cp_proxydecap_distance)) + { + if(SAME_TEAM(it, this)) + ++_friendly_count; + else + ++_enemy_count; + } + }); + + _friendly_count = _friendly_count * (autocvar_g_onslaught_cp_proxydecap_dps * ONS_CP_THINKRATE); + _enemy_count = _enemy_count * (autocvar_g_onslaught_cp_proxydecap_dps * ONS_CP_THINKRATE); + + this.health = bound(0, this.health + (_friendly_count - _enemy_count), this.max_health); + this.SendFlags |= CPSF_STATUS; + if(this.health <= 0) + { + ons_ControlPoint_Icon_Damage(this, this, this, 1, 0, this.origin, '0 0 0'); + return; + } + } + + if (time > this.pain_finished + 5) + { + if(this.health < this.max_health) + { + this.health = this.health + this.count; + if (this.health >= this.max_health) + this.health = this.max_health; + WaypointSprite_UpdateHealth(this.owner.sprite, this.health); + } + } + + if(this.owner.islinked != this.owner.waslinked) + { + // unteam the spawnpoint if needed + int t = this.owner.team; + if(!this.owner.islinked) + this.owner.team = 0; + + SUB_UseTargets(this.owner, this, NULL); + + this.owner.team = t; + + this.owner.waslinked = this.owner.islinked; + } + + // damaged fx + if(random() < 0.6 - this.health / this.max_health) + { + Send_Effect(EFFECT_ELECTRIC_SPARKS, this.origin + randompos('-10 -10 -20', '10 10 20'), '0 0 0', 1); + + if(random() > 0.8) + sound(this, CH_PAIN, SND_ONS_SPARK1, VOL_BASE, ATTEN_NORM); + else if (random() > 0.5) + sound(this, CH_PAIN, SND_ONS_SPARK2, VOL_BASE, ATTEN_NORM); + } +} + +void ons_ControlPoint_Icon_BuildThink(entity this) +{ + int a; + + this.nextthink = time + ONS_CP_THINKRATE; + + // only do this if there is power + a = ons_ControlPoint_CanBeLinked(this.owner, this.owner.team); + if(!a) + return; + + this.health = this.health + this.count; + + this.SendFlags |= CPSF_STATUS; + + if (this.health >= this.max_health) + { + this.health = this.max_health; + this.count = autocvar_g_onslaught_cp_regen * ONS_CP_THINKRATE; // slow repair rate from now on + setthink(this, ons_ControlPoint_Icon_Think); + sound(this, CH_TRIGGER, SND_ONS_CONTROLPOINT_BUILT, VOL_BASE, ATTEN_NORM); + this.owner.iscaptured = true; + this.solid = SOLID_BBOX; + + Send_Effect(EFFECT_CAP(this.owner.team), this.owner.origin, '0 0 0', 1); + + WaypointSprite_UpdateMaxHealth(this.owner.sprite, this.max_health); + WaypointSprite_UpdateHealth(this.owner.sprite, this.health); + + if(IS_PLAYER(this.owner.ons_toucher)) + { + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ONSLAUGHT_CAPTURE, this.owner.ons_toucher.netname, this.owner.message); + Send_Notification(NOTIF_ALL_EXCEPT, this.owner.ons_toucher, MSG_CENTER, APP_TEAM_NUM(this.owner.ons_toucher.team, CENTER_ONS_CAPTURE), this.owner.message); + Send_Notification(NOTIF_ONE, this.owner.ons_toucher, MSG_CENTER, CENTER_ONS_CAPTURE, this.owner.message); + PlayerScore_Add(this.owner.ons_toucher, SP_ONS_CAPS, 1); + PlayerTeamScore_AddScore(this.owner.ons_toucher, 10); + } + + this.owner.ons_toucher = NULL; + + onslaught_updatelinks(); + + // Use targets now (somebody make sure this is in the right place..) + SUB_UseTargets(this.owner, this, NULL); + + this.SendFlags |= CPSF_SETUP; + } + if(this.owner.model != MDL_ONS_CP_PAD2.model_str()) + setmodel_fixsize(this.owner, MDL_ONS_CP_PAD2); + + if(random() < 0.9 - this.health / this.max_health) + Send_Effect(EFFECT_RAGE, this.origin + 10 * randomvec(), '0 0 -1', 1); +} + +void onslaught_controlpoint_icon_link(entity e, void(entity this) spawnproc); + +void ons_ControlPoint_Icon_Spawn(entity cp, entity player) +{ + entity e = new(onslaught_controlpoint_icon); + + setsize(e, CPICON_MIN, CPICON_MAX); + setorigin(e, cp.origin + CPICON_OFFSET); + + e.owner = cp; + e.max_health = autocvar_g_onslaught_cp_health; + e.health = autocvar_g_onslaught_cp_buildhealth; + e.solid = SOLID_NOT; + e.takedamage = DAMAGE_AIM; + e.bot_attack = true; + e.event_damage = ons_ControlPoint_Icon_Damage; + e.team = player.team; + e.colormap = 1024 + (e.team - 1) * 17; + e.count = (e.max_health - e.health) * ONS_CP_THINKRATE / autocvar_g_onslaught_cp_buildtime; // how long it takes to build + + sound(e, CH_TRIGGER, SND_ONS_CONTROLPOINT_BUILD, VOL_BASE, ATTEN_NORM); + + cp.goalentity = e; + cp.team = e.team; + cp.colormap = e.colormap; + + Send_Effect(EFFECT_FLAG_TOUCH(player.team), e.origin, '0 0 0', 1); + + WaypointSprite_UpdateBuildFinished(cp.sprite, time + (e.max_health - e.health) / (e.count / ONS_CP_THINKRATE)); + WaypointSprite_UpdateRule(cp.sprite,cp.team,SPRITERULE_TEAMPLAY); + cp.sprite.SendFlags |= 16; + + onslaught_controlpoint_icon_link(e, ons_ControlPoint_Icon_BuildThink); +} + +entity ons_ControlPoint_Waypoint(entity e) +{ + if(e.team) + { + int a = ons_ControlPoint_Attackable(e, e.team); + + if(a == -2) { return WP_OnsCPDefend; } // defend now + if(a == -1 || a == 1 || a == 2) { return WP_OnsCP; } // touch + if(a == 3 || a == 4) { return WP_OnsCPAttack; } // attack + } + else + return WP_OnsCP; + + return WP_Null; +} + +void ons_ControlPoint_UpdateSprite(entity e) +{ + entity s1 = ons_ControlPoint_Waypoint(e); + WaypointSprite_UpdateSprites(e.sprite, s1, s1, s1); + + bool sh; + sh = !(ons_ControlPoint_CanBeLinked(e, NUM_TEAM_1) || ons_ControlPoint_CanBeLinked(e, NUM_TEAM_2) || ons_ControlPoint_CanBeLinked(e, NUM_TEAM_3) || ons_ControlPoint_CanBeLinked(e, NUM_TEAM_4)); + + if(e.lastteam != e.team + 2 || e.lastshielded != sh || e.iscaptured != e.lastcaptured) + { + if(e.iscaptured) // don't mess up build bars! + { + if(sh) + { + WaypointSprite_UpdateMaxHealth(e.sprite, 0); + } + else + { + WaypointSprite_UpdateMaxHealth(e.sprite, e.goalentity.max_health); + WaypointSprite_UpdateHealth(e.sprite, e.goalentity.health); + } + } + if(e.lastshielded) + { + if(e.team) + WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, 0.5 * colormapPaletteColor(e.team - 1, false)); + else + WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, '0.5 0.5 0.5'); + } + else + { + if(e.team) + WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, colormapPaletteColor(e.team - 1, false)); + else + WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, '0.75 0.75 0.75'); + } + WaypointSprite_Ping(e.sprite); + + e.lastteam = e.team + 2; + e.lastshielded = sh; + e.lastcaptured = e.iscaptured; + } +} + +void ons_ControlPoint_Touch(entity this, entity toucher) +{ + int attackable; + + if(IS_VEHICLE(toucher) && toucher.owner) + if(autocvar_g_onslaught_allow_vehicle_touch) + toucher = toucher.owner; + else + return; + + if(!IS_PLAYER(toucher)) { return; } + if(STAT(FROZEN, toucher)) { return; } + if(IS_DEAD(toucher)) { return; } + + if ( SAME_TEAM(this,toucher) ) + if ( this.iscaptured ) + { + if(time <= toucher.teleport_antispam) + Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_ONS_TELEPORT_ANTISPAM, rint(toucher.teleport_antispam - time)); + else + Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_ONS_TELEPORT); + } + + attackable = ons_ControlPoint_Attackable(this, toucher.team); + if(attackable != 2 && attackable != 4) + return; + // we've verified that this player has a legitimate claim to this point, + // so start building the captured point icon (which only captures this + // point if it successfully builds without being destroyed first) + ons_ControlPoint_Icon_Spawn(this, toucher); + + this.ons_toucher = toucher; + + onslaught_updatelinks(); +} + +void ons_ControlPoint_Think(entity this) +{ + this.nextthink = time + ONS_CP_THINKRATE; + CSQCMODEL_AUTOUPDATE(this); +} + +void ons_ControlPoint_Reset(entity this) +{ + if(this.goalentity) + delete(this.goalentity); + + this.goalentity = NULL; + this.team = 0; + this.colormap = 1024; + this.iscaptured = false; + this.islinked = false; + this.isshielded = true; + setthink(this, ons_ControlPoint_Think); + this.ons_toucher = NULL; + this.nextthink = time + ONS_CP_THINKRATE; + setmodel_fixsize(this, MDL_ONS_CP_PAD1); + + WaypointSprite_UpdateMaxHealth(this.sprite, 0); + WaypointSprite_UpdateRule(this.sprite,this.team,SPRITERULE_TEAMPLAY); + + onslaught_updatelinks(); + + SUB_UseTargets(this, this, NULL); // to reset the structures, playerspawns etc. + + CSQCMODEL_AUTOUPDATE(this); +} + +void ons_DelayedControlPoint_Setup(entity this) +{ + onslaught_updatelinks(); + + // captureshield setup + ons_CaptureShield_Spawn(this, false); + + CSQCMODEL_AUTOINIT(this); +} + +void ons_ControlPoint_Setup(entity cp) +{ + // main setup + cp.ons_worldcpnext = ons_worldcplist; // link control point into ons_worldcplist + ons_worldcplist = cp; + + cp.netname = "Control point"; + cp.team = 0; + cp.solid = SOLID_BBOX; + set_movetype(cp, MOVETYPE_NONE); + settouch(cp, ons_ControlPoint_Touch); + setthink(cp, ons_ControlPoint_Think); + cp.nextthink = time + ONS_CP_THINKRATE; + cp.reset = ons_ControlPoint_Reset; + cp.colormap = 1024; + cp.iscaptured = false; + cp.islinked = false; + cp.isshielded = true; + + if(cp.message == "") { cp.message = "a"; } + + // appearence + setmodel_fixsize(cp, MDL_ONS_CP_PAD1); + + // control point placement + if((cp.spawnflags & 1) || cp.noalign) // don't drop to floor, just stay at fixed location + { + cp.noalign = true; + set_movetype(cp, MOVETYPE_NONE); + } + else // drop to floor, automatically find a platform and set that as spawn origin + { + setorigin(cp, cp.origin + '0 0 20'); + cp.noalign = false; + droptofloor(cp); + set_movetype(cp, MOVETYPE_TOSS); + } + + // waypointsprites + WaypointSprite_SpawnFixed(WP_Null, cp.origin + CPGEN_WAYPOINT_OFFSET, cp, sprite, RADARICON_NONE); + WaypointSprite_UpdateRule(cp.sprite, cp.team, SPRITERULE_TEAMPLAY); + + InitializeEntity(cp, ons_DelayedControlPoint_Setup, INITPRIO_SETLOCATION); +} + + +// ========================= +// Main Generator Functions +// ========================= + +entity ons_Generator_Waypoint(entity e) +{ + if (e.isshielded) + return WP_OnsGenShielded; + return WP_OnsGen; +} + +void ons_Generator_UpdateSprite(entity e) +{ + entity s1 = ons_Generator_Waypoint(e); + WaypointSprite_UpdateSprites(e.sprite, s1, s1, s1); + + if(e.lastteam != e.team + 2 || e.lastshielded != e.isshielded) + { + e.lastteam = e.team + 2; + e.lastshielded = e.isshielded; + if(e.lastshielded) + { + if(e.team) + WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, 0.5 * colormapPaletteColor(e.team - 1, false)); + else + WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, '0.5 0.5 0.5'); + } + else + { + if(e.team) + WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, colormapPaletteColor(e.team - 1, false)); + else + WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, '0.75 0.75 0.75'); + } + WaypointSprite_Ping(e.sprite); + } +} + +void ons_GeneratorDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force) +{ + if(damage <= 0) { return; } + if(warmup_stage || gameover) { return; } + if(!round_handler_IsRoundStarted()) { return; } + + if (attacker != this) + { + if (this.isshielded) + { + // this is protected by a shield, so ignore the damage + if (time > this.pain_finished) + if (IS_PLAYER(attacker)) + { + play2(attacker, SND(ONS_DAMAGEBLOCKEDBYSHIELD)); + attacker.typehitsound += 1; + this.pain_finished = time + 1; + } + return; + } + if (time > this.pain_finished) + { + this.pain_finished = time + 10; + FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it) && SAME_TEAM(it, this), Send_Notification(NOTIF_ONE, it, MSG_CENTER, CENTER_GENERATOR_UNDERATTACK)); + play2team(this.team, SND(ONS_GENERATOR_UNDERATTACK)); + } + } + this.health = this.health - damage; + WaypointSprite_UpdateHealth(this.sprite, this.health); + // choose an animation frame based on health + this.frame = 10 * bound(0, (1 - this.health / this.max_health), 1); + // see if the generator is still functional, or dying + if (this.health > 0) + { + this.lasthealth = this.health; + } + else + { + if (attacker == this) + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(this.team, INFO_ONSLAUGHT_GENDESTROYED_OVERTIME)); + else + { + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(this.team, INFO_ONSLAUGHT_GENDESTROYED)); + PlayerScore_Add(attacker, SP_SCORE, 100); + } + this.iscaptured = false; + this.islinked = false; + this.isshielded = false; + this.takedamage = DAMAGE_NO; // can't be hurt anymore + this.event_damage = func_null; // won't do anything if hurt + this.count = 0; // reset counter + setthink(this, func_null); + this.nextthink = 0; + //this.think(); // do the first explosion now + + WaypointSprite_UpdateMaxHealth(this.sprite, 0); + WaypointSprite_Ping(this.sprite); + //WaypointSprite_Kill(this.sprite); // can't do this yet, code too poor + + onslaught_updatelinks(); + } + + // Throw some flaming gibs on damage, more damage = more chance for gib + if(random() < damage/220) + { + sound(this, CH_TRIGGER, SND_ROCKET_IMPACT, VOL_BASE, ATTEN_NORM); + } + else + { + // particles on every hit + Send_Effect(EFFECT_SPARKS, hitloc, force * -1, 1); + + //sound on every hit + if (random() < 0.5) + sound(this, CH_TRIGGER, SND_ONS_HIT1, VOL_BASE, ATTEN_NORM); + else + sound(this, CH_TRIGGER, SND_ONS_HIT2, VOL_BASE, ATTEN_NORM); + } + + this.SendFlags |= GSF_STATUS; +} + +void ons_GeneratorThink(entity this) +{ + this.nextthink = time + GEN_THINKRATE; + if (!gameover) + { + if(!this.isshielded && this.wait < time) + { + this.wait = time + 5; + FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), { + if(SAME_TEAM(it, this)) + { + Send_Notification(NOTIF_ONE, it, MSG_CENTER, CENTER_ONS_NOTSHIELDED_TEAM); + soundto(MSG_ONE, it, CHAN_AUTO, SND(KH_ALARM), VOL_BASE, ATTEN_NONE); // FIXME: unique sound? + } + else + Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_TEAM_NUM(this.team, CENTER_ONS_NOTSHIELDED)); + }); + } + } +} + +void ons_GeneratorReset(entity this) +{ + this.team = this.team_saved; + this.lasthealth = this.max_health = this.health = autocvar_g_onslaught_gen_health; + this.takedamage = DAMAGE_AIM; + this.bot_attack = true; + this.iscaptured = true; + this.islinked = true; + this.isshielded = true; + this.event_damage = ons_GeneratorDamage; + setthink(this, ons_GeneratorThink); + this.nextthink = time + GEN_THINKRATE; + + Net_LinkEntity(this, false, 0, generator_send); + + this.SendFlags = GSF_SETUP; // just incase + this.SendFlags |= GSF_STATUS; + + WaypointSprite_UpdateMaxHealth(this.sprite, this.max_health); + WaypointSprite_UpdateHealth(this.sprite, this.health); + WaypointSprite_UpdateRule(this.sprite,this.team,SPRITERULE_TEAMPLAY); + + onslaught_updatelinks(); +} + +void ons_DelayedGeneratorSetup(entity this) +{ + // bot waypoints + waypoint_spawnforitem_force(this, this.origin); + this.nearestwaypointtimeout = 0; // activate waypointing again + this.bot_basewaypoint = this.nearestwaypoint; + + // captureshield setup + ons_CaptureShield_Spawn(this, true); + + onslaught_updatelinks(); + + Net_LinkEntity(this, false, 0, generator_send); +} + + +void onslaught_generator_touch(entity this, entity toucher) +{ + if ( IS_PLAYER(toucher) ) + if ( SAME_TEAM(this,toucher) ) + if ( this.iscaptured ) + { + Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_ONS_TELEPORT); + } +} + +void ons_GeneratorSetup(entity gen) // called when spawning a generator entity on the map as a spawnfunc +{ + // declarations + int teamnumber = gen.team; + + // main setup + gen.ons_worldgeneratornext = ons_worldgeneratorlist; // link generator into ons_worldgeneratorlist + ons_worldgeneratorlist = gen; + + gen.netname = sprintf("%s generator", Team_ColoredFullName(teamnumber)); + gen.classname = "onslaught_generator"; + gen.solid = SOLID_BBOX; + gen.team_saved = teamnumber; + set_movetype(gen, MOVETYPE_NONE); + gen.lasthealth = gen.max_health = gen.health = autocvar_g_onslaught_gen_health; + gen.takedamage = DAMAGE_AIM; + gen.bot_attack = true; + gen.event_damage = ons_GeneratorDamage; + gen.reset = ons_GeneratorReset; + setthink(gen, ons_GeneratorThink); + gen.nextthink = time + GEN_THINKRATE; + gen.iscaptured = true; + gen.islinked = true; + gen.isshielded = true; + settouch(gen, onslaught_generator_touch); + + // appearence + // model handled by CSQC + setsize(gen, GENERATOR_MIN, GENERATOR_MAX); + setorigin(gen, (gen.origin + CPGEN_SPAWN_OFFSET)); + gen.colormap = 1024 + (teamnumber - 1) * 17; + + // generator placement + droptofloor(gen); + + // waypointsprites + WaypointSprite_SpawnFixed(WP_Null, gen.origin + CPGEN_WAYPOINT_OFFSET, gen, sprite, RADARICON_NONE); + WaypointSprite_UpdateRule(gen.sprite, gen.team, SPRITERULE_TEAMPLAY); + WaypointSprite_UpdateMaxHealth(gen.sprite, gen.max_health); + WaypointSprite_UpdateHealth(gen.sprite, gen.health); + + InitializeEntity(gen, ons_DelayedGeneratorSetup, INITPRIO_SETLOCATION); +} + + +// =============== +// Round Handler +// =============== + +int total_generators; +void Onslaught_count_generators() +{ + entity e; + total_generators = redowned = blueowned = yellowowned = pinkowned = 0; + for(e = ons_worldgeneratorlist; e; e = e.ons_worldgeneratornext) + { + ++total_generators; + redowned += (e.team == NUM_TEAM_1 && e.health > 0); + blueowned += (e.team == NUM_TEAM_2 && e.health > 0); + yellowowned += (e.team == NUM_TEAM_3 && e.health > 0); + pinkowned += (e.team == NUM_TEAM_4 && e.health > 0); + } +} + +int Onslaught_GetWinnerTeam() +{ + int winner_team = 0; + if(redowned > 0) + winner_team = NUM_TEAM_1; + if(blueowned > 0) + { + if(winner_team) return 0; + winner_team = NUM_TEAM_2; + } + if(yellowowned > 0) + { + if(winner_team) return 0; + winner_team = NUM_TEAM_3; + } + if(pinkowned > 0) + { + if(winner_team) return 0; + winner_team = NUM_TEAM_4; + } + if(winner_team) + return winner_team; + return -1; // no generators left? +} + +void nades_Clear(entity e); + +#define ONS_OWNED_GENERATORS() ((redowned > 0) + (blueowned > 0) + (yellowowned > 0) + (pinkowned > 0)) +#define ONS_OWNED_GENERATORS_OK() (ONS_OWNED_GENERATORS() > 1) +bool Onslaught_CheckWinner() +{ + if ((autocvar_timelimit && time > game_starttime + autocvar_timelimit * 60) || (round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)) + { + ons_stalemate = true; + + if (!wpforenemy_announced) + { + Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_OVERTIME_CONTROLPOINT); + sound(NULL, CH_INFO, SND_ONS_GENERATOR_DECAY, VOL_BASE, ATTEN_NONE); + + wpforenemy_announced = true; + } + + entity tmp_entity; // temporary entity + float d; + for(tmp_entity = ons_worldgeneratorlist; tmp_entity; tmp_entity = tmp_entity.ons_worldgeneratornext) if(time >= tmp_entity.ons_overtime_damagedelay) + { + // tmp_entity.max_health / 300 gives 5 minutes of overtime. + // control points reduce the overtime duration. + d = 1; + entity e; + for(e = ons_worldcplist; e; e = e.ons_worldcpnext) + { + if(DIFF_TEAM(e, tmp_entity)) + if(e.islinked) + d = d + 1; + } + + if(autocvar_g_campaign && autocvar__campaign_testrun) + d = d * tmp_entity.max_health; + else + d = d * tmp_entity.max_health / max(30, 60 * autocvar_timelimit_suddendeath); + + Damage(tmp_entity, tmp_entity, tmp_entity, d, DEATH_HURTTRIGGER.m_id, tmp_entity.origin, '0 0 0'); + + tmp_entity.sprite.SendFlags |= 16; + + tmp_entity.ons_overtime_damagedelay = time + 1; + } + } + else { wpforenemy_announced = false; ons_stalemate = false; } + + Onslaught_count_generators(); + + if(ONS_OWNED_GENERATORS_OK()) + return 0; + + int winner_team = Onslaught_GetWinnerTeam(); + + if(winner_team > 0) + { + Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN)); + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN)); + TeamScore_AddToTeam(winner_team, ST_ONS_CAPS, +1); + } + else if(winner_team == -1) + { + Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED); + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED); + } + + ons_stalemate = false; + + play2all(SND(CTF_CAPTURE(winner_team))); + + round_handler_Init(7, autocvar_g_onslaught_warmup, autocvar_g_onslaught_round_timelimit); + + FOREACH_CLIENT(IS_PLAYER(it), { + it.ons_roundlost = true; + it.player_blocked = true; + + nades_Clear(it); + }); + + return 1; +} + +bool Onslaught_CheckPlayers() +{ + return 1; +} + +void Onslaught_RoundStart() +{ + entity tmp_entity; + FOREACH_CLIENT(IS_PLAYER(it), it.player_blocked = false); + + for(tmp_entity = ons_worldcplist; tmp_entity; tmp_entity = tmp_entity.ons_worldcpnext) + tmp_entity.sprite.SendFlags |= 16; + + for(tmp_entity = ons_worldgeneratorlist; tmp_entity; tmp_entity = tmp_entity.ons_worldgeneratornext) + tmp_entity.sprite.SendFlags |= 16; +} + + +// ================ +// Bot player logic +// ================ + +// NOTE: LEGACY CODE, needs to be re-written! + +void havocbot_goalrating_ons_offenseitems(entity this, float ratingscale, vector org, float sradius) +{ + bool needarmor = false, needweapons = false; + + // Needs armor/health? + if(this.health<100) + needarmor = true; + + // Needs weapons? + int c = 0; + FOREACH(Weapons, it != WEP_Null, { + if(this.weapons & (it.m_wepset)) + if(++c >= 4) + break; + }); + + if(c<4) + needweapons = true; + + if(!needweapons && !needarmor) + return; + + LOG_DEBUG(this.netname, " needs weapons ", ftos(needweapons)); + LOG_DEBUG(this.netname, " needs armor ", ftos(needarmor)); + + // See what is around + FOREACH_ENTITY_FLOAT(bot_pickup, true, + { + // gather health and armor only + if (it.solid) + if ( ((it.health || it.armorvalue) && needarmor) || (it.weapons && needweapons ) ) + if (vdist(it.origin - org, <, sradius)) + { + int t = it.bot_pickupevalfunc(this, it); + if (t > 0) + navigation_routerating(this, it, t * ratingscale, 500); + } + }); +} + +void havocbot_role_ons_setrole(entity this, int role) +{ + LOG_DEBUG(this.netname," switched to "); + switch(role) + { + case HAVOCBOT_ONS_ROLE_DEFENSE: + LOG_DEBUG("defense"); + this.havocbot_role = havocbot_role_ons_defense; + this.havocbot_role_flags = HAVOCBOT_ONS_ROLE_DEFENSE; + this.havocbot_role_timeout = 0; + break; + case HAVOCBOT_ONS_ROLE_ASSISTANT: + LOG_DEBUG("assistant"); + this.havocbot_role = havocbot_role_ons_assistant; + this.havocbot_role_flags = HAVOCBOT_ONS_ROLE_ASSISTANT; + this.havocbot_role_timeout = 0; + break; + case HAVOCBOT_ONS_ROLE_OFFENSE: + LOG_DEBUG("offense"); + this.havocbot_role = havocbot_role_ons_offense; + this.havocbot_role_flags = HAVOCBOT_ONS_ROLE_OFFENSE; + this.havocbot_role_timeout = 0; + break; + } + LOG_DEBUG(""); +} + +void havocbot_goalrating_ons_controlpoints_attack(entity this, float ratingscale) +{ + entity cp, cp1, cp2, best, wp; + float radius, bestvalue; + int c; + bool found; + + // Filter control points + for(cp2 = ons_worldcplist; cp2; cp2 = cp2.ons_worldcpnext) + { + cp2.wpcost = c = 0; + cp2.wpconsidered = false; + + if(cp2.isshielded) + continue; + + // Ignore owned controlpoints + if(!(cp2.isgenneighbor[this.team] || cp2.iscpneighbor[this.team])) + continue; + + // Count team mates interested in this control point + // (easier and cleaner than keeping counters per cp and teams) + FOREACH_CLIENT(IS_PLAYER(it), { + if(SAME_TEAM(it, this)) + if(it.havocbot_role_flags & HAVOCBOT_ONS_ROLE_OFFENSE) + if(it.havocbot_ons_target == cp2) + ++c; + }); + + // NOTE: probably decrease the cost of attackable control points + cp2.wpcost = c; + cp2.wpconsidered = true; + } + + // We'll consider only the best case + bestvalue = 99999999999; + cp = NULL; + for(cp1 = ons_worldcplist; cp1; cp1 = cp1.ons_worldcpnext) + { + if (!cp1.wpconsidered) + continue; + + if(cp1.wpcost this.havocbot_role_timeout) + { + havocbot_ons_reset_role(this); + return; + } + + if(this.havocbot_attack_time>time) + return; + + if (this.bot_strategytime < time) + { + navigation_goalrating_start(this); + havocbot_goalrating_enemyplayers(this, 20000, this.origin, 650); + if(!havocbot_goalrating_ons_generator_attack(this, 20000)) + havocbot_goalrating_ons_controlpoints_attack(this, 20000); + havocbot_goalrating_ons_offenseitems(this, 10000, this.origin, 10000); + navigation_goalrating_end(this); + + this.bot_strategytime = time + autocvar_bot_ai_strategyinterval; + } +} + +void havocbot_role_ons_assistant(entity this) +{ + havocbot_ons_reset_role(this); +} + +void havocbot_role_ons_defense(entity this) +{ + havocbot_ons_reset_role(this); +} + +void havocbot_ons_reset_role(entity this) +{ + if(IS_DEAD(this)) + return; + + this.havocbot_ons_target = NULL; + + // TODO: Defend control points or generator if necessary + + havocbot_role_ons_setrole(this, HAVOCBOT_ONS_ROLE_OFFENSE); +} + + +/* + * Find control point or generator owned by the same team self which is nearest to pos + * if max_dist is positive, only control points within this range will be considered + */ +entity ons_Nearest_ControlPoint(entity this, vector pos, float max_dist) +{ + entity closest_target = NULL; + FOREACH_ENTITY_CLASS("onslaught_controlpoint", true, + { + if(SAME_TEAM(it, this)) + if(it.iscaptured) + if(max_dist <= 0 || vdist(it.origin - pos, <=, max_dist)) + if(vlen2(it.origin - pos) <= vlen2(closest_target.origin - pos) || closest_target == NULL) + closest_target = it; + }); + FOREACH_ENTITY_CLASS("onslaught_generator", true, + { + if(SAME_TEAM(it, this)) + if(max_dist <= 0 || vdist(it.origin - pos, <, max_dist)) + if(vlen2(it.origin - pos) <= vlen2(closest_target.origin - pos) || closest_target == NULL) + closest_target = it; + }); + + return closest_target; +} + +/* + * Find control point or generator owned by the same team self which is nearest to pos + * if max_dist is positive, only control points within this range will be considered + * This function only check distances on the XY plane, disregarding Z + */ +entity ons_Nearest_ControlPoint_2D(entity this, vector pos, float max_dist) +{ + entity closest_target = NULL; + vector delta; + float smallest_distance = 0, distance; + + FOREACH_ENTITY_CLASS("onslaught_controlpoint", true, + { + delta = it.origin - pos; + delta_z = 0; + distance = vlen(delta); + + if(SAME_TEAM(it, this)) + if(it.iscaptured) + if(max_dist <= 0 || distance <= max_dist) + if(closest_target == NULL || distance <= smallest_distance ) + { + closest_target = it; + smallest_distance = distance; + } + }); + FOREACH_ENTITY_CLASS("onslaught_generator", true, + { + delta = it.origin - pos; + delta_z = 0; + distance = vlen(delta); + + if(SAME_TEAM(it, this)) + if(max_dist <= 0 || distance <= max_dist) + if(closest_target == NULL || distance <= smallest_distance ) + { + closest_target = it; + smallest_distance = distance; + } + }); + + return closest_target; +} +/** + * find the number of control points and generators in the same team as this + */ +int ons_Count_SelfControlPoints(entity this) +{ + int n = 0; + FOREACH_ENTITY_CLASS("onslaught_controlpoint", true, + { + if(SAME_TEAM(it, this)) + if(it.iscaptured) + n++; + }); + FOREACH_ENTITY_CLASS("onslaught_generator", true, + { + if(SAME_TEAM(it, this)) + n++; + }); + return n; +} + +/** + * Teleport player to a random position near tele_target + * if tele_effects is true, teleport sound+particles are created + * return false on failure + */ +bool ons_Teleport(entity player, entity tele_target, float range, bool tele_effects) +{ + if ( !tele_target ) + return false; + + int i; + vector loc; + float theta; + // narrow the range for each iteration to increase chances that a spawnpoint + // can be found even if there's little room around the control point + float iteration_scale = 1; + for(i = 0; i < 16; ++i) + { + iteration_scale -= i / 16; + theta = random() * 2 * M_PI; + loc_y = sin(theta); + loc_x = cos(theta); + loc_z = 0; + loc *= random() * range * iteration_scale; + + loc += tele_target.origin + '0 0 128' * iteration_scale; + + tracebox(loc, STAT(PL_MIN, NULL), STAT(PL_MAX, NULL), loc, MOVE_NORMAL, player); + if(trace_fraction == 1.0 && !trace_startsolid) + { + traceline(tele_target.origin, loc, MOVE_NOMONSTERS, tele_target); // double check to make sure we're not spawning outside the NULL + if(trace_fraction == 1.0 && !trace_startsolid) + { + if ( tele_effects ) + { + Send_Effect(EFFECT_TELEPORT, player.origin, '0 0 0', 1); + sound (player, CH_TRIGGER, SND_TELEPORT, VOL_BASE, ATTEN_NORM); + } + setorigin(player, loc); + player.angles = '0 1 0' * ( theta * RAD2DEG + 180 ); + makevectors(player.angles); + player.fixangle = true; + player.teleport_antispam = time + autocvar_g_onslaught_teleport_wait; + + if ( tele_effects ) + Send_Effect(EFFECT_TELEPORT, player.origin + v_forward * 32, '0 0 0', 1); + return true; + } + } + } + + return false; +} + +// ============== +// Hook Functions +// ============== + +MUTATOR_HOOKFUNCTION(ons, reset_map_global) +{ + FOREACH_CLIENT(IS_PLAYER(it), { + it.ons_roundlost = false; + it.ons_deathloc = '0 0 0'; + PutClientInServer(it); + }); + return false; +} + +MUTATOR_HOOKFUNCTION(ons, ClientDisconnect) +{ + entity player = M_ARGV(0, entity); + + player.ons_deathloc = '0 0 0'; +} + +MUTATOR_HOOKFUNCTION(ons, MakePlayerObserver) +{ + entity player = M_ARGV(0, entity); + + player.ons_deathloc = '0 0 0'; +} + +MUTATOR_HOOKFUNCTION(ons, PlayerSpawn) +{ + entity player = M_ARGV(0, entity); + + if(!round_handler_IsRoundStarted()) + { + player.player_blocked = true; + return false; + } + + entity l; + for(l = ons_worldgeneratorlist; l; l = l.ons_worldgeneratornext) + { + l.sprite.SendFlags |= 16; + } + for(l = ons_worldcplist; l; l = l.ons_worldcpnext) + { + l.sprite.SendFlags |= 16; + } + + if(ons_stalemate) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_OVERTIME_CONTROLPOINT); } + + if ( autocvar_g_onslaught_spawn_choose ) + if ( player.ons_spawn_by ) + if ( ons_Teleport(player,player.ons_spawn_by,autocvar_g_onslaught_teleport_radius,false) ) + { + player.ons_spawn_by = NULL; + return false; + } + + if(autocvar_g_onslaught_spawn_at_controlpoints) + if(random() <= autocvar_g_onslaught_spawn_at_controlpoints_chance) + { + float random_target = autocvar_g_onslaught_spawn_at_controlpoints_random; + entity tmp_entity, closest_target = NULL; + vector spawn_loc = player.ons_deathloc; + + // new joining player or round reset, don't bother checking + if(spawn_loc == '0 0 0') { return false; } + + if(random_target) { RandomSelection_Init(); } + + for(tmp_entity = ons_worldcplist; tmp_entity; tmp_entity = tmp_entity.ons_worldcpnext) + { + if(SAME_TEAM(tmp_entity, player)) + if(random_target) + RandomSelection_Add(tmp_entity, 0, string_null, 1, 1); + else if(vlen2(tmp_entity.origin - spawn_loc) <= vlen2(closest_target.origin - spawn_loc) || closest_target == NULL) + closest_target = tmp_entity; + } + + if(random_target) { closest_target = RandomSelection_chosen_ent; } + + if(closest_target) + { + float i; + vector loc; + float iteration_scale = 1; + for(i = 0; i < 10; ++i) + { + iteration_scale -= i / 10; + loc = closest_target.origin + '0 0 96' * iteration_scale; + loc += ('0 1 0' * random()) * 128 * iteration_scale; + tracebox(loc, STAT(PL_MIN, NULL), STAT(PL_MAX, NULL), loc, MOVE_NORMAL, player); + if(trace_fraction == 1.0 && !trace_startsolid) + { + traceline(closest_target.origin, loc, MOVE_NOMONSTERS, closest_target); // double check to make sure we're not spawning outside the NULL + if(trace_fraction == 1.0 && !trace_startsolid) + { + setorigin(player, loc); + player.angles = normalize(loc - closest_target.origin) * RAD2DEG; + return false; + } + } + } + } + } + + if(autocvar_g_onslaught_spawn_at_generator) + if(random() <= autocvar_g_onslaught_spawn_at_generator_chance) + { + float random_target = autocvar_g_onslaught_spawn_at_generator_random; + entity tmp_entity, closest_target = NULL; + vector spawn_loc = player.ons_deathloc; + + // new joining player or round reset, don't bother checking + if(spawn_loc == '0 0 0') { return false; } + + if(random_target) { RandomSelection_Init(); } + + for(tmp_entity = ons_worldgeneratorlist; tmp_entity; tmp_entity = tmp_entity.ons_worldgeneratornext) + { + if(random_target) + RandomSelection_Add(tmp_entity, 0, string_null, 1, 1); + else + { + if(SAME_TEAM(tmp_entity, player)) + if(vlen2(tmp_entity.origin - spawn_loc) <= vlen2(closest_target.origin - spawn_loc) || closest_target == NULL) + closest_target = tmp_entity; + } + } + + if(random_target) { closest_target = RandomSelection_chosen_ent; } + + if(closest_target) + { + float i; + vector loc; + float iteration_scale = 1; + for(i = 0; i < 10; ++i) + { + iteration_scale -= i / 10; + loc = closest_target.origin + '0 0 128' * iteration_scale; + loc += ('0 1 0' * random()) * 256 * iteration_scale; + tracebox(loc, STAT(PL_MIN, NULL), STAT(PL_MAX, NULL), loc, MOVE_NORMAL, player); + if(trace_fraction == 1.0 && !trace_startsolid) + { + traceline(closest_target.origin, loc, MOVE_NOMONSTERS, closest_target); // double check to make sure we're not spawning outside the NULL + if(trace_fraction == 1.0 && !trace_startsolid) + { + setorigin(player, loc); + player.angles = normalize(loc - closest_target.origin) * RAD2DEG; + return false; + } + } + } + } + } + + return false; +} + +MUTATOR_HOOKFUNCTION(ons, PlayerDies) +{ + entity frag_target = M_ARGV(2, entity); + + frag_target.ons_deathloc = frag_target.origin; + entity l; + for(l = ons_worldgeneratorlist; l; l = l.ons_worldgeneratornext) + { + l.sprite.SendFlags |= 16; + } + for(l = ons_worldcplist; l; l = l.ons_worldcpnext) + { + l.sprite.SendFlags |= 16; + } + + if ( autocvar_g_onslaught_spawn_choose ) + if ( ons_Count_SelfControlPoints(frag_target) > 1 ) + stuffcmd(frag_target, "qc_cmd_cl hud clickradar\n"); + + return false; +} + +MUTATOR_HOOKFUNCTION(ons, MonsterMove) +{ + entity mon = M_ARGV(0, entity); + + entity e = find(NULL, targetname, mon.target); + if (e != NULL) + mon.team = e.team; +} + +void ons_MonsterSpawn_Delayed(entity this) +{ + entity own = this.owner; + + if(!own) { delete(this); return; } + + if(own.targetname) + { + entity e = find(NULL, target, own.targetname); + if(e != NULL) + { + own.team = e.team; + + own.use(own, e, NULL); + } + } + + delete(this); +} + +MUTATOR_HOOKFUNCTION(ons, MonsterSpawn) +{ + entity mon = M_ARGV(0, entity); + + entity e = spawn(); + e.owner = mon; + InitializeEntity(e, ons_MonsterSpawn_Delayed, INITPRIO_FINDTARGET); +} + +void ons_TurretSpawn_Delayed(entity this) +{ + entity own = this.owner; + + if(!own) { delete(this); return; } + + if(own.targetname) + { + entity e = find(NULL, target, own.targetname); + if(e != NULL) + { + own.team = e.team; + own.active = ACTIVE_NOT; + + own.use(own, e, NULL); + } + } + + delete(this); +} + +MUTATOR_HOOKFUNCTION(ons, TurretSpawn) +{ + entity turret = M_ARGV(0, entity); + + entity e = spawn(); + e.owner = turret; + InitializeEntity(e, ons_TurretSpawn_Delayed, INITPRIO_FINDTARGET); + + return false; +} + +MUTATOR_HOOKFUNCTION(ons, HavocBot_ChooseRole) +{ + entity bot = M_ARGV(0, entity); + + havocbot_ons_reset_role(bot); + return true; +} + +MUTATOR_HOOKFUNCTION(ons, GetTeamCount) +{ + // onslaught is special + for(entity tmp_entity = ons_worldgeneratorlist; tmp_entity; tmp_entity = tmp_entity.ons_worldgeneratornext) + { + switch(tmp_entity.team) + { + case NUM_TEAM_1: c1 = 0; break; + case NUM_TEAM_2: c2 = 0; break; + case NUM_TEAM_3: c3 = 0; break; + case NUM_TEAM_4: c4 = 0; break; + } + } + + return true; +} + +MUTATOR_HOOKFUNCTION(ons, SpectateCopy) +{ + entity spectatee = M_ARGV(0, entity); + entity client = M_ARGV(1, entity); + + client.ons_roundlost = spectatee.ons_roundlost; // make spectators see it too +} + +MUTATOR_HOOKFUNCTION(ons, SV_ParseClientCommand) +{ + if(MUTATOR_RETURNVALUE) // command was already handled? + return false; + + entity player = M_ARGV(0, entity); + string cmd_name = M_ARGV(1, string); + int cmd_argc = M_ARGV(2, int); + + if ( cmd_name == "ons_spawn" ) + { + vector pos = player.origin; + if(cmd_argc > 1) + pos_x = stof(argv(1)); + if(cmd_argc > 2) + pos_y = stof(argv(2)); + if(cmd_argc > 3) + pos_z = stof(argv(3)); + + if ( IS_PLAYER(player) ) + { + if ( !STAT(FROZEN, player) ) + { + entity source_point = ons_Nearest_ControlPoint(player, player.origin, autocvar_g_onslaught_teleport_radius); + + if ( !source_point && player.health > 0 ) + { + sprint(player, "\nYou need to be next to a control point\n"); + return true; + } + + + entity closest_target = ons_Nearest_ControlPoint_2D(player, pos, autocvar_g_onslaught_click_radius); + + if ( closest_target == NULL ) + { + sprint(player, "\nNo control point found\n"); + return true; + } + + if ( player.health <= 0 ) + { + player.ons_spawn_by = closest_target; + player.respawn_flags = player.respawn_flags | RESPAWN_FORCE; + } + else + { + if ( source_point == closest_target ) + { + sprint(player, "\nTeleporting to the same point\n"); + return true; + } + + if ( !ons_Teleport(player,closest_target,autocvar_g_onslaught_teleport_radius,true) ) + sprint(player, "\nUnable to teleport there\n"); + } + + return true; + } + + sprint(player, "\nNo teleportation for you\n"); + } + + return true; + } + return false; +} + +MUTATOR_HOOKFUNCTION(ons, PlayerUseKey) +{ + if(MUTATOR_RETURNVALUE || gameover) { return false; } + + entity player = M_ARGV(0, entity); + + if((time > player.teleport_antispam) && (!IS_DEAD(player)) && !player.vehicle) + { + entity source_point = ons_Nearest_ControlPoint(player, player.origin, autocvar_g_onslaught_teleport_radius); + if ( source_point ) + { + stuffcmd(player, "qc_cmd_cl hud clickradar\n"); + return true; + } + } +} + +MUTATOR_HOOKFUNCTION(ons, PlayHitsound) +{ + entity frag_victim = M_ARGV(0, entity); + + return (frag_victim.classname == "onslaught_generator" && !frag_victim.isshielded) + || (frag_victim.classname == "onslaught_controlpoint_icon" && !frag_victim.owner.isshielded); +} + +MUTATOR_HOOKFUNCTION(ons, SendWaypoint) +{ + entity wp = M_ARGV(0, entity); + entity to = M_ARGV(1, entity); + int sf = M_ARGV(2, int); + int wp_flag = M_ARGV(3, int); + + if(sf & 16) + { + if(wp.owner.classname == "onslaught_controlpoint") + { + entity wp_owner = wp.owner; + entity e = WaypointSprite_getviewentity(to); + if(SAME_TEAM(e, wp_owner) && wp_owner.goalentity.health >= wp_owner.goalentity.max_health) { wp_flag |= 2; } + if(!ons_ControlPoint_Attackable(wp_owner, e.team)) { wp_flag |= 2; } + } + if(wp.owner.classname == "onslaught_generator") + { + entity wp_owner = wp.owner; + if(wp_owner.isshielded && wp_owner.health >= wp_owner.max_health) { wp_flag |= 2; } + if(wp_owner.health <= 0) { wp_flag |= 2; } + } + } + + M_ARGV(3, int) = wp_flag; +} + +MUTATOR_HOOKFUNCTION(ons, TurretValidateTarget) +{ + entity turret_target = M_ARGV(1, entity); + + if(substring(turret_target.classname, 0, 10) == "onslaught_") // don't attack onslaught targets, that's the player's job! + { + M_ARGV(3, float) = -3; + return true; + } + + return false; +} + +MUTATOR_HOOKFUNCTION(ons, TurretThink) +{ + entity turret = M_ARGV(0, entity); + + // ONS uses somewhat backwards linking. + if(turret.target) + { + entity e = find(NULL, targetname, turret.target); + if (e != NULL) + turret.team = e.team; + } + + if(turret.team != turret.tur_head.team) + turret_respawn(turret); +} + + +// ========== +// Spawnfuncs +// ========== + +/*QUAKED spawnfunc_onslaught_link (0 .5 .8) (-16 -16 -16) (16 16 16) + Link between control points. + + This entity targets two different spawnfunc_onslaught_controlpoint or spawnfunc_onslaught_generator entities, and suppresses shielding on both if they are owned by different teams. + +keys: +"target" - first control point. +"target2" - second control point. + */ +spawnfunc(onslaught_link) +{ + if(!g_onslaught) { delete(this); return; } + + if (this.target == "" || this.target2 == "") + objerror(this, "target and target2 must be set\n"); + + this.ons_worldlinknext = ons_worldlinklist; // link into ons_worldlinklist + ons_worldlinklist = this; + + InitializeEntity(this, ons_DelayedLinkSetup, INITPRIO_FINDTARGET); + Net_LinkEntity(this, false, 0, ons_Link_Send); +} + +/*QUAKED spawnfunc_onslaught_controlpoint (0 .5 .8) (-32 -32 0) (32 32 128) + Control point. Be sure to give this enough clearance so that the shootable part has room to exist + + This should link to an spawnfunc_onslaught_controlpoint entity or spawnfunc_onslaught_generator entity. + +keys: +"targetname" - name that spawnfunc_onslaught_link entities will use to target this. +"target" - target any entities that are tied to this control point, such as vehicles and buildable structure entities. +"message" - name of this control point (should reflect the location in the map, such as "center bridge", "north tower", etc) + */ + +spawnfunc(onslaught_controlpoint) +{ + if(!g_onslaught) { delete(this); return; } + + ons_ControlPoint_Setup(this); +} + +/*QUAKED spawnfunc_onslaught_generator (0 .5 .8) (-32 -32 -24) (32 32 64) + Base generator. + + spawnfunc_onslaught_link entities can target this. + +keys: +"team" - team that owns this generator (5 = red, 14 = blue, etc), MUST BE SET. +"targetname" - name that spawnfunc_onslaught_link entities will use to target this. + */ +spawnfunc(onslaught_generator) +{ + if(!g_onslaught) { delete(this); return; } + if(!this.team) { objerror(this, "team must be set"); } + + ons_GeneratorSetup(this); +} + +// scoreboard setup +void ons_ScoreRules() +{ + CheckAllowedTeams(NULL); + int teams = 0; + if(c1 >= 0) teams |= BIT(0); + if(c2 >= 0) teams |= BIT(1); + if(c3 >= 0) teams |= BIT(2); + if(c4 >= 0) teams |= BIT(3); + ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true); + ScoreInfo_SetLabel_TeamScore (ST_ONS_CAPS, "destroyed", SFL_SORT_PRIO_PRIMARY); + ScoreInfo_SetLabel_PlayerScore(SP_ONS_CAPS, "caps", SFL_SORT_PRIO_SECONDARY); + ScoreInfo_SetLabel_PlayerScore(SP_ONS_TAKES, "takes", 0); + ScoreRules_basics_end(); +} + +void ons_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up +{ + ons_ScoreRules(); + + round_handler_Spawn(Onslaught_CheckPlayers, Onslaught_CheckWinner, Onslaught_RoundStart); + round_handler_Init(5, autocvar_g_onslaught_warmup, autocvar_g_onslaught_round_timelimit); +} + +void ons_Initialize() +{ + g_onslaught = true; + ons_captureshield_force = autocvar_g_onslaught_shield_force; + + InitializeEntity(NULL, ons_DelayedInit, INITPRIO_GAMETYPE); +} diff --git a/qcsrc/common/gamemodes/gamemode/onslaught/sv_onslaught.qh b/qcsrc/common/gamemodes/gamemode/onslaught/sv_onslaught.qh new file mode 100644 index 0000000000..c8f94c9d80 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/onslaught/sv_onslaught.qh @@ -0,0 +1,117 @@ +float autocvar_g_onslaught_point_limit; +void ons_Initialize(); + +REGISTER_MUTATOR(ons, false) +{ + MUTATOR_ONADD + { + if (time > 1) // game loads at time 1 + error("This is a game type and it cannot be added at runtime."); + ons_Initialize(); + + ActivateTeamplay(); + SetLimits(autocvar_g_onslaught_point_limit, autocvar_leadlimit_override, autocvar_timelimit_override, -1); + have_team_spawns = -1; // request team spawns + } + + MUTATOR_ONROLLBACK_OR_REMOVE + { + // we actually cannot roll back ons_Initialize here + // BUT: we don't need to! If this gets called, adding always + // succeeds. + } + + MUTATOR_ONREMOVE + { + LOG_INFO("This is a game type and it cannot be removed at runtime."); + return -1; + } + + return false; +} + +.entity ons_toucher; // player who touched the control point + +// control point / generator constants +const float ONS_CP_THINKRATE = 0.2; +const float GEN_THINKRATE = 1; +#define CPGEN_SPAWN_OFFSET ('0 0 1' * (PL_MAX_CONST.z - 13)) +const vector CPGEN_WAYPOINT_OFFSET = ('0 0 128'); +const vector CPICON_OFFSET = ('0 0 96'); + +// list of generators on the map +entity ons_worldgeneratorlist; +.entity ons_worldgeneratornext; +.entity ons_stalegeneratornext; + +// list of control points on the map +entity ons_worldcplist; +.entity ons_worldcpnext; +.entity ons_stalecpnext; + +// list of links on the map +entity ons_worldlinklist; +.entity ons_worldlinknext; +.entity ons_stalelinknext; + +// definitions +.entity sprite; +.string target2; +.int iscaptured; +.int islinked; +.int isshielded; +.float lasthealth; +.int lastteam; +.int lastshielded; +.int lastcaptured; + +.bool waslinked; + +bool ons_stalemate; + +.float teleport_antispam; + +.bool ons_roundlost = _STAT(ROUNDLOST); + +// waypoint sprites +.entity bot_basewaypoint; // generator waypointsprite + +.bool isgenneighbor[17]; +.bool iscpneighbor[17]; +float ons_notification_time[17]; + +.float ons_overtime_damagedelay; + +.vector ons_deathloc; + +.entity ons_spawn_by; + +// declarations for functions used outside gamemode_onslaught.qc +void ons_Generator_UpdateSprite(entity e); +void ons_ControlPoint_UpdateSprite(entity e); +bool ons_ControlPoint_Attackable(entity cp, int teamnumber); + +// CaptureShield: Prevent capturing or destroying control point/generator if it is not available yet +float ons_captureshield_force; // push force of the shield + +// bot player logic +const int HAVOCBOT_ONS_ROLE_NONE = 0; +const int HAVOCBOT_ONS_ROLE_DEFENSE = 2; +const int HAVOCBOT_ONS_ROLE_ASSISTANT = 4; +const int HAVOCBOT_ONS_ROLE_OFFENSE = 8; + +.entity havocbot_ons_target; + +.int havocbot_role_flags; +.float havocbot_attack_time; + +void havocbot_role_ons_defense(entity this); +void havocbot_role_ons_offense(entity this); +void havocbot_role_ons_assistant(entity this); + +void havocbot_ons_reset_role(entity this); +void havocbot_goalrating_items(entity this, float ratingscale, vector org, float sradius); +void havocbot_goalrating_enemyplayers(entity this, float ratingscale, vector org, float sradius); + +// score rule declarations +const int ST_ONS_CAPS = 1; diff --git a/qcsrc/common/items/item/jetpack.qh b/qcsrc/common/items/item/jetpack.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/items/item/jetpack.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/minigames/_mod.inc b/qcsrc/common/minigames/_mod.inc index 43ad69de59..66693ef2ab 100644 --- a/qcsrc/common/minigames/_mod.inc +++ b/qcsrc/common/minigames/_mod.inc @@ -1,5 +1,7 @@ // generated file; do not modify -#include +#ifdef CSQC + #include +#endif #include #ifdef CSQC #include diff --git a/qcsrc/common/minigames/_mod.qh b/qcsrc/common/minigames/_mod.qh index 36f5de2f15..9c33760030 100644 --- a/qcsrc/common/minigames/_mod.qh +++ b/qcsrc/common/minigames/_mod.qh @@ -1,3 +1,11 @@ // generated file; do not modify -#include +#ifdef CSQC + #include +#endif #include +#ifdef CSQC + #include +#endif +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/minigames/minigame/bd.qh b/qcsrc/common/minigames/minigame/bd.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/minigames/minigame/bd.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/minigames/minigame/c4.qh b/qcsrc/common/minigames/minigame/c4.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/minigames/minigame/c4.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/minigames/minigame/nmm.qh b/qcsrc/common/minigames/minigame/nmm.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/minigames/minigame/nmm.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/minigames/minigame/pong.qh b/qcsrc/common/minigames/minigame/pong.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/minigames/minigame/pong.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/minigames/minigame/pp.qh b/qcsrc/common/minigames/minigame/pp.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/minigames/minigame/pp.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/minigames/minigame/ps.qh b/qcsrc/common/minigames/minigame/ps.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/minigames/minigame/ps.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/minigames/minigame/snake.qh b/qcsrc/common/minigames/minigame/snake.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/minigames/minigame/snake.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/minigames/minigame/ttt.qh b/qcsrc/common/minigames/minigame/ttt.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/minigames/minigame/ttt.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/monsters/_mod.inc b/qcsrc/common/monsters/_mod.inc index f9d80b3f4e..0478f09eec 100644 --- a/qcsrc/common/monsters/_mod.inc +++ b/qcsrc/common/monsters/_mod.inc @@ -1,4 +1,6 @@ // generated file; do not modify #include #include -#include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/monsters/_mod.qh b/qcsrc/common/monsters/_mod.qh index 48427f94f7..39d594e4c5 100644 --- a/qcsrc/common/monsters/_mod.qh +++ b/qcsrc/common/monsters/_mod.qh @@ -1,4 +1,6 @@ // generated file; do not modify #include #include -#include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/monsters/monster/mage.qh b/qcsrc/common/monsters/monster/mage.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/monsters/monster/mage.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/monsters/monster/shambler.qh b/qcsrc/common/monsters/monster/shambler.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/monsters/monster/shambler.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/monsters/monster/spider.qh b/qcsrc/common/monsters/monster/spider.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/monsters/monster/spider.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/monsters/monster/wyvern.qh b/qcsrc/common/monsters/monster/wyvern.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/monsters/monster/wyvern.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/monsters/monster/zombie.qh b/qcsrc/common/monsters/monster/zombie.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/monsters/monster/zombie.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mutators/_all.inc b/qcsrc/common/mutators/_all.inc index 8bc63f720a..65d709243e 100644 --- a/qcsrc/common/mutators/_all.inc +++ b/qcsrc/common/mutators/_all.inc @@ -1,2 +1,4 @@ #include "_all.qh" #include "_mod.inc" + +#include "mutator/_all.inc" diff --git a/qcsrc/common/mutators/_all.qh b/qcsrc/common/mutators/_all.qh index 947026dd59..bae58a55c5 100644 --- a/qcsrc/common/mutators/_all.qh +++ b/qcsrc/common/mutators/_all.qh @@ -1,2 +1,4 @@ #pragma once #include "_mod.qh" + +#include "mutator/_all.qh" diff --git a/qcsrc/common/mutators/_mod.inc b/qcsrc/common/mutators/_mod.inc index 8220b2d195..98fb4815c1 100644 --- a/qcsrc/common/mutators/_mod.inc +++ b/qcsrc/common/mutators/_mod.inc @@ -1,2 +1 @@ // generated file; do not modify -#include diff --git a/qcsrc/common/mutators/_mod.qh b/qcsrc/common/mutators/_mod.qh index 5d6ac5628b..98fb4815c1 100644 --- a/qcsrc/common/mutators/_mod.qh +++ b/qcsrc/common/mutators/_mod.qh @@ -1,2 +1 @@ // generated file; do not modify -#include diff --git a/qcsrc/common/mutators/all.inc b/qcsrc/common/mutators/all.inc deleted file mode 100644 index 6225872332..0000000000 --- a/qcsrc/common/mutators/all.inc +++ /dev/null @@ -1,39 +0,0 @@ -#include "mutator/waypoints/module.inc" - -#include "mutator/itemstime.qc" -#include "mutator/multijump/module.inc" -#include "mutator/nades/module.inc" -#include "mutator/superspec/module.inc" - -// completely self contained - -#include "mutator/bloodloss/module.inc" -#include "mutator/breakablehook/module.inc" -#include "mutator/buffs/module.inc" -#include "mutator/bugrigs/module.inc" -#include "mutator/campcheck/module.inc" -#include "mutator/cloaked/module.inc" -#include "mutator/damagetext/module.inc" -#include "mutator/dodging/module.inc" -#include "mutator/doublejump/module.inc" -#include "mutator/globalforces/module.inc" -#include "mutator/hook/module.inc" -#include "mutator/instagib/module.inc" -#include "mutator/invincibleproj/module.inc" -#include "mutator/melee_only/module.inc" -#include "mutator/midair/module.inc" -#include "mutator/new_toys/module.inc" -#include "mutator/nix/module.inc" -#include "mutator/overkill/module.inc" -#include "mutator/physical_items/module.inc" -#include "mutator/pinata/module.inc" -#include "mutator/random_gravity/module.inc" -#include "mutator/rocketflying/module.inc" -#include "mutator/rocketminsta/module.inc" -#include "mutator/running_guns/module.inc" -#include "mutator/sandbox/module.inc" -#include "mutator/spawn_near_teammate/module.inc" -#include "mutator/touchexplode/module.inc" -#include "mutator/vampirehook/module.inc" -#include "mutator/vampire/module.inc" -#include "mutator/weaponarena_random/module.inc" diff --git a/qcsrc/common/mutators/all.qc b/qcsrc/common/mutators/all.qc deleted file mode 100644 index f0fc7195a8..0000000000 --- a/qcsrc/common/mutators/all.qc +++ /dev/null @@ -1,5 +0,0 @@ -#include "all.qh" - -#define IMPLEMENTATION -#include "all.inc" -#undef IMPLEMENTATION diff --git a/qcsrc/common/mutators/all.qh b/qcsrc/common/mutators/all.qh deleted file mode 100644 index ceb4b51dd5..0000000000 --- a/qcsrc/common/mutators/all.qh +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef MUTATORS_ALL_H -#define MUTATORS_ALL_H - -#include "all.inc" - -#endif diff --git a/qcsrc/common/mutators/mutator/_all.inc b/qcsrc/common/mutators/mutator/_all.inc new file mode 100644 index 0000000000..9b557a22ba --- /dev/null +++ b/qcsrc/common/mutators/mutator/_all.inc @@ -0,0 +1,43 @@ +#include "_all.qh" +#include "_mod.inc" + +#include "waypoints/_mod.inc" + +#include "itemstime/_mod.inc" +#include "multijump/_mod.inc" +#include "nades/_mod.inc" +#include "superspec/_mod.inc" + +// completely self contained + +#include "bloodloss/_mod.inc" +#include "breakablehook/_mod.inc" +#include "buffs/_mod.inc" +#include "bugrigs/_mod.inc" +#include "campcheck/_mod.inc" +#include "cloaked/_mod.inc" +#include "damagetext/_mod.inc" +#include "dodging/_mod.inc" +#include "doublejump/_mod.inc" +#include "globalforces/_mod.inc" +#include "hook/_mod.inc" +#include "instagib/_mod.inc" +#include "invincibleproj/_mod.inc" +#include "melee_only/_mod.inc" +#include "midair/_mod.inc" +#include "new_toys/_mod.inc" +#include "nix/_mod.inc" +#include "overkill/_mod.inc" +#include "physical_items/_mod.inc" +#include "pinata/_mod.inc" +#include "random_gravity/_mod.inc" +#include "rocketflying/_mod.inc" +#include "rocketminsta/_mod.inc" +#include "running_guns/_mod.inc" +#include "sandbox/_mod.inc" +#include "spawn_near_teammate/_mod.inc" +#include "touchexplode/_mod.inc" +#include "vampirehook/_mod.inc" +#include "vampire/_mod.inc" +#include "weaponarena_random/_mod.inc" + diff --git a/qcsrc/common/mutators/mutator/_all.qh b/qcsrc/common/mutators/mutator/_all.qh new file mode 100644 index 0000000000..b49167e7e7 --- /dev/null +++ b/qcsrc/common/mutators/mutator/_all.qh @@ -0,0 +1,43 @@ +#pragma once +#include "_mod.qh" + +#include "waypoints/_mod.qh" + +#include "itemstime/_mod.qh" +#include "multijump/_mod.qh" +#include "nades/_mod.qh" +#include "superspec/_mod.qh" + +// completely self contained + +#include "bloodloss/_mod.qh" +#include "breakablehook/_mod.qh" +#include "buffs/_mod.qh" +#include "bugrigs/_mod.qh" +#include "campcheck/_mod.qh" +#include "cloaked/_mod.qh" +#include "damagetext/_mod.qh" +#include "dodging/_mod.qh" +#include "doublejump/_mod.qh" +#include "globalforces/_mod.qh" +#include "hook/_mod.qh" +#include "instagib/_mod.qh" +#include "invincibleproj/_mod.qh" +#include "melee_only/_mod.qh" +#include "midair/_mod.qh" +#include "new_toys/_mod.qh" +#include "nix/_mod.qh" +#include "overkill/_mod.qh" +#include "physical_items/_mod.qh" +#include "pinata/_mod.qh" +#include "random_gravity/_mod.qh" +#include "rocketflying/_mod.qh" +#include "rocketminsta/_mod.qh" +#include "running_guns/_mod.qh" +#include "sandbox/_mod.qh" +#include "spawn_near_teammate/_mod.qh" +#include "touchexplode/_mod.qh" +#include "vampirehook/_mod.qh" +#include "vampire/_mod.qh" +#include "weaponarena_random/_mod.qh" + diff --git a/qcsrc/common/mutators/mutator/_mod.inc b/qcsrc/common/mutators/mutator/_mod.inc index 30d67e34bc..98fb4815c1 100644 --- a/qcsrc/common/mutators/mutator/_mod.inc +++ b/qcsrc/common/mutators/mutator/_mod.inc @@ -1,2 +1 @@ // generated file; do not modify -#include diff --git a/qcsrc/common/mutators/mutator/_mod.qh b/qcsrc/common/mutators/mutator/_mod.qh index ac056ac68b..98fb4815c1 100644 --- a/qcsrc/common/mutators/mutator/_mod.qh +++ b/qcsrc/common/mutators/mutator/_mod.qh @@ -1,2 +1 @@ // generated file; do not modify -#include diff --git a/qcsrc/common/mutators/mutator/bloodloss/_mod.inc b/qcsrc/common/mutators/mutator/bloodloss/_mod.inc index 16e6308acf..768808db71 100644 --- a/qcsrc/common/mutators/mutator/bloodloss/_mod.inc +++ b/qcsrc/common/mutators/mutator/bloodloss/_mod.inc @@ -1,2 +1,4 @@ // generated file; do not modify -#include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/mutators/mutator/bloodloss/_mod.qh b/qcsrc/common/mutators/mutator/bloodloss/_mod.qh index b1d45e2791..e6ad248c62 100644 --- a/qcsrc/common/mutators/mutator/bloodloss/_mod.qh +++ b/qcsrc/common/mutators/mutator/bloodloss/_mod.qh @@ -1,2 +1,4 @@ // generated file; do not modify -#include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/mutators/mutator/bloodloss/bloodloss.qc b/qcsrc/common/mutators/mutator/bloodloss/bloodloss.qc deleted file mode 100644 index a335bf1cc4..0000000000 --- a/qcsrc/common/mutators/mutator/bloodloss/bloodloss.qc +++ /dev/null @@ -1,43 +0,0 @@ -#ifdef IMPLEMENTATION -REGISTER_MUTATOR(bloodloss, cvar("g_bloodloss")); - -.float bloodloss_timer; - -MUTATOR_HOOKFUNCTION(bloodloss, PlayerPreThink) -{ - entity player = M_ARGV(0, entity); - - if(IS_PLAYER(player)) - if(player.health <= autocvar_g_bloodloss && !IS_DEAD(player)) - { - PHYS_INPUT_BUTTON_CROUCH(player) = true; - - if(time >= player.bloodloss_timer) - { - if(player.vehicle) - vehicles_exit(player.vehicle, VHEF_RELEASE); - if(player.event_damage) - player.event_damage(player, player, player, 1, DEATH_ROT.m_id, player.origin, '0 0 0'); - player.bloodloss_timer = time + 0.5 + random() * 0.5; - } - } -} - -MUTATOR_HOOKFUNCTION(bloodloss, PlayerJump) -{ - entity player = M_ARGV(0, entity); - - if(player.health <= autocvar_g_bloodloss) - return true; -} - -MUTATOR_HOOKFUNCTION(bloodloss, BuildMutatorsString) -{ - M_ARGV(0, string) = strcat(M_ARGV(0, string), ":bloodloss"); -} - -MUTATOR_HOOKFUNCTION(bloodloss, BuildMutatorsPrettyString) -{ - M_ARGV(0, string) = strcat(M_ARGV(0, string), ", Blood loss"); -} -#endif diff --git a/qcsrc/common/mutators/mutator/bloodloss/module.inc b/qcsrc/common/mutators/mutator/bloodloss/module.inc deleted file mode 100644 index d3f665a18b..0000000000 --- a/qcsrc/common/mutators/mutator/bloodloss/module.inc +++ /dev/null @@ -1,3 +0,0 @@ -#ifdef SVQC -#include "bloodloss.qc" -#endif diff --git a/qcsrc/common/mutators/mutator/bloodloss/sv_bloodloss.qc b/qcsrc/common/mutators/mutator/bloodloss/sv_bloodloss.qc new file mode 100644 index 0000000000..61b0c06016 --- /dev/null +++ b/qcsrc/common/mutators/mutator/bloodloss/sv_bloodloss.qc @@ -0,0 +1,43 @@ +#include "sv_bloodloss.qh" + +REGISTER_MUTATOR(bloodloss, cvar("g_bloodloss")); + +.float bloodloss_timer; + +MUTATOR_HOOKFUNCTION(bloodloss, PlayerPreThink) +{ + entity player = M_ARGV(0, entity); + + if(IS_PLAYER(player)) + if(player.health <= autocvar_g_bloodloss && !IS_DEAD(player)) + { + PHYS_INPUT_BUTTON_CROUCH(player) = true; + + if(time >= player.bloodloss_timer) + { + if(player.vehicle) + vehicles_exit(player.vehicle, VHEF_RELEASE); + if(player.event_damage) + player.event_damage(player, player, player, 1, DEATH_ROT.m_id, player.origin, '0 0 0'); + player.bloodloss_timer = time + 0.5 + random() * 0.5; + } + } +} + +MUTATOR_HOOKFUNCTION(bloodloss, PlayerJump) +{ + entity player = M_ARGV(0, entity); + + if(player.health <= autocvar_g_bloodloss) + return true; +} + +MUTATOR_HOOKFUNCTION(bloodloss, BuildMutatorsString) +{ + M_ARGV(0, string) = strcat(M_ARGV(0, string), ":bloodloss"); +} + +MUTATOR_HOOKFUNCTION(bloodloss, BuildMutatorsPrettyString) +{ + M_ARGV(0, string) = strcat(M_ARGV(0, string), ", Blood loss"); +} diff --git a/qcsrc/common/mutators/mutator/bloodloss/sv_bloodloss.qh b/qcsrc/common/mutators/mutator/bloodloss/sv_bloodloss.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/mutators/mutator/bloodloss/sv_bloodloss.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mutators/mutator/breakablehook/_mod.inc b/qcsrc/common/mutators/mutator/breakablehook/_mod.inc index bdbbae46cd..11a080ef4a 100644 --- a/qcsrc/common/mutators/mutator/breakablehook/_mod.inc +++ b/qcsrc/common/mutators/mutator/breakablehook/_mod.inc @@ -1,2 +1,4 @@ // generated file; do not modify -#include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/mutators/mutator/breakablehook/_mod.qh b/qcsrc/common/mutators/mutator/breakablehook/_mod.qh index 8a41908af3..f8b2d1bc65 100644 --- a/qcsrc/common/mutators/mutator/breakablehook/_mod.qh +++ b/qcsrc/common/mutators/mutator/breakablehook/_mod.qh @@ -1,2 +1,4 @@ // generated file; do not modify -#include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/mutators/mutator/breakablehook/breakablehook.qc b/qcsrc/common/mutators/mutator/breakablehook/breakablehook.qc deleted file mode 100644 index ca266eb5af..0000000000 --- a/qcsrc/common/mutators/mutator/breakablehook/breakablehook.qc +++ /dev/null @@ -1,30 +0,0 @@ -#ifdef IMPLEMENTATION -#include -#include - -REGISTER_MUTATOR(breakablehook, cvar("g_breakablehook")); - -bool autocvar_g_breakablehook; // allow toggling mid match? -bool autocvar_g_breakablehook_owner; - -MUTATOR_HOOKFUNCTION(breakablehook, PlayerDamage_Calculate) -{ - entity frag_attacker = M_ARGV(1, entity); - entity frag_target = M_ARGV(2, entity); - - if(frag_target.classname == "grapplinghook") - { - if((!autocvar_g_breakablehook) - || (!autocvar_g_breakablehook_owner && frag_attacker == frag_target.realowner) - ) { M_ARGV(4, float) = 0; } - - // hurt the owner of the hook - if(DIFF_TEAM(frag_attacker, frag_target.realowner)) - { - Damage (frag_target.realowner, frag_attacker, frag_attacker, 5, WEP_HOOK.m_id | HITTYPE_SPLASH, frag_target.realowner.origin, '0 0 0'); - RemoveGrapplingHook(frag_target.realowner); - return; // dead - } - } -} -#endif diff --git a/qcsrc/common/mutators/mutator/breakablehook/module.inc b/qcsrc/common/mutators/mutator/breakablehook/module.inc deleted file mode 100644 index 484eb4c563..0000000000 --- a/qcsrc/common/mutators/mutator/breakablehook/module.inc +++ /dev/null @@ -1,3 +0,0 @@ -#ifdef SVQC -#include "breakablehook.qc" -#endif diff --git a/qcsrc/common/mutators/mutator/breakablehook/sv_breakablehook.qc b/qcsrc/common/mutators/mutator/breakablehook/sv_breakablehook.qc new file mode 100644 index 0000000000..fdb0dc38d1 --- /dev/null +++ b/qcsrc/common/mutators/mutator/breakablehook/sv_breakablehook.qc @@ -0,0 +1,30 @@ +#include "sv_breakablehook.qh" + +#include +#include + +REGISTER_MUTATOR(breakablehook, cvar("g_breakablehook")); + +bool autocvar_g_breakablehook; // allow toggling mid match? +bool autocvar_g_breakablehook_owner; + +MUTATOR_HOOKFUNCTION(breakablehook, PlayerDamage_Calculate) +{ + entity frag_attacker = M_ARGV(1, entity); + entity frag_target = M_ARGV(2, entity); + + if(frag_target.classname == "grapplinghook") + { + if((!autocvar_g_breakablehook) + || (!autocvar_g_breakablehook_owner && frag_attacker == frag_target.realowner) + ) { M_ARGV(4, float) = 0; } + + // hurt the owner of the hook + if(DIFF_TEAM(frag_attacker, frag_target.realowner)) + { + Damage (frag_target.realowner, frag_attacker, frag_attacker, 5, WEP_HOOK.m_id | HITTYPE_SPLASH, frag_target.realowner.origin, '0 0 0'); + RemoveGrapplingHook(frag_target.realowner); + return; // dead + } + } +} diff --git a/qcsrc/common/mutators/mutator/breakablehook/sv_breakablehook.qh b/qcsrc/common/mutators/mutator/breakablehook/sv_breakablehook.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/mutators/mutator/breakablehook/sv_breakablehook.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mutators/mutator/buffs/_mod.inc b/qcsrc/common/mutators/mutator/buffs/_mod.inc index c06263a92f..715a3acfcc 100644 --- a/qcsrc/common/mutators/mutator/buffs/_mod.inc +++ b/qcsrc/common/mutators/mutator/buffs/_mod.inc @@ -1,3 +1,8 @@ // generated file; do not modify -#include #include +#ifdef CSQC + #include +#endif +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/mutators/mutator/buffs/_mod.qh b/qcsrc/common/mutators/mutator/buffs/_mod.qh index 2133c7250e..78217bb46e 100644 --- a/qcsrc/common/mutators/mutator/buffs/_mod.qh +++ b/qcsrc/common/mutators/mutator/buffs/_mod.qh @@ -1,3 +1,8 @@ // generated file; do not modify -#include #include +#ifdef CSQC + #include +#endif +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/mutators/mutator/buffs/all.qc b/qcsrc/common/mutators/mutator/buffs/all.qc deleted file mode 100644 index b056751624..0000000000 --- a/qcsrc/common/mutators/mutator/buffs/all.qc +++ /dev/null @@ -1 +0,0 @@ -#include "all.qh" diff --git a/qcsrc/common/mutators/mutator/buffs/all.qh b/qcsrc/common/mutators/mutator/buffs/all.qh deleted file mode 100644 index 79117c95a8..0000000000 --- a/qcsrc/common/mutators/mutator/buffs/all.qh +++ /dev/null @@ -1,71 +0,0 @@ -#ifndef BUFFS_ALL_H -#define BUFFS_ALL_H - -#include -#include - -REGISTER_WAYPOINT(Buff, _("Buff"), '1 0.5 0', 1); -REGISTER_RADARICON(Buff, 1); - -REGISTRY(Buffs, BITS(5)) -#define Buffs_from(i) _Buffs_from(i, BUFF_Null) -REGISTER_REGISTRY(Buffs) -REGISTRY_CHECK(Buffs) - -#define REGISTER_BUFF(id) \ - REGISTER(Buffs, BUFF_##id, m_id, NEW(Buff)) - -#include -CLASS(Buff, Pickup) - /** bit index */ - ATTRIB(Buff, m_itemid, int, 0); - ATTRIB(Buff, m_name, string, "buff"); - ATTRIB(Buff, m_color, vector, '1 1 1'); - ATTRIB(Buff, m_prettyName, string, "Buff"); - ATTRIB(Buff, m_skin, int, 0); - ATTRIB(Buff, m_sprite, string, ""); - METHOD(Buff, display, void(entity this, void(string name, string icon) returns)) { - returns(this.m_prettyName, sprintf("/gfx/hud/%s/buff_%s", cvar_string("menu_skin"), this.m_name)); - } -#ifdef SVQC - METHOD(Buff, m_time, float(Buff this)) - { return cvar(strcat("g_buffs_", this.netname, "_time")); } -#endif -ENDCLASS(Buff) - -STATIC_INIT(REGISTER_BUFFS) { - FOREACH(Buffs, true, { - it.netname = it.m_name; \ - it.m_itemid = BIT(it.m_id - 1); \ - it.m_sprite = strzone(strcat("buff-", it.m_name)); \ - }); -} - -#ifdef SVQC - // .int buffs = _STAT(BUFFS); - void buff_Init(entity ent); - void buff_Init_Compat(entity ent, entity replacement); - #define BUFF_SPAWNFUNC(e, b, t) spawnfunc(item_buff_##e) { \ - this.buffs = b.m_itemid; \ - this.team = t; \ - buff_Init(this); \ - } - #define BUFF_SPAWNFUNCS(e, b) \ - BUFF_SPAWNFUNC(e, b, 0) \ - BUFF_SPAWNFUNC(e##_team1, b, NUM_TEAM_1) \ - BUFF_SPAWNFUNC(e##_team2, b, NUM_TEAM_2) \ - BUFF_SPAWNFUNC(e##_team3, b, NUM_TEAM_3) \ - BUFF_SPAWNFUNC(e##_team4, b, NUM_TEAM_4) - #define BUFF_SPAWNFUNC_Q3TA_COMPAT(o, r) spawnfunc(item_##o) { buff_Init_Compat(this, r); } -#else - #define BUFF_SPAWNFUNC(e, b, t) - #define BUFF_SPAWNFUNCS(e, b) - #define BUFF_SPAWNFUNC_Q3TA_COMPAT(o, r) -#endif - -REGISTER_BUFF(Null); -BUFF_SPAWNFUNCS(random, BUFF_Null) - -#include "all.inc" - -#endif diff --git a/qcsrc/common/mutators/mutator/buffs/buffs.qc b/qcsrc/common/mutators/mutator/buffs/buffs.qc index 29a0a39256..f38d39d618 100644 --- a/qcsrc/common/mutators/mutator/buffs/buffs.qc +++ b/qcsrc/common/mutators/mutator/buffs/buffs.qc @@ -1,1081 +1,16 @@ -#ifndef MUTATOR_BUFFS_H -#define MUTATOR_BUFFS_H +#include "buffs.qh" -#include "../instagib/module.inc" - -bool autocvar_g_buffs_effects; -float autocvar_g_buffs_waypoint_distance; -bool autocvar_g_buffs_randomize; -float autocvar_g_buffs_random_lifetime; -bool autocvar_g_buffs_random_location; -int autocvar_g_buffs_random_location_attempts; -int autocvar_g_buffs_spawn_count; -bool autocvar_g_buffs_replace_powerups; -float autocvar_g_buffs_cooldown_activate; -float autocvar_g_buffs_cooldown_respawn; -float autocvar_g_buffs_resistance_blockpercent; -float autocvar_g_buffs_medic_survive_chance; -float autocvar_g_buffs_medic_survive_health; -float autocvar_g_buffs_medic_rot; -float autocvar_g_buffs_medic_max; -float autocvar_g_buffs_medic_regen; -float autocvar_g_buffs_medic_heal_amount = 15; -float autocvar_g_buffs_medic_heal_delay = 1; -float autocvar_g_buffs_medic_heal_range = 400; -float autocvar_g_buffs_vengeance_damage_multiplier; -float autocvar_g_buffs_bash_force; -float autocvar_g_buffs_bash_force_self; -float autocvar_g_buffs_disability_slowtime; -float autocvar_g_buffs_disability_speed; -float autocvar_g_buffs_disability_rate; -float autocvar_g_buffs_disability_weaponspeed; -float autocvar_g_buffs_speed_speed; -float autocvar_g_buffs_speed_rate; -float autocvar_g_buffs_speed_weaponspeed; -float autocvar_g_buffs_speed_damage_take; -float autocvar_g_buffs_speed_regen; -float autocvar_g_buffs_vampire_damage_steal; -float autocvar_g_buffs_invisible_alpha; -float autocvar_g_buffs_jump_height; -float autocvar_g_buffs_inferno_burntime_factor; -float autocvar_g_buffs_inferno_burntime_min_time; -float autocvar_g_buffs_inferno_burntime_target_damage; -float autocvar_g_buffs_inferno_burntime_target_time; -float autocvar_g_buffs_inferno_damagemultiplier; -float autocvar_g_buffs_swapper_range; -float autocvar_g_buffs_magnet_range_item; -float autocvar_g_buffs_magnet_range_buff = 200; -float autocvar_g_buffs_luck_chance = 0.15; -float autocvar_g_buffs_luck_damagemultiplier = 3; - -// ammo -.float buff_ammo_prev_infitems; -.int buff_ammo_prev_clipload; -// invisible -.float buff_invisible_prev_alpha; -// medic -.float buff_medic_healtime; -// disability -.float buff_disability_time; -.float buff_disability_effect_time; -// common buff variables -.float buff_effect_delay; - -// buff definitions -.float buff_active; -.float buff_activetime; -.float buff_activetime_updated; -.entity buff_waypoint; -.int oldbuffs; // for updating effects -.entity buff_model; // controls effects (TODO: make csqc) - -const vector BUFF_MIN = ('-16 -16 -20'); -const vector BUFF_MAX = ('16 16 20'); - -// client side options -.float cvar_cl_buffs_autoreplace; -#endif - -#ifdef IMPLEMENTATION - -#include -#include - -.float buff_time = _STAT(BUFF_TIME); -void buffs_DelayedInit(entity this); - -REGISTER_MUTATOR(buffs, cvar("g_buffs")) -{ - MUTATOR_ONADD - { - InitializeEntity(NULL, buffs_DelayedInit, INITPRIO_FINDTARGET); - } -} - -bool buffs_BuffModel_Customize(entity this, entity client) -{ - entity player, myowner; - bool same_team; - - player = WaypointSprite_getviewentity(client); - myowner = this.owner; - same_team = (SAME_TEAM(player, myowner) || SAME_TEAM(player, myowner)); - - if(myowner.alpha <= 0.5 && !same_team && myowner.alpha != 0) - return false; - - if(MUTATOR_CALLHOOK(BuffModel_Customize, this, player)) - return false; - - if(player == myowner || (IS_SPEC(client) && client.enemy == myowner)) - { - // somewhat hide the model, but keep the glow - this.effects = 0; - this.alpha = -1; - } - else - { - this.effects = EF_FULLBRIGHT | EF_LOWPRECISION; - this.alpha = 1; - } - return true; -} - -void buffs_BuffModel_Spawn(entity player) -{ - player.buff_model = spawn(); - setmodel(player.buff_model, MDL_BUFF); - setsize(player.buff_model, '0 0 -40', '0 0 40'); - setattachment(player.buff_model, player, ""); - setorigin(player.buff_model, '0 0 1' * (player.buff_model.maxs.z * 1)); - player.buff_model.owner = player; - player.buff_model.scale = 0.7; - player.buff_model.pflags = PFLAGS_FULLDYNAMIC; - player.buff_model.light_lev = 200; - setcefc(player.buff_model, buffs_BuffModel_Customize); -} - -vector buff_GlowColor(entity buff) -{ - //if(buff.team) { return Team_ColorRGB(buff.team); } - return buff.m_color; -} - -void buff_Effect(entity player, string eff) -{ - if(!autocvar_g_buffs_effects) { return; } - - if(time >= player.buff_effect_delay) - { - Send_Effect_(eff, player.origin + ((player.mins + player.maxs) * 0.5), '0 0 0', 1); - player.buff_effect_delay = time + 0.05; // prevent spam - } -} - -// buff item -bool buff_Waypoint_visible_for_player(entity this, entity player, entity view) -{ - if(!this.owner.buff_active && !this.owner.buff_activetime) - return false; - - if (view.buffs) - { - return view.cvar_cl_buffs_autoreplace == false || view.buffs != this.owner.buffs; - } - - return WaypointSprite_visible_for_player(this, player, view); -} - -void buff_Waypoint_Spawn(entity e) -{ - entity buff = buff_FirstFromFlags(e.buffs); - entity wp = WaypointSprite_Spawn(WP_Buff, 0, autocvar_g_buffs_waypoint_distance, e, '0 0 1' * e.maxs.z, NULL, e.team, e, buff_waypoint, true, RADARICON_Buff); - wp.wp_extra = buff.m_id; - WaypointSprite_UpdateTeamRadar(e.buff_waypoint, RADARICON_Buff, e.glowmod); - e.buff_waypoint.waypointsprite_visible_for_player = buff_Waypoint_visible_for_player; -} - -void buff_SetCooldown(entity this, float cd) -{ - cd = max(0, cd); - - if(!this.buff_waypoint) - buff_Waypoint_Spawn(this); - - WaypointSprite_UpdateBuildFinished(this.buff_waypoint, time + cd); - this.buff_activetime = cd; - this.buff_active = !cd; -} - -void buff_Respawn(entity this) -{ - if(gameover) { return; } - - vector oldbufforigin = this.origin; - this.velocity = '0 0 200'; - - if(!MoveToRandomMapLocation(this, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, - ((autocvar_g_buffs_random_location_attempts > 0) ? autocvar_g_buffs_random_location_attempts : 10), 1024, 256)) - { - entity spot = SelectSpawnPoint(this, true); - setorigin(this, spot.origin); - this.velocity = ((randomvec() * 100) + '0 0 200'); - this.angles = spot.angles; - } - - tracebox(this.origin, this.mins * 1.5, this.maxs * 1.5, this.origin, MOVE_NOMONSTERS, this); - - setorigin(this, trace_endpos); // attempt to unstick - - set_movetype(this, MOVETYPE_TOSS); - - makevectors(this.angles); - this.angles = '0 0 0'; - if(autocvar_g_buffs_random_lifetime > 0) - this.lifetime = time + autocvar_g_buffs_random_lifetime; - - Send_Effect(EFFECT_ELECTRO_COMBO, oldbufforigin + ((this.mins + this.maxs) * 0.5), '0 0 0', 1); - Send_Effect(EFFECT_ELECTRO_COMBO, CENTER_OR_VIEWOFS(this), '0 0 0', 1); - - WaypointSprite_Ping(this.buff_waypoint); - - sound(this, CH_TRIGGER, SND_KA_RESPAWN, VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere) -} - -void buff_Touch(entity this, entity toucher) -{ - if(gameover) { return; } - - if(ITEM_TOUCH_NEEDKILL()) - { - buff_Respawn(this); - return; - } - - if((this.team && DIFF_TEAM(toucher, this)) - || (STAT(FROZEN, toucher)) - || (toucher.vehicle) - || (!this.buff_active) - ) - { - // can't touch this - return; - } - - if(MUTATOR_CALLHOOK(BuffTouch, this, toucher)) - return; - toucher = M_ARGV(1, entity); - - if(!IS_PLAYER(toucher)) - return; // incase mutator changed toucher - - if (toucher.buffs) - { - if (toucher.cvar_cl_buffs_autoreplace && toucher.buffs != this.buffs) - { - int buffid = buff_FirstFromFlags(toucher.buffs).m_id; - //Send_Notification(NOTIF_ONE, toucher, MSG_MULTI, ITEM_BUFF_DROP, toucher.buffs); - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ITEM_BUFF_LOST, toucher.netname, buffid); - - toucher.buffs = 0; - //sound(toucher, CH_TRIGGER, SND_BUFF_LOST, VOL_BASE, ATTN_NORM); - } - else { return; } // do nothing - } - - this.owner = toucher; - this.buff_active = false; - this.lifetime = 0; - int buffid = buff_FirstFromFlags(this.buffs).m_id; - Send_Notification(NOTIF_ONE, toucher, MSG_MULTI, ITEM_BUFF_GOT, buffid); - Send_Notification(NOTIF_ALL_EXCEPT, toucher, MSG_INFO, INFO_ITEM_BUFF, toucher.netname, buffid); - - Send_Effect(EFFECT_ITEM_PICKUP, CENTER_OR_VIEWOFS(this), '0 0 0', 1); - sound(toucher, CH_TRIGGER, SND_SHIELD_RESPAWN, VOL_BASE, ATTN_NORM); - toucher.buffs |= (this.buffs); -} - -float buff_Available(entity buff) -{ - if (buff == BUFF_Null) - return false; - if (buff == BUFF_AMMO && ((start_items & IT_UNLIMITED_WEAPON_AMMO) || (start_items & IT_UNLIMITED_AMMO) || (cvar("g_melee_only")))) - return false; - if (buff == BUFF_VAMPIRE && cvar("g_vampire")) - return false; - return cvar(strcat("g_buffs_", buff.m_name)); -} - -.int buff_seencount; - -void buff_NewType(entity ent, float cb) -{ - RandomSelection_Init(); - FOREACH(Buffs, buff_Available(it), LAMBDA( - it.buff_seencount += 1; - // if it's already been chosen, give it a lower priority - RandomSelection_Add(NULL, it.m_itemid, string_null, 1, max(0.2, 1 / it.buff_seencount)); - )); - ent.buffs = RandomSelection_chosen_float; -} - -void buff_Think(entity this) -{ - if(this.buffs != this.oldbuffs) - { - entity buff = buff_FirstFromFlags(this.buffs); - this.color = buff.m_color; - this.glowmod = buff_GlowColor(buff); - this.skin = buff.m_skin; - - setmodel(this, MDL_BUFF); - - if(this.buff_waypoint) - { - //WaypointSprite_Disown(this.buff_waypoint, 1); - WaypointSprite_Kill(this.buff_waypoint); - buff_Waypoint_Spawn(this); - if(this.buff_activetime) - WaypointSprite_UpdateBuildFinished(this.buff_waypoint, time + this.buff_activetime - frametime); - } - - this.oldbuffs = this.buffs; - } - - if(!gameover) - if((round_handler_IsActive() && !round_handler_IsRoundStarted()) || time >= game_starttime) - if(!this.buff_activetime_updated) - { - buff_SetCooldown(this, this.buff_activetime); - this.buff_activetime_updated = true; - } - - if(!this.buff_active && !this.buff_activetime) - if(!this.owner || STAT(FROZEN, this.owner) || IS_DEAD(this.owner) || !this.owner.iscreature || !(this.owner.buffs & this.buffs)) - { - buff_SetCooldown(this, autocvar_g_buffs_cooldown_respawn + frametime); - this.owner = NULL; - if(autocvar_g_buffs_randomize) - buff_NewType(this, this.buffs); - - if(autocvar_g_buffs_random_location || (this.spawnflags & 64)) - buff_Respawn(this); - } - - if(this.buff_activetime) - if(!gameover) - if((round_handler_IsActive() && !round_handler_IsRoundStarted()) || time >= game_starttime) - { - this.buff_activetime = max(0, this.buff_activetime - frametime); - - if(!this.buff_activetime) - { - this.buff_active = true; - sound(this, CH_TRIGGER, SND_STRENGTH_RESPAWN, VOL_BASE, ATTN_NORM); - Send_Effect(EFFECT_ITEM_RESPAWN, CENTER_OR_VIEWOFS(this), '0 0 0', 1); - } - } - - if(this.buff_active) - { - if(this.team && !this.buff_waypoint) - buff_Waypoint_Spawn(this); - - if(this.lifetime) - if(time >= this.lifetime) - buff_Respawn(this); - } - - this.nextthink = time; - //this.angles_y = time * 110.1; -} - -void buff_Waypoint_Reset(entity this) -{ - WaypointSprite_Kill(this.buff_waypoint); - - if(this.buff_activetime) { buff_Waypoint_Spawn(this); } -} - -void buff_Reset(entity this) -{ - if(autocvar_g_buffs_randomize) - buff_NewType(this, this.buffs); - this.owner = NULL; - buff_SetCooldown(this, autocvar_g_buffs_cooldown_activate); - buff_Waypoint_Reset(this); - this.buff_activetime_updated = false; - - if(autocvar_g_buffs_random_location || (this.spawnflags & 64)) - buff_Respawn(this); -} - -bool buff_Customize(entity this, entity client) -{ - entity player = WaypointSprite_getviewentity(client); - if(!this.buff_active || (this.team && DIFF_TEAM(player, this))) - { - this.alpha = 0.3; - if(this.effects & EF_FULLBRIGHT) { this.effects &= ~(EF_FULLBRIGHT); } - this.pflags = 0; - } - else - { - this.alpha = 1; - if(!(this.effects & EF_FULLBRIGHT)) { this.effects |= EF_FULLBRIGHT; } - this.light_lev = 220 + 36 * sin(time); - this.pflags = PFLAGS_FULLDYNAMIC; - } - return true; -} - -void buff_Init(entity this) -{ - if(!cvar("g_buffs")) { delete(this); return; } - - if(!teamplay && this.team) { this.team = 0; } - - entity buff = buff_FirstFromFlags(this.buffs); - - if(!this.buffs || buff_Available(buff)) - buff_NewType(this, 0); - - this.classname = "item_buff"; - this.solid = SOLID_TRIGGER; - this.flags = FL_ITEM; - setthink(this, buff_Think); - settouch(this, buff_Touch); - this.reset = buff_Reset; - this.nextthink = time + 0.1; - this.gravity = 1; - set_movetype(this, MOVETYPE_TOSS); - this.scale = 1; - this.skin = buff.m_skin; - this.effects = EF_FULLBRIGHT | EF_STARDUST | EF_NOSHADOW; - this.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY; - setcefc(this, buff_Customize); - //this.gravity = 100; - this.color = buff.m_color; - this.glowmod = buff_GlowColor(this); - buff_SetCooldown(this, autocvar_g_buffs_cooldown_activate + game_starttime); - this.buff_active = !this.buff_activetime; - this.pflags = PFLAGS_FULLDYNAMIC; - - if(this.spawnflags & 1) - this.noalign = true; - - if(this.noalign) - set_movetype(this, MOVETYPE_NONE); // reset by random location - - setmodel(this, MDL_BUFF); - setsize(this, BUFF_MIN, BUFF_MAX); - - if(cvar("g_buffs_random_location") || (this.spawnflags & 64)) - buff_Respawn(this); -} - -void buff_Init_Compat(entity ent, entity replacement) -{ - if (ent.spawnflags & 2) - ent.team = NUM_TEAM_1; - else if (ent.spawnflags & 4) - ent.team = NUM_TEAM_2; - - ent.buffs = replacement.m_itemid; - - buff_Init(ent); -} - -void buff_SpawnReplacement(entity ent, entity old) -{ - setorigin(ent, old.origin); - ent.angles = old.angles; - ent.noalign = (old.noalign || (old.spawnflags & 1)); - - buff_Init(ent); -} - -void buff_Vengeance_DelayedDamage(entity this) -{ - if(this.enemy) - Damage(this.enemy, this.owner, this.owner, this.dmg, DEATH_BUFF.m_id, this.enemy.origin, '0 0 0'); - - delete(this); - return; -} - -// note: only really useful in teamplay -void buff_Medic_Heal(entity this) -{ - FOREACH_CLIENT(IS_PLAYER(it) && it != this && vdist(it.origin - this.origin, <=, autocvar_g_buffs_medic_heal_range), - { - if(SAME_TEAM(it, this)) - if(it.health < autocvar_g_balance_health_regenstable) - { - Send_Effect(EFFECT_HEALING, it.origin, '0 0 0', 1); - it.health = bound(0, it.health + autocvar_g_buffs_medic_heal_amount, autocvar_g_balance_health_regenstable); - } - }); -} - -float buff_Inferno_CalculateTime(float x, float offset_x, float offset_y, float intersect_x, float intersect_y, float base) -{ - return offset_y + (intersect_y - offset_y) * logn(((x - offset_x) * ((base - 1) / intersect_x)) + 1, base); -} - -// mutator hooks -MUTATOR_HOOKFUNCTION(buffs, PlayerDamage_SplitHealthArmor) -{ - entity frag_target = M_ARGV(2, entity); - float frag_deathtype = M_ARGV(6, float); - float frag_damage = M_ARGV(7, float); - - if(frag_deathtype == DEATH_BUFF.m_id) { return; } - - if(frag_target.buffs & BUFF_RESISTANCE.m_itemid) - { - vector v = healtharmor_applydamage(50, autocvar_g_buffs_resistance_blockpercent, frag_deathtype, frag_damage); - M_ARGV(4, float) = v.x; // take - M_ARGV(5, float) = v.y; // save - } -} - -MUTATOR_HOOKFUNCTION(buffs, PlayerDamage_Calculate) -{ - entity frag_attacker = M_ARGV(1, entity); - entity frag_target = M_ARGV(2, entity); - float frag_deathtype = M_ARGV(3, float); - float frag_damage = M_ARGV(4, float); - vector frag_force = M_ARGV(6, vector); - - if(frag_deathtype == DEATH_BUFF.m_id) { return; } - - if(frag_target.buffs & BUFF_SPEED.m_itemid) - if(frag_target != frag_attacker) - frag_damage *= autocvar_g_buffs_speed_damage_take; - - if(frag_target.buffs & BUFF_MEDIC.m_itemid) - if((frag_target.health - frag_damage) <= 0) - if(!ITEM_DAMAGE_NEEDKILL(frag_deathtype)) - if(frag_attacker) - if(random() <= autocvar_g_buffs_medic_survive_chance) - frag_damage = max(5, frag_target.health - autocvar_g_buffs_medic_survive_health); - - if(frag_target.buffs & BUFF_JUMP.m_itemid) - if(frag_deathtype == DEATH_FALL.m_id) - frag_damage = 0; - - if(frag_target.buffs & BUFF_VENGEANCE.m_itemid) - if(frag_attacker) - if(frag_attacker != frag_target) - if(!ITEM_DAMAGE_NEEDKILL(frag_deathtype)) - { - entity dmgent = spawn(); - - dmgent.dmg = frag_damage * autocvar_g_buffs_vengeance_damage_multiplier; - dmgent.enemy = frag_attacker; - dmgent.owner = frag_target; - setthink(dmgent, buff_Vengeance_DelayedDamage); - dmgent.nextthink = time + 0.1; - } - - if(frag_target.buffs & BUFF_BASH.m_itemid) - if(frag_attacker != frag_target) - frag_force = '0 0 0'; - - if(frag_attacker.buffs & BUFF_BASH.m_itemid) - if(frag_force) - if(frag_attacker == frag_target) - frag_force *= autocvar_g_buffs_bash_force_self; - else - frag_force *= autocvar_g_buffs_bash_force; - - if(frag_attacker.buffs & BUFF_DISABILITY.m_itemid) - if(frag_target != frag_attacker) - frag_target.buff_disability_time = time + autocvar_g_buffs_disability_slowtime; - - if(frag_target.buffs & BUFF_INFERNO.m_itemid) - { - if(frag_deathtype == DEATH_FIRE.m_id) - frag_damage = 0; - if(frag_deathtype == DEATH_LAVA.m_id) - frag_damage *= 0.5; // TODO: cvarize? - } - - if(frag_attacker.buffs & BUFF_LUCK.m_itemid) - if(frag_attacker != frag_target) - if(autocvar_g_buffs_luck_damagemultiplier > 0) - if(random() <= autocvar_g_buffs_luck_chance) - frag_damage *= autocvar_g_buffs_luck_damagemultiplier; - - if(frag_attacker.buffs & BUFF_INFERNO.m_itemid) - if(frag_target != frag_attacker) { - float btime = buff_Inferno_CalculateTime( - frag_damage, - 0, - autocvar_g_buffs_inferno_burntime_min_time, - autocvar_g_buffs_inferno_burntime_target_damage, - autocvar_g_buffs_inferno_burntime_target_time, - autocvar_g_buffs_inferno_burntime_factor - ); - Fire_AddDamage(frag_target, frag_attacker, (frag_damage * autocvar_g_buffs_inferno_damagemultiplier), btime, DEATH_BUFF.m_id); - } - - // this... is ridiculous (TODO: fix!) - if(frag_attacker.buffs & BUFF_VAMPIRE.m_itemid) - if(!frag_target.vehicle) - if(!ITEM_DAMAGE_NEEDKILL(frag_deathtype)) - if(!IS_DEAD(frag_target)) - if(IS_PLAYER(frag_target) || IS_MONSTER(frag_target)) - if(frag_attacker != frag_target) - if(!STAT(FROZEN, frag_target)) - if(frag_target.takedamage) - if(DIFF_TEAM(frag_attacker, frag_target)) - { - frag_attacker.health = bound(0, frag_attacker.health + bound(0, frag_damage * autocvar_g_buffs_vampire_damage_steal, frag_target.health), g_pickup_healthsmall_max); - if(frag_target.armorvalue) - frag_attacker.armorvalue = bound(0, frag_attacker.armorvalue + bound(0, frag_damage * autocvar_g_buffs_vampire_damage_steal, frag_target.armorvalue), g_pickup_armorsmall_max); - } - - M_ARGV(4, float) = frag_damage; - M_ARGV(6, vector) = frag_force; -} - -MUTATOR_HOOKFUNCTION(buffs, PlayerSpawn) -{ - entity player = M_ARGV(0, entity); - - player.buffs = 0; - player.buff_time = 0; - // reset timers here to prevent them continuing after re-spawn - player.buff_disability_time = 0; - player.buff_disability_effect_time = 0; -} - -.float stat_sv_maxspeed; -.float stat_sv_airspeedlimit_nonqw; -.float stat_sv_jumpvelocity; - -MUTATOR_HOOKFUNCTION(buffs, PlayerPhysics) -{ - entity player = M_ARGV(0, entity); - - if(player.buffs & BUFF_SPEED.m_itemid) - { - player.stat_sv_maxspeed *= autocvar_g_buffs_speed_speed; - player.stat_sv_airspeedlimit_nonqw *= autocvar_g_buffs_speed_speed; - } - - if(time < player.buff_disability_time) - { - player.stat_sv_maxspeed *= autocvar_g_buffs_disability_speed; - player.stat_sv_airspeedlimit_nonqw *= autocvar_g_buffs_disability_speed; - } - - if(player.buffs & BUFF_JUMP.m_itemid) - { - // automatically reset, no need to worry - player.stat_sv_jumpvelocity = autocvar_g_buffs_jump_height; - } -} - -MUTATOR_HOOKFUNCTION(buffs, PlayerJump) -{ - entity player = M_ARGV(0, entity); - - if(player.buffs & BUFF_JUMP.m_itemid) - M_ARGV(1, float) = autocvar_g_buffs_jump_height; -} - -MUTATOR_HOOKFUNCTION(buffs, MonsterMove) -{ - entity mon = M_ARGV(0, entity); - - if(time < mon.buff_disability_time) - { - M_ARGV(1, float) *= autocvar_g_buffs_disability_speed; // run speed - M_ARGV(2, float) *= autocvar_g_buffs_disability_speed; // walk speed - } -} - -MUTATOR_HOOKFUNCTION(buffs, PlayerDies) -{ - entity frag_target = M_ARGV(2, entity); - - if(frag_target.buffs) - { - int buffid = buff_FirstFromFlags(frag_target.buffs).m_id; - Send_Notification(NOTIF_ALL_EXCEPT, frag_target, MSG_INFO, INFO_ITEM_BUFF_LOST, frag_target.netname, buffid); - frag_target.buffs = 0; - - if(frag_target.buff_model) - { - delete(frag_target.buff_model); - frag_target.buff_model = NULL; - } - } -} - -MUTATOR_HOOKFUNCTION(buffs, PlayerUseKey, CBC_ORDER_FIRST) -{ - if(MUTATOR_RETURNVALUE || gameover) { return; } - - entity player = M_ARGV(0, entity); - - if(player.buffs) - { - int buffid = buff_FirstFromFlags(player.buffs).m_id; - Send_Notification(NOTIF_ONE, player, MSG_MULTI, ITEM_BUFF_DROP, buffid); - Send_Notification(NOTIF_ALL_EXCEPT, player, MSG_INFO, INFO_ITEM_BUFF_LOST, player.netname, buffid); - - player.buffs = 0; - player.buff_time = 0; // already notified - sound(player, CH_TRIGGER, SND_BUFF_LOST, VOL_BASE, ATTN_NORM); - return true; - } -} - -MUTATOR_HOOKFUNCTION(buffs, ForbidThrowCurrentWeapon) -{ - if(MUTATOR_RETURNVALUE || gameover) { return; } - entity player = M_ARGV(0, entity); - - if(player.buffs & BUFF_SWAPPER.m_itemid) - { - float best_distance = autocvar_g_buffs_swapper_range; - entity closest = NULL; - FOREACH_CLIENT(IS_PLAYER(it), LAMBDA( - if(!IS_DEAD(it) && !STAT(FROZEN, it) && !it.vehicle) - if(DIFF_TEAM(it, player)) - { - float test = vlen2(player.origin - it.origin); - if(test <= best_distance * best_distance) - { - best_distance = sqrt(test); - closest = it; - } - } - )); - - if(closest) - { - vector my_org, my_vel, my_ang, their_org, their_vel, their_ang; - - my_org = player.origin; - my_vel = player.velocity; - my_ang = player.angles; - their_org = closest.origin; - their_vel = closest.velocity; - their_ang = closest.angles; - - Drop_Special_Items(closest); - - MUTATOR_CALLHOOK(PortalTeleport, player); // initiate flag dropper - - setorigin(player, their_org); - setorigin(closest, my_org); - - closest.velocity = my_vel; - closest.angles = my_ang; - closest.fixangle = true; - closest.oldorigin = my_org; - closest.oldvelocity = my_vel; - player.velocity = their_vel; - player.angles = their_ang; - player.fixangle = true; - player.oldorigin = their_org; - player.oldvelocity = their_vel; - - // set pusher so player gets the kill if they fall into void - closest.pusher = player; - closest.pushltime = time + autocvar_g_maxpushtime; - closest.istypefrag = PHYS_INPUT_BUTTON_CHAT(closest); - - Send_Effect(EFFECT_ELECTRO_COMBO, their_org, '0 0 0', 1); - Send_Effect(EFFECT_ELECTRO_COMBO, my_org, '0 0 0', 1); - - sound(player, CH_TRIGGER, SND_KA_RESPAWN, VOL_BASE, ATTEN_NORM); - sound(closest, CH_TRIGGER, SND_KA_RESPAWN, VOL_BASE, ATTEN_NORM); - - // TODO: add a counter to handle how many times one can teleport, and a delay to prevent spam - player.buffs = 0; - return true; - } - } -} - -bool buffs_RemovePlayer(entity player) -{ - if(player.buff_model) - { - delete(player.buff_model); - player.buff_model = NULL; - } - - // also reset timers here to prevent them continuing after spectating - player.buff_disability_time = 0; - player.buff_disability_effect_time = 0; - - return false; -} -MUTATOR_HOOKFUNCTION(buffs, MakePlayerObserver) { entity player = M_ARGV(0, entity); return buffs_RemovePlayer(player); } -MUTATOR_HOOKFUNCTION(buffs, ClientDisconnect) { entity player = M_ARGV(0, entity); return buffs_RemovePlayer(player); } - -MUTATOR_HOOKFUNCTION(buffs, CustomizeWaypoint) -{ - entity wp = M_ARGV(0, entity); - entity player = M_ARGV(1, entity); - - entity e = WaypointSprite_getviewentity(player); - - // if you have the invisibility powerup, sprites ALWAYS are restricted to your team - // but only apply this to real players, not to spectators - if((wp.owner.flags & FL_CLIENT) && (wp.owner.buffs & BUFF_INVISIBLE.m_itemid) && (e == player)) - if(DIFF_TEAM(wp.owner, e)) - return true; -} - -MUTATOR_HOOKFUNCTION(buffs, OnEntityPreSpawn, CBC_ORDER_LAST) -{ - entity ent = M_ARGV(0, entity); - - if(autocvar_g_buffs_replace_powerups) - switch(ent.classname) - { - case "item_strength": - case "item_invincible": - { - entity e = spawn(); - buff_SpawnReplacement(e, ent); - return true; - } - } -} - -MUTATOR_HOOKFUNCTION(buffs, WeaponRateFactor) -{ - entity player = M_ARGV(1, entity); - - if(player.buffs & BUFF_SPEED.m_itemid) - M_ARGV(0, float) *= autocvar_g_buffs_speed_rate; - - if(time < player.buff_disability_time) - M_ARGV(0, float) *= autocvar_g_buffs_disability_rate; -} - -MUTATOR_HOOKFUNCTION(buffs, WeaponSpeedFactor) -{ - entity player = M_ARGV(1, entity); - - if(player.buffs & BUFF_SPEED.m_itemid) - M_ARGV(0, float) *= autocvar_g_buffs_speed_weaponspeed; - - if(time < player.buff_disability_time) - M_ARGV(0, float) *= autocvar_g_buffs_disability_weaponspeed; -} - -MUTATOR_HOOKFUNCTION(buffs, PlayerPreThink) -{ - entity player = M_ARGV(0, entity); - - if(gameover || IS_DEAD(player)) { return; } - - if(time < player.buff_disability_time) - if(time >= player.buff_disability_effect_time) - { - Send_Effect(EFFECT_SMOKING, player.origin + ((player.mins + player.maxs) * 0.5), '0 0 0', 1); - player.buff_disability_effect_time = time + 0.5; - } - - // handle buff lost status - // 1: notify everyone else - // 2: notify carrier as well - int buff_lost = 0; - - if(player.buff_time && player.buffs) - if(time >= player.buff_time) - { - player.buff_time = 0; - buff_lost = 2; - } - - if(STAT(FROZEN, player)) { buff_lost = 1; } - - if(buff_lost) - { - if(player.buffs) - { - int buffid = buff_FirstFromFlags(player.buffs).m_id; - if(buff_lost == 2) - { - Send_Notification(NOTIF_ONE, player, MSG_MULTI, ITEM_BUFF_DROP, buffid); // TODO: special timeout message? - sound(player, CH_TRIGGER, SND_BUFF_LOST, VOL_BASE, ATTN_NORM); - } - else - Send_Notification(NOTIF_ALL_EXCEPT, player, MSG_INFO, INFO_ITEM_BUFF_LOST, player.netname, buffid); - player.buffs = 0; - } - } - - if(player.buffs & BUFF_MAGNET.m_itemid) - { - vector pickup_size; - FOREACH_ENTITY_FLAGS(flags, FL_ITEM, - { - if(it.buffs) - pickup_size = '1 1 1' * autocvar_g_buffs_magnet_range_buff; - else - pickup_size = '1 1 1' * autocvar_g_buffs_magnet_range_item; - - if(boxesoverlap(player.absmin - pickup_size, player.absmax + pickup_size, it.absmin, it.absmax)) - { - if(gettouch(it)) - gettouch(it)(it, player); - } - }); - } - - if(player.buffs & BUFF_AMMO.m_itemid) - if(player.clip_size) - player.clip_load = player.(weapon_load[PS(player).m_switchweapon.m_id]) = player.clip_size; - - if((player.buffs & BUFF_INVISIBLE.m_itemid) && (player.oldbuffs & BUFF_INVISIBLE.m_itemid)) - if(player.alpha != autocvar_g_buffs_invisible_alpha) - player.alpha = autocvar_g_buffs_invisible_alpha; // powerups reset alpha, so we must enforce this (TODO) - - if(player.buffs & BUFF_MEDIC.m_itemid) - if(time >= player.buff_medic_healtime) - { - buff_Medic_Heal(player); - player.buff_medic_healtime = time + autocvar_g_buffs_medic_heal_delay; - } - -#define BUFF_ONADD(b) if ( (player.buffs & (b).m_itemid) && !(player.oldbuffs & (b).m_itemid)) -#define BUFF_ONREM(b) if (!(player.buffs & (b).m_itemid) && (player.oldbuffs & (b).m_itemid)) - - if(player.buffs != player.oldbuffs) - { - entity buff = buff_FirstFromFlags(player.buffs); - float bufftime = buff != BUFF_Null ? buff.m_time(buff) : 0; - player.buff_time = (bufftime) ? time + bufftime : 0; - - BUFF_ONADD(BUFF_AMMO) - { - player.buff_ammo_prev_infitems = (player.items & IT_UNLIMITED_WEAPON_AMMO); - player.items |= IT_UNLIMITED_WEAPON_AMMO; - - if(player.clip_load) - player.buff_ammo_prev_clipload = player.clip_load; - player.clip_load = player.(weapon_load[PS(player).m_switchweapon.m_id]) = player.clip_size; - } - - BUFF_ONREM(BUFF_AMMO) - { - if(player.buff_ammo_prev_infitems) - player.items |= IT_UNLIMITED_WEAPON_AMMO; - else - player.items &= ~IT_UNLIMITED_WEAPON_AMMO; - - if(player.buff_ammo_prev_clipload) - player.clip_load = player.buff_ammo_prev_clipload; - } - - BUFF_ONADD(BUFF_INVISIBLE) - { - if(time < player.strength_finished && g_instagib) - player.alpha = autocvar_g_instagib_invis_alpha; - else - player.alpha = player.buff_invisible_prev_alpha; - player.alpha = autocvar_g_buffs_invisible_alpha; - } - - BUFF_ONREM(BUFF_INVISIBLE) - player.alpha = player.buff_invisible_prev_alpha; - - player.oldbuffs = player.buffs; - if(player.buffs) - { - if(!player.buff_model) - buffs_BuffModel_Spawn(player); - - player.buff_model.color = buff.m_color; - player.buff_model.glowmod = buff_GlowColor(player.buff_model); - player.buff_model.skin = buff.m_skin; - - player.effects |= EF_NOSHADOW; - } - else - { - delete(player.buff_model); - player.buff_model = NULL; - - player.effects &= ~(EF_NOSHADOW); - } - } - - if(player.buff_model) - { - player.buff_model.effects = player.effects; - player.buff_model.effects |= EF_LOWPRECISION; - player.buff_model.effects = player.buff_model.effects & EFMASK_CHEAP; // eat performance - - player.buff_model.alpha = player.alpha; - } - -#undef BUFF_ONADD -#undef BUFF_ONREM -} - -MUTATOR_HOOKFUNCTION(buffs, SpectateCopy) -{ - entity spectatee = M_ARGV(0, entity); - entity client = M_ARGV(1, entity); - - client.buffs = spectatee.buffs; -} - -MUTATOR_HOOKFUNCTION(buffs, VehicleEnter) -{ - entity player = M_ARGV(0, entity); - entity veh = M_ARGV(1, entity); - - veh.buffs = player.buffs; - player.buffs = 0; - veh.buff_time = max(0, player.buff_time - time); - player.buff_time = 0; -} - -MUTATOR_HOOKFUNCTION(buffs, VehicleExit) -{ - entity player = M_ARGV(0, entity); - entity veh = M_ARGV(1, entity); - - player.buffs = player.oldbuffs = veh.buffs; - veh.buffs = 0; - player.buff_time = time + veh.buff_time; - veh.buff_time = 0; -} - -MUTATOR_HOOKFUNCTION(buffs, PlayerRegen) -{ - entity player = M_ARGV(0, entity); - - if(player.buffs & BUFF_MEDIC.m_itemid) - { - M_ARGV(2, float) = autocvar_g_buffs_medic_rot; // rot_mod - M_ARGV(4, float) = M_ARGV(1, float) = autocvar_g_buffs_medic_max; // limit_mod = max_mod - M_ARGV(2, float) = autocvar_g_buffs_medic_regen; // regen_mod - } - - if(player.buffs & BUFF_SPEED.m_itemid) - M_ARGV(2, float) = autocvar_g_buffs_speed_regen; // regen_mod -} - -REPLICATE(cvar_cl_buffs_autoreplace, bool, "cl_buffs_autoreplace"); - -MUTATOR_HOOKFUNCTION(buffs, BuildMutatorsString) -{ - M_ARGV(0, string) = strcat(M_ARGV(0, string), ":Buffs"); -} - -MUTATOR_HOOKFUNCTION(buffs, BuildMutatorsPrettyString) +string BUFF_NAME(int i) { - M_ARGV(0, string) = strcat(M_ARGV(0, string), ", Buffs"); + Buff b = Buffs_from(i); + return sprintf("%s%s", rgb_to_hexcolor(b.m_color), b.m_prettyName); } -void buffs_DelayedInit(entity this) +entity buff_FirstFromFlags(int _buffs) { - if(autocvar_g_buffs_spawn_count > 0) - if(find(NULL, classname, "item_buff") == NULL) - { - float i; - for(i = 0; i < autocvar_g_buffs_spawn_count; ++i) - { - entity e = spawn(); - e.spawnflags |= 64; // always randomize - e.velocity = randomvec() * 250; // this gets reset anyway if random location works - buff_Init(e); - } - } + if (flags) + { + FOREACH(Buffs, it.m_itemid & _buffs, LAMBDA(return it)); + } + return BUFF_Null; } -#endif diff --git a/qcsrc/common/mutators/mutator/buffs/buffs.qh b/qcsrc/common/mutators/mutator/buffs/buffs.qh new file mode 100644 index 0000000000..89c550f7a9 --- /dev/null +++ b/qcsrc/common/mutators/mutator/buffs/buffs.qh @@ -0,0 +1,68 @@ +#pragma once + +#include +#include + +REGISTER_WAYPOINT(Buff, _("Buff"), '1 0.5 0', 1); +REGISTER_RADARICON(Buff, 1); + +REGISTRY(Buffs, BITS(5)) +#define Buffs_from(i) _Buffs_from(i, BUFF_Null) +REGISTER_REGISTRY(Buffs) +REGISTRY_CHECK(Buffs) + +#define REGISTER_BUFF(id) \ + REGISTER(Buffs, BUFF_##id, m_id, NEW(Buff)) + +#include +CLASS(Buff, Pickup) + /** bit index */ + ATTRIB(Buff, m_itemid, int, 0); + ATTRIB(Buff, m_name, string, "buff"); + ATTRIB(Buff, m_color, vector, '1 1 1'); + ATTRIB(Buff, m_prettyName, string, "Buff"); + ATTRIB(Buff, m_skin, int, 0); + ATTRIB(Buff, m_sprite, string, ""); + METHOD(Buff, display, void(entity this, void(string name, string icon) returns)) { + returns(this.m_prettyName, sprintf("/gfx/hud/%s/buff_%s", cvar_string("menu_skin"), this.m_name)); + } +#ifdef SVQC + METHOD(Buff, m_time, float(Buff this)) + { return cvar(strcat("g_buffs_", this.netname, "_time")); } +#endif +ENDCLASS(Buff) + +STATIC_INIT(REGISTER_BUFFS) { + FOREACH(Buffs, true, { + it.netname = it.m_name; \ + it.m_itemid = BIT(it.m_id - 1); \ + it.m_sprite = strzone(strcat("buff-", it.m_name)); \ + }); +} + +#ifdef SVQC + // .int buffs = _STAT(BUFFS); + void buff_Init(entity ent); + void buff_Init_Compat(entity ent, entity replacement); + #define BUFF_SPAWNFUNC(e, b, t) spawnfunc(item_buff_##e) { \ + this.buffs = b.m_itemid; \ + this.team = t; \ + buff_Init(this); \ + } + #define BUFF_SPAWNFUNCS(e, b) \ + BUFF_SPAWNFUNC(e, b, 0) \ + BUFF_SPAWNFUNC(e##_team1, b, NUM_TEAM_1) \ + BUFF_SPAWNFUNC(e##_team2, b, NUM_TEAM_2) \ + BUFF_SPAWNFUNC(e##_team3, b, NUM_TEAM_3) \ + BUFF_SPAWNFUNC(e##_team4, b, NUM_TEAM_4) + #define BUFF_SPAWNFUNC_Q3TA_COMPAT(o, r) spawnfunc(item_##o) { buff_Init_Compat(this, r); } +#else + #define BUFF_SPAWNFUNC(e, b, t) + #define BUFF_SPAWNFUNCS(e, b) + #define BUFF_SPAWNFUNC_Q3TA_COMPAT(o, r) +#endif + +REGISTER_BUFF(Null); +BUFF_SPAWNFUNCS(random, BUFF_Null) + +#include "all.inc" diff --git a/qcsrc/common/mutators/mutator/buffs/cl_buffs.qc b/qcsrc/common/mutators/mutator/buffs/cl_buffs.qc new file mode 100644 index 0000000000..ca14753727 --- /dev/null +++ b/qcsrc/common/mutators/mutator/buffs/cl_buffs.qc @@ -0,0 +1,22 @@ +#include "cl_buffs.qh" + +REGISTER_MUTATOR(cl_buffs, true); +MUTATOR_HOOKFUNCTION(cl_buffs, HUD_Powerups_add) +{ + int allBuffs = STAT(BUFFS); + FOREACH(Buffs, it.m_itemid & allBuffs, LAMBDA( + addPowerupItem(it.m_prettyName, strcat("buff_", it.m_name), it.m_color, bound(0, STAT(BUFF_TIME) - time, 99), 60); + )); +} +MUTATOR_HOOKFUNCTION(cl_buffs, WP_Format) +{ + entity this = M_ARGV(0, entity); + string s = M_ARGV(1, string); + if (s == WP_Buff.netname || s == RADARICON_Buff.netname) + { + Buff b = Buffs_from(this.wp_extra); + M_ARGV(2, vector) = b.m_color; + M_ARGV(3, string) = b.m_prettyName; + return true; + } +} diff --git a/qcsrc/common/mutators/mutator/buffs/cl_buffs.qh b/qcsrc/common/mutators/mutator/buffs/cl_buffs.qh new file mode 100644 index 0000000000..c93902291d --- /dev/null +++ b/qcsrc/common/mutators/mutator/buffs/cl_buffs.qh @@ -0,0 +1,3 @@ +#pragma once + +#include "buffs.qh" diff --git a/qcsrc/common/mutators/mutator/buffs/module.inc b/qcsrc/common/mutators/mutator/buffs/module.inc deleted file mode 100644 index c24892836a..0000000000 --- a/qcsrc/common/mutators/mutator/buffs/module.inc +++ /dev/null @@ -1,46 +0,0 @@ -#include "all.qc" -#ifdef SVQC -#include "buffs.qc" -#endif - -#ifdef IMPLEMENTATION - -string BUFF_NAME(int i) -{ - Buff b = Buffs_from(i); - return sprintf("%s%s", rgb_to_hexcolor(b.m_color), b.m_prettyName); -} - -entity buff_FirstFromFlags(int _buffs) -{ - if (flags) - { - FOREACH(Buffs, it.m_itemid & _buffs, LAMBDA(return it)); - } - return BUFF_Null; -} - -#ifdef CSQC -REGISTER_MUTATOR(cl_buffs, true); -MUTATOR_HOOKFUNCTION(cl_buffs, HUD_Powerups_add) -{ - int allBuffs = STAT(BUFFS); - FOREACH(Buffs, it.m_itemid & allBuffs, LAMBDA( - addPowerupItem(it.m_prettyName, strcat("buff_", it.m_name), it.m_color, bound(0, STAT(BUFF_TIME) - time, 99), 60); - )); -} -MUTATOR_HOOKFUNCTION(cl_buffs, WP_Format) -{ - entity this = M_ARGV(0, entity); - string s = M_ARGV(1, string); - if (s == WP_Buff.netname || s == RADARICON_Buff.netname) - { - Buff b = Buffs_from(this.wp_extra); - M_ARGV(2, vector) = b.m_color; - M_ARGV(3, string) = b.m_prettyName; - return true; - } -} - -#endif -#endif diff --git a/qcsrc/common/mutators/mutator/buffs/sv_buffs.qc b/qcsrc/common/mutators/mutator/buffs/sv_buffs.qc new file mode 100644 index 0000000000..0ea6b057a9 --- /dev/null +++ b/qcsrc/common/mutators/mutator/buffs/sv_buffs.qc @@ -0,0 +1,1002 @@ +#include "sv_buffs.qh" + +#include +#include + +.float buff_time = _STAT(BUFF_TIME); +void buffs_DelayedInit(entity this); + +REGISTER_MUTATOR(buffs, cvar("g_buffs")) +{ + MUTATOR_ONADD + { + InitializeEntity(NULL, buffs_DelayedInit, INITPRIO_FINDTARGET); + } +} + +bool buffs_BuffModel_Customize(entity this, entity client) +{ + entity player, myowner; + bool same_team; + + player = WaypointSprite_getviewentity(client); + myowner = this.owner; + same_team = (SAME_TEAM(player, myowner) || SAME_TEAM(player, myowner)); + + if(myowner.alpha <= 0.5 && !same_team && myowner.alpha != 0) + return false; + + if(MUTATOR_CALLHOOK(BuffModel_Customize, this, player)) + return false; + + if(player == myowner || (IS_SPEC(client) && client.enemy == myowner)) + { + // somewhat hide the model, but keep the glow + this.effects = 0; + this.alpha = -1; + } + else + { + this.effects = EF_FULLBRIGHT | EF_LOWPRECISION; + this.alpha = 1; + } + return true; +} + +void buffs_BuffModel_Spawn(entity player) +{ + player.buff_model = spawn(); + setmodel(player.buff_model, MDL_BUFF); + setsize(player.buff_model, '0 0 -40', '0 0 40'); + setattachment(player.buff_model, player, ""); + setorigin(player.buff_model, '0 0 1' * (player.buff_model.maxs.z * 1)); + player.buff_model.owner = player; + player.buff_model.scale = 0.7; + player.buff_model.pflags = PFLAGS_FULLDYNAMIC; + player.buff_model.light_lev = 200; + setcefc(player.buff_model, buffs_BuffModel_Customize); +} + +vector buff_GlowColor(entity buff) +{ + //if(buff.team) { return Team_ColorRGB(buff.team); } + return buff.m_color; +} + +void buff_Effect(entity player, string eff) +{ + if(!autocvar_g_buffs_effects) { return; } + + if(time >= player.buff_effect_delay) + { + Send_Effect_(eff, player.origin + ((player.mins + player.maxs) * 0.5), '0 0 0', 1); + player.buff_effect_delay = time + 0.05; // prevent spam + } +} + +// buff item +bool buff_Waypoint_visible_for_player(entity this, entity player, entity view) +{ + if(!this.owner.buff_active && !this.owner.buff_activetime) + return false; + + if (view.buffs) + { + return view.cvar_cl_buffs_autoreplace == false || view.buffs != this.owner.buffs; + } + + return WaypointSprite_visible_for_player(this, player, view); +} + +void buff_Waypoint_Spawn(entity e) +{ + entity buff = buff_FirstFromFlags(e.buffs); + entity wp = WaypointSprite_Spawn(WP_Buff, 0, autocvar_g_buffs_waypoint_distance, e, '0 0 1' * e.maxs.z, NULL, e.team, e, buff_waypoint, true, RADARICON_Buff); + wp.wp_extra = buff.m_id; + WaypointSprite_UpdateTeamRadar(e.buff_waypoint, RADARICON_Buff, e.glowmod); + e.buff_waypoint.waypointsprite_visible_for_player = buff_Waypoint_visible_for_player; +} + +void buff_SetCooldown(entity this, float cd) +{ + cd = max(0, cd); + + if(!this.buff_waypoint) + buff_Waypoint_Spawn(this); + + WaypointSprite_UpdateBuildFinished(this.buff_waypoint, time + cd); + this.buff_activetime = cd; + this.buff_active = !cd; +} + +void buff_Respawn(entity this) +{ + if(gameover) { return; } + + vector oldbufforigin = this.origin; + this.velocity = '0 0 200'; + + if(!MoveToRandomMapLocation(this, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, + ((autocvar_g_buffs_random_location_attempts > 0) ? autocvar_g_buffs_random_location_attempts : 10), 1024, 256)) + { + entity spot = SelectSpawnPoint(this, true); + setorigin(this, spot.origin); + this.velocity = ((randomvec() * 100) + '0 0 200'); + this.angles = spot.angles; + } + + tracebox(this.origin, this.mins * 1.5, this.maxs * 1.5, this.origin, MOVE_NOMONSTERS, this); + + setorigin(this, trace_endpos); // attempt to unstick + + set_movetype(this, MOVETYPE_TOSS); + + makevectors(this.angles); + this.angles = '0 0 0'; + if(autocvar_g_buffs_random_lifetime > 0) + this.lifetime = time + autocvar_g_buffs_random_lifetime; + + Send_Effect(EFFECT_ELECTRO_COMBO, oldbufforigin + ((this.mins + this.maxs) * 0.5), '0 0 0', 1); + Send_Effect(EFFECT_ELECTRO_COMBO, CENTER_OR_VIEWOFS(this), '0 0 0', 1); + + WaypointSprite_Ping(this.buff_waypoint); + + sound(this, CH_TRIGGER, SND_KA_RESPAWN, VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere) +} + +void buff_Touch(entity this, entity toucher) +{ + if(gameover) { return; } + + if(ITEM_TOUCH_NEEDKILL()) + { + buff_Respawn(this); + return; + } + + if((this.team && DIFF_TEAM(toucher, this)) + || (STAT(FROZEN, toucher)) + || (toucher.vehicle) + || (!this.buff_active) + ) + { + // can't touch this + return; + } + + if(MUTATOR_CALLHOOK(BuffTouch, this, toucher)) + return; + toucher = M_ARGV(1, entity); + + if(!IS_PLAYER(toucher)) + return; // incase mutator changed toucher + + if (toucher.buffs) + { + if (toucher.cvar_cl_buffs_autoreplace && toucher.buffs != this.buffs) + { + int buffid = buff_FirstFromFlags(toucher.buffs).m_id; + //Send_Notification(NOTIF_ONE, toucher, MSG_MULTI, ITEM_BUFF_DROP, toucher.buffs); + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ITEM_BUFF_LOST, toucher.netname, buffid); + + toucher.buffs = 0; + //sound(toucher, CH_TRIGGER, SND_BUFF_LOST, VOL_BASE, ATTN_NORM); + } + else { return; } // do nothing + } + + this.owner = toucher; + this.buff_active = false; + this.lifetime = 0; + int buffid = buff_FirstFromFlags(this.buffs).m_id; + Send_Notification(NOTIF_ONE, toucher, MSG_MULTI, ITEM_BUFF_GOT, buffid); + Send_Notification(NOTIF_ALL_EXCEPT, toucher, MSG_INFO, INFO_ITEM_BUFF, toucher.netname, buffid); + + Send_Effect(EFFECT_ITEM_PICKUP, CENTER_OR_VIEWOFS(this), '0 0 0', 1); + sound(toucher, CH_TRIGGER, SND_SHIELD_RESPAWN, VOL_BASE, ATTN_NORM); + toucher.buffs |= (this.buffs); +} + +float buff_Available(entity buff) +{ + if (buff == BUFF_Null) + return false; + if (buff == BUFF_AMMO && ((start_items & IT_UNLIMITED_WEAPON_AMMO) || (start_items & IT_UNLIMITED_AMMO) || (cvar("g_melee_only")))) + return false; + if (buff == BUFF_VAMPIRE && cvar("g_vampire")) + return false; + return cvar(strcat("g_buffs_", buff.m_name)); +} + +.int buff_seencount; + +void buff_NewType(entity ent, float cb) +{ + RandomSelection_Init(); + FOREACH(Buffs, buff_Available(it), LAMBDA( + it.buff_seencount += 1; + // if it's already been chosen, give it a lower priority + RandomSelection_Add(NULL, it.m_itemid, string_null, 1, max(0.2, 1 / it.buff_seencount)); + )); + ent.buffs = RandomSelection_chosen_float; +} + +void buff_Think(entity this) +{ + if(this.buffs != this.oldbuffs) + { + entity buff = buff_FirstFromFlags(this.buffs); + this.color = buff.m_color; + this.glowmod = buff_GlowColor(buff); + this.skin = buff.m_skin; + + setmodel(this, MDL_BUFF); + + if(this.buff_waypoint) + { + //WaypointSprite_Disown(this.buff_waypoint, 1); + WaypointSprite_Kill(this.buff_waypoint); + buff_Waypoint_Spawn(this); + if(this.buff_activetime) + WaypointSprite_UpdateBuildFinished(this.buff_waypoint, time + this.buff_activetime - frametime); + } + + this.oldbuffs = this.buffs; + } + + if(!gameover) + if((round_handler_IsActive() && !round_handler_IsRoundStarted()) || time >= game_starttime) + if(!this.buff_activetime_updated) + { + buff_SetCooldown(this, this.buff_activetime); + this.buff_activetime_updated = true; + } + + if(!this.buff_active && !this.buff_activetime) + if(!this.owner || STAT(FROZEN, this.owner) || IS_DEAD(this.owner) || !this.owner.iscreature || !(this.owner.buffs & this.buffs)) + { + buff_SetCooldown(this, autocvar_g_buffs_cooldown_respawn + frametime); + this.owner = NULL; + if(autocvar_g_buffs_randomize) + buff_NewType(this, this.buffs); + + if(autocvar_g_buffs_random_location || (this.spawnflags & 64)) + buff_Respawn(this); + } + + if(this.buff_activetime) + if(!gameover) + if((round_handler_IsActive() && !round_handler_IsRoundStarted()) || time >= game_starttime) + { + this.buff_activetime = max(0, this.buff_activetime - frametime); + + if(!this.buff_activetime) + { + this.buff_active = true; + sound(this, CH_TRIGGER, SND_STRENGTH_RESPAWN, VOL_BASE, ATTN_NORM); + Send_Effect(EFFECT_ITEM_RESPAWN, CENTER_OR_VIEWOFS(this), '0 0 0', 1); + } + } + + if(this.buff_active) + { + if(this.team && !this.buff_waypoint) + buff_Waypoint_Spawn(this); + + if(this.lifetime) + if(time >= this.lifetime) + buff_Respawn(this); + } + + this.nextthink = time; + //this.angles_y = time * 110.1; +} + +void buff_Waypoint_Reset(entity this) +{ + WaypointSprite_Kill(this.buff_waypoint); + + if(this.buff_activetime) { buff_Waypoint_Spawn(this); } +} + +void buff_Reset(entity this) +{ + if(autocvar_g_buffs_randomize) + buff_NewType(this, this.buffs); + this.owner = NULL; + buff_SetCooldown(this, autocvar_g_buffs_cooldown_activate); + buff_Waypoint_Reset(this); + this.buff_activetime_updated = false; + + if(autocvar_g_buffs_random_location || (this.spawnflags & 64)) + buff_Respawn(this); +} + +bool buff_Customize(entity this, entity client) +{ + entity player = WaypointSprite_getviewentity(client); + if(!this.buff_active || (this.team && DIFF_TEAM(player, this))) + { + this.alpha = 0.3; + if(this.effects & EF_FULLBRIGHT) { this.effects &= ~(EF_FULLBRIGHT); } + this.pflags = 0; + } + else + { + this.alpha = 1; + if(!(this.effects & EF_FULLBRIGHT)) { this.effects |= EF_FULLBRIGHT; } + this.light_lev = 220 + 36 * sin(time); + this.pflags = PFLAGS_FULLDYNAMIC; + } + return true; +} + +void buff_Init(entity this) +{ + if(!cvar("g_buffs")) { delete(this); return; } + + if(!teamplay && this.team) { this.team = 0; } + + entity buff = buff_FirstFromFlags(this.buffs); + + if(!this.buffs || buff_Available(buff)) + buff_NewType(this, 0); + + this.classname = "item_buff"; + this.solid = SOLID_TRIGGER; + this.flags = FL_ITEM; + setthink(this, buff_Think); + settouch(this, buff_Touch); + this.reset = buff_Reset; + this.nextthink = time + 0.1; + this.gravity = 1; + set_movetype(this, MOVETYPE_TOSS); + this.scale = 1; + this.skin = buff.m_skin; + this.effects = EF_FULLBRIGHT | EF_STARDUST | EF_NOSHADOW; + this.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY; + setcefc(this, buff_Customize); + //this.gravity = 100; + this.color = buff.m_color; + this.glowmod = buff_GlowColor(this); + buff_SetCooldown(this, autocvar_g_buffs_cooldown_activate + game_starttime); + this.buff_active = !this.buff_activetime; + this.pflags = PFLAGS_FULLDYNAMIC; + + if(this.spawnflags & 1) + this.noalign = true; + + if(this.noalign) + set_movetype(this, MOVETYPE_NONE); // reset by random location + + setmodel(this, MDL_BUFF); + setsize(this, BUFF_MIN, BUFF_MAX); + + if(cvar("g_buffs_random_location") || (this.spawnflags & 64)) + buff_Respawn(this); +} + +void buff_Init_Compat(entity ent, entity replacement) +{ + if (ent.spawnflags & 2) + ent.team = NUM_TEAM_1; + else if (ent.spawnflags & 4) + ent.team = NUM_TEAM_2; + + ent.buffs = replacement.m_itemid; + + buff_Init(ent); +} + +void buff_SpawnReplacement(entity ent, entity old) +{ + setorigin(ent, old.origin); + ent.angles = old.angles; + ent.noalign = (old.noalign || (old.spawnflags & 1)); + + buff_Init(ent); +} + +void buff_Vengeance_DelayedDamage(entity this) +{ + if(this.enemy) + Damage(this.enemy, this.owner, this.owner, this.dmg, DEATH_BUFF.m_id, this.enemy.origin, '0 0 0'); + + delete(this); + return; +} + +// note: only really useful in teamplay +void buff_Medic_Heal(entity this) +{ + FOREACH_CLIENT(IS_PLAYER(it) && it != this && vdist(it.origin - this.origin, <=, autocvar_g_buffs_medic_heal_range), + { + if(SAME_TEAM(it, this)) + if(it.health < autocvar_g_balance_health_regenstable) + { + Send_Effect(EFFECT_HEALING, it.origin, '0 0 0', 1); + it.health = bound(0, it.health + autocvar_g_buffs_medic_heal_amount, autocvar_g_balance_health_regenstable); + } + }); +} + +float buff_Inferno_CalculateTime(float x, float offset_x, float offset_y, float intersect_x, float intersect_y, float base) +{ + return offset_y + (intersect_y - offset_y) * logn(((x - offset_x) * ((base - 1) / intersect_x)) + 1, base); +} + +// mutator hooks +MUTATOR_HOOKFUNCTION(buffs, PlayerDamage_SplitHealthArmor) +{ + entity frag_target = M_ARGV(2, entity); + float frag_deathtype = M_ARGV(6, float); + float frag_damage = M_ARGV(7, float); + + if(frag_deathtype == DEATH_BUFF.m_id) { return; } + + if(frag_target.buffs & BUFF_RESISTANCE.m_itemid) + { + vector v = healtharmor_applydamage(50, autocvar_g_buffs_resistance_blockpercent, frag_deathtype, frag_damage); + M_ARGV(4, float) = v.x; // take + M_ARGV(5, float) = v.y; // save + } +} + +MUTATOR_HOOKFUNCTION(buffs, PlayerDamage_Calculate) +{ + entity frag_attacker = M_ARGV(1, entity); + entity frag_target = M_ARGV(2, entity); + float frag_deathtype = M_ARGV(3, float); + float frag_damage = M_ARGV(4, float); + vector frag_force = M_ARGV(6, vector); + + if(frag_deathtype == DEATH_BUFF.m_id) { return; } + + if(frag_target.buffs & BUFF_SPEED.m_itemid) + if(frag_target != frag_attacker) + frag_damage *= autocvar_g_buffs_speed_damage_take; + + if(frag_target.buffs & BUFF_MEDIC.m_itemid) + if((frag_target.health - frag_damage) <= 0) + if(!ITEM_DAMAGE_NEEDKILL(frag_deathtype)) + if(frag_attacker) + if(random() <= autocvar_g_buffs_medic_survive_chance) + frag_damage = max(5, frag_target.health - autocvar_g_buffs_medic_survive_health); + + if(frag_target.buffs & BUFF_JUMP.m_itemid) + if(frag_deathtype == DEATH_FALL.m_id) + frag_damage = 0; + + if(frag_target.buffs & BUFF_VENGEANCE.m_itemid) + if(frag_attacker) + if(frag_attacker != frag_target) + if(!ITEM_DAMAGE_NEEDKILL(frag_deathtype)) + { + entity dmgent = spawn(); + + dmgent.dmg = frag_damage * autocvar_g_buffs_vengeance_damage_multiplier; + dmgent.enemy = frag_attacker; + dmgent.owner = frag_target; + setthink(dmgent, buff_Vengeance_DelayedDamage); + dmgent.nextthink = time + 0.1; + } + + if(frag_target.buffs & BUFF_BASH.m_itemid) + if(frag_attacker != frag_target) + frag_force = '0 0 0'; + + if(frag_attacker.buffs & BUFF_BASH.m_itemid) + if(frag_force) + if(frag_attacker == frag_target) + frag_force *= autocvar_g_buffs_bash_force_self; + else + frag_force *= autocvar_g_buffs_bash_force; + + if(frag_attacker.buffs & BUFF_DISABILITY.m_itemid) + if(frag_target != frag_attacker) + frag_target.buff_disability_time = time + autocvar_g_buffs_disability_slowtime; + + if(frag_target.buffs & BUFF_INFERNO.m_itemid) + { + if(frag_deathtype == DEATH_FIRE.m_id) + frag_damage = 0; + if(frag_deathtype == DEATH_LAVA.m_id) + frag_damage *= 0.5; // TODO: cvarize? + } + + if(frag_attacker.buffs & BUFF_LUCK.m_itemid) + if(frag_attacker != frag_target) + if(autocvar_g_buffs_luck_damagemultiplier > 0) + if(random() <= autocvar_g_buffs_luck_chance) + frag_damage *= autocvar_g_buffs_luck_damagemultiplier; + + if(frag_attacker.buffs & BUFF_INFERNO.m_itemid) + if(frag_target != frag_attacker) { + float btime = buff_Inferno_CalculateTime( + frag_damage, + 0, + autocvar_g_buffs_inferno_burntime_min_time, + autocvar_g_buffs_inferno_burntime_target_damage, + autocvar_g_buffs_inferno_burntime_target_time, + autocvar_g_buffs_inferno_burntime_factor + ); + Fire_AddDamage(frag_target, frag_attacker, (frag_damage * autocvar_g_buffs_inferno_damagemultiplier), btime, DEATH_BUFF.m_id); + } + + // this... is ridiculous (TODO: fix!) + if(frag_attacker.buffs & BUFF_VAMPIRE.m_itemid) + if(!frag_target.vehicle) + if(!ITEM_DAMAGE_NEEDKILL(frag_deathtype)) + if(!IS_DEAD(frag_target)) + if(IS_PLAYER(frag_target) || IS_MONSTER(frag_target)) + if(frag_attacker != frag_target) + if(!STAT(FROZEN, frag_target)) + if(frag_target.takedamage) + if(DIFF_TEAM(frag_attacker, frag_target)) + { + frag_attacker.health = bound(0, frag_attacker.health + bound(0, frag_damage * autocvar_g_buffs_vampire_damage_steal, frag_target.health), g_pickup_healthsmall_max); + if(frag_target.armorvalue) + frag_attacker.armorvalue = bound(0, frag_attacker.armorvalue + bound(0, frag_damage * autocvar_g_buffs_vampire_damage_steal, frag_target.armorvalue), g_pickup_armorsmall_max); + } + + M_ARGV(4, float) = frag_damage; + M_ARGV(6, vector) = frag_force; +} + +MUTATOR_HOOKFUNCTION(buffs, PlayerSpawn) +{ + entity player = M_ARGV(0, entity); + + player.buffs = 0; + player.buff_time = 0; + // reset timers here to prevent them continuing after re-spawn + player.buff_disability_time = 0; + player.buff_disability_effect_time = 0; +} + +.float stat_sv_maxspeed; +.float stat_sv_airspeedlimit_nonqw; +.float stat_sv_jumpvelocity; + +MUTATOR_HOOKFUNCTION(buffs, PlayerPhysics) +{ + entity player = M_ARGV(0, entity); + + if(player.buffs & BUFF_SPEED.m_itemid) + { + player.stat_sv_maxspeed *= autocvar_g_buffs_speed_speed; + player.stat_sv_airspeedlimit_nonqw *= autocvar_g_buffs_speed_speed; + } + + if(time < player.buff_disability_time) + { + player.stat_sv_maxspeed *= autocvar_g_buffs_disability_speed; + player.stat_sv_airspeedlimit_nonqw *= autocvar_g_buffs_disability_speed; + } + + if(player.buffs & BUFF_JUMP.m_itemid) + { + // automatically reset, no need to worry + player.stat_sv_jumpvelocity = autocvar_g_buffs_jump_height; + } +} + +MUTATOR_HOOKFUNCTION(buffs, PlayerJump) +{ + entity player = M_ARGV(0, entity); + + if(player.buffs & BUFF_JUMP.m_itemid) + M_ARGV(1, float) = autocvar_g_buffs_jump_height; +} + +MUTATOR_HOOKFUNCTION(buffs, MonsterMove) +{ + entity mon = M_ARGV(0, entity); + + if(time < mon.buff_disability_time) + { + M_ARGV(1, float) *= autocvar_g_buffs_disability_speed; // run speed + M_ARGV(2, float) *= autocvar_g_buffs_disability_speed; // walk speed + } +} + +MUTATOR_HOOKFUNCTION(buffs, PlayerDies) +{ + entity frag_target = M_ARGV(2, entity); + + if(frag_target.buffs) + { + int buffid = buff_FirstFromFlags(frag_target.buffs).m_id; + Send_Notification(NOTIF_ALL_EXCEPT, frag_target, MSG_INFO, INFO_ITEM_BUFF_LOST, frag_target.netname, buffid); + frag_target.buffs = 0; + + if(frag_target.buff_model) + { + delete(frag_target.buff_model); + frag_target.buff_model = NULL; + } + } +} + +MUTATOR_HOOKFUNCTION(buffs, PlayerUseKey, CBC_ORDER_FIRST) +{ + if(MUTATOR_RETURNVALUE || gameover) { return; } + + entity player = M_ARGV(0, entity); + + if(player.buffs) + { + int buffid = buff_FirstFromFlags(player.buffs).m_id; + Send_Notification(NOTIF_ONE, player, MSG_MULTI, ITEM_BUFF_DROP, buffid); + Send_Notification(NOTIF_ALL_EXCEPT, player, MSG_INFO, INFO_ITEM_BUFF_LOST, player.netname, buffid); + + player.buffs = 0; + player.buff_time = 0; // already notified + sound(player, CH_TRIGGER, SND_BUFF_LOST, VOL_BASE, ATTN_NORM); + return true; + } +} + +MUTATOR_HOOKFUNCTION(buffs, ForbidThrowCurrentWeapon) +{ + if(MUTATOR_RETURNVALUE || gameover) { return; } + entity player = M_ARGV(0, entity); + + if(player.buffs & BUFF_SWAPPER.m_itemid) + { + float best_distance = autocvar_g_buffs_swapper_range; + entity closest = NULL; + FOREACH_CLIENT(IS_PLAYER(it), LAMBDA( + if(!IS_DEAD(it) && !STAT(FROZEN, it) && !it.vehicle) + if(DIFF_TEAM(it, player)) + { + float test = vlen2(player.origin - it.origin); + if(test <= best_distance * best_distance) + { + best_distance = sqrt(test); + closest = it; + } + } + )); + + if(closest) + { + vector my_org, my_vel, my_ang, their_org, their_vel, their_ang; + + my_org = player.origin; + my_vel = player.velocity; + my_ang = player.angles; + their_org = closest.origin; + their_vel = closest.velocity; + their_ang = closest.angles; + + Drop_Special_Items(closest); + + MUTATOR_CALLHOOK(PortalTeleport, player); // initiate flag dropper + + setorigin(player, their_org); + setorigin(closest, my_org); + + closest.velocity = my_vel; + closest.angles = my_ang; + closest.fixangle = true; + closest.oldorigin = my_org; + closest.oldvelocity = my_vel; + player.velocity = their_vel; + player.angles = their_ang; + player.fixangle = true; + player.oldorigin = their_org; + player.oldvelocity = their_vel; + + // set pusher so player gets the kill if they fall into void + closest.pusher = player; + closest.pushltime = time + autocvar_g_maxpushtime; + closest.istypefrag = PHYS_INPUT_BUTTON_CHAT(closest); + + Send_Effect(EFFECT_ELECTRO_COMBO, their_org, '0 0 0', 1); + Send_Effect(EFFECT_ELECTRO_COMBO, my_org, '0 0 0', 1); + + sound(player, CH_TRIGGER, SND_KA_RESPAWN, VOL_BASE, ATTEN_NORM); + sound(closest, CH_TRIGGER, SND_KA_RESPAWN, VOL_BASE, ATTEN_NORM); + + // TODO: add a counter to handle how many times one can teleport, and a delay to prevent spam + player.buffs = 0; + return true; + } + } +} + +bool buffs_RemovePlayer(entity player) +{ + if(player.buff_model) + { + delete(player.buff_model); + player.buff_model = NULL; + } + + // also reset timers here to prevent them continuing after spectating + player.buff_disability_time = 0; + player.buff_disability_effect_time = 0; + + return false; +} +MUTATOR_HOOKFUNCTION(buffs, MakePlayerObserver) { entity player = M_ARGV(0, entity); return buffs_RemovePlayer(player); } +MUTATOR_HOOKFUNCTION(buffs, ClientDisconnect) { entity player = M_ARGV(0, entity); return buffs_RemovePlayer(player); } + +MUTATOR_HOOKFUNCTION(buffs, CustomizeWaypoint) +{ + entity wp = M_ARGV(0, entity); + entity player = M_ARGV(1, entity); + + entity e = WaypointSprite_getviewentity(player); + + // if you have the invisibility powerup, sprites ALWAYS are restricted to your team + // but only apply this to real players, not to spectators + if((wp.owner.flags & FL_CLIENT) && (wp.owner.buffs & BUFF_INVISIBLE.m_itemid) && (e == player)) + if(DIFF_TEAM(wp.owner, e)) + return true; +} + +MUTATOR_HOOKFUNCTION(buffs, OnEntityPreSpawn, CBC_ORDER_LAST) +{ + entity ent = M_ARGV(0, entity); + + if(autocvar_g_buffs_replace_powerups) + switch(ent.classname) + { + case "item_strength": + case "item_invincible": + { + entity e = spawn(); + buff_SpawnReplacement(e, ent); + return true; + } + } +} + +MUTATOR_HOOKFUNCTION(buffs, WeaponRateFactor) +{ + entity player = M_ARGV(1, entity); + + if(player.buffs & BUFF_SPEED.m_itemid) + M_ARGV(0, float) *= autocvar_g_buffs_speed_rate; + + if(time < player.buff_disability_time) + M_ARGV(0, float) *= autocvar_g_buffs_disability_rate; +} + +MUTATOR_HOOKFUNCTION(buffs, WeaponSpeedFactor) +{ + entity player = M_ARGV(1, entity); + + if(player.buffs & BUFF_SPEED.m_itemid) + M_ARGV(0, float) *= autocvar_g_buffs_speed_weaponspeed; + + if(time < player.buff_disability_time) + M_ARGV(0, float) *= autocvar_g_buffs_disability_weaponspeed; +} + +MUTATOR_HOOKFUNCTION(buffs, PlayerPreThink) +{ + entity player = M_ARGV(0, entity); + + if(gameover || IS_DEAD(player)) { return; } + + if(time < player.buff_disability_time) + if(time >= player.buff_disability_effect_time) + { + Send_Effect(EFFECT_SMOKING, player.origin + ((player.mins + player.maxs) * 0.5), '0 0 0', 1); + player.buff_disability_effect_time = time + 0.5; + } + + // handle buff lost status + // 1: notify everyone else + // 2: notify carrier as well + int buff_lost = 0; + + if(player.buff_time && player.buffs) + if(time >= player.buff_time) + { + player.buff_time = 0; + buff_lost = 2; + } + + if(STAT(FROZEN, player)) { buff_lost = 1; } + + if(buff_lost) + { + if(player.buffs) + { + int buffid = buff_FirstFromFlags(player.buffs).m_id; + if(buff_lost == 2) + { + Send_Notification(NOTIF_ONE, player, MSG_MULTI, ITEM_BUFF_DROP, buffid); // TODO: special timeout message? + sound(player, CH_TRIGGER, SND_BUFF_LOST, VOL_BASE, ATTN_NORM); + } + else + Send_Notification(NOTIF_ALL_EXCEPT, player, MSG_INFO, INFO_ITEM_BUFF_LOST, player.netname, buffid); + player.buffs = 0; + } + } + + if(player.buffs & BUFF_MAGNET.m_itemid) + { + vector pickup_size; + FOREACH_ENTITY_FLAGS(flags, FL_ITEM, + { + if(it.buffs) + pickup_size = '1 1 1' * autocvar_g_buffs_magnet_range_buff; + else + pickup_size = '1 1 1' * autocvar_g_buffs_magnet_range_item; + + if(boxesoverlap(player.absmin - pickup_size, player.absmax + pickup_size, it.absmin, it.absmax)) + { + if(gettouch(it)) + gettouch(it)(it, player); + } + }); + } + + if(player.buffs & BUFF_AMMO.m_itemid) + if(player.clip_size) + player.clip_load = player.(weapon_load[PS(player).m_switchweapon.m_id]) = player.clip_size; + + if((player.buffs & BUFF_INVISIBLE.m_itemid) && (player.oldbuffs & BUFF_INVISIBLE.m_itemid)) + if(player.alpha != autocvar_g_buffs_invisible_alpha) + player.alpha = autocvar_g_buffs_invisible_alpha; // powerups reset alpha, so we must enforce this (TODO) + + if(player.buffs & BUFF_MEDIC.m_itemid) + if(time >= player.buff_medic_healtime) + { + buff_Medic_Heal(player); + player.buff_medic_healtime = time + autocvar_g_buffs_medic_heal_delay; + } + +#define BUFF_ONADD(b) if ( (player.buffs & (b).m_itemid) && !(player.oldbuffs & (b).m_itemid)) +#define BUFF_ONREM(b) if (!(player.buffs & (b).m_itemid) && (player.oldbuffs & (b).m_itemid)) + + if(player.buffs != player.oldbuffs) + { + entity buff = buff_FirstFromFlags(player.buffs); + float bufftime = buff != BUFF_Null ? buff.m_time(buff) : 0; + player.buff_time = (bufftime) ? time + bufftime : 0; + + BUFF_ONADD(BUFF_AMMO) + { + player.buff_ammo_prev_infitems = (player.items & IT_UNLIMITED_WEAPON_AMMO); + player.items |= IT_UNLIMITED_WEAPON_AMMO; + + if(player.clip_load) + player.buff_ammo_prev_clipload = player.clip_load; + player.clip_load = player.(weapon_load[PS(player).m_switchweapon.m_id]) = player.clip_size; + } + + BUFF_ONREM(BUFF_AMMO) + { + if(player.buff_ammo_prev_infitems) + player.items |= IT_UNLIMITED_WEAPON_AMMO; + else + player.items &= ~IT_UNLIMITED_WEAPON_AMMO; + + if(player.buff_ammo_prev_clipload) + player.clip_load = player.buff_ammo_prev_clipload; + } + + BUFF_ONADD(BUFF_INVISIBLE) + { + if(time < player.strength_finished && g_instagib) + player.alpha = autocvar_g_instagib_invis_alpha; + else + player.alpha = player.buff_invisible_prev_alpha; + player.alpha = autocvar_g_buffs_invisible_alpha; + } + + BUFF_ONREM(BUFF_INVISIBLE) + player.alpha = player.buff_invisible_prev_alpha; + + player.oldbuffs = player.buffs; + if(player.buffs) + { + if(!player.buff_model) + buffs_BuffModel_Spawn(player); + + player.buff_model.color = buff.m_color; + player.buff_model.glowmod = buff_GlowColor(player.buff_model); + player.buff_model.skin = buff.m_skin; + + player.effects |= EF_NOSHADOW; + } + else + { + delete(player.buff_model); + player.buff_model = NULL; + + player.effects &= ~(EF_NOSHADOW); + } + } + + if(player.buff_model) + { + player.buff_model.effects = player.effects; + player.buff_model.effects |= EF_LOWPRECISION; + player.buff_model.effects = player.buff_model.effects & EFMASK_CHEAP; // eat performance + + player.buff_model.alpha = player.alpha; + } + +#undef BUFF_ONADD +#undef BUFF_ONREM +} + +MUTATOR_HOOKFUNCTION(buffs, SpectateCopy) +{ + entity spectatee = M_ARGV(0, entity); + entity client = M_ARGV(1, entity); + + client.buffs = spectatee.buffs; +} + +MUTATOR_HOOKFUNCTION(buffs, VehicleEnter) +{ + entity player = M_ARGV(0, entity); + entity veh = M_ARGV(1, entity); + + veh.buffs = player.buffs; + player.buffs = 0; + veh.buff_time = max(0, player.buff_time - time); + player.buff_time = 0; +} + +MUTATOR_HOOKFUNCTION(buffs, VehicleExit) +{ + entity player = M_ARGV(0, entity); + entity veh = M_ARGV(1, entity); + + player.buffs = player.oldbuffs = veh.buffs; + veh.buffs = 0; + player.buff_time = time + veh.buff_time; + veh.buff_time = 0; +} + +MUTATOR_HOOKFUNCTION(buffs, PlayerRegen) +{ + entity player = M_ARGV(0, entity); + + if(player.buffs & BUFF_MEDIC.m_itemid) + { + M_ARGV(2, float) = autocvar_g_buffs_medic_rot; // rot_mod + M_ARGV(4, float) = M_ARGV(1, float) = autocvar_g_buffs_medic_max; // limit_mod = max_mod + M_ARGV(2, float) = autocvar_g_buffs_medic_regen; // regen_mod + } + + if(player.buffs & BUFF_SPEED.m_itemid) + M_ARGV(2, float) = autocvar_g_buffs_speed_regen; // regen_mod +} + +REPLICATE(cvar_cl_buffs_autoreplace, bool, "cl_buffs_autoreplace"); + +MUTATOR_HOOKFUNCTION(buffs, BuildMutatorsString) +{ + M_ARGV(0, string) = strcat(M_ARGV(0, string), ":Buffs"); +} + +MUTATOR_HOOKFUNCTION(buffs, BuildMutatorsPrettyString) +{ + M_ARGV(0, string) = strcat(M_ARGV(0, string), ", Buffs"); +} + +void buffs_DelayedInit(entity this) +{ + if(autocvar_g_buffs_spawn_count > 0) + if(find(NULL, classname, "item_buff") == NULL) + { + float i; + for(i = 0; i < autocvar_g_buffs_spawn_count; ++i) + { + entity e = spawn(); + e.spawnflags |= 64; // always randomize + e.velocity = randomvec() * 250; // this gets reset anyway if random location works + buff_Init(e); + } + } +} diff --git a/qcsrc/common/mutators/mutator/buffs/sv_buffs.qh b/qcsrc/common/mutators/mutator/buffs/sv_buffs.qh new file mode 100644 index 0000000000..b9fc1e42d2 --- /dev/null +++ b/qcsrc/common/mutators/mutator/buffs/sv_buffs.qh @@ -0,0 +1,77 @@ +#pragma once + +#include "buffs.qh" + +#include "../instagib/_mod.qh" + +bool autocvar_g_buffs_effects; +float autocvar_g_buffs_waypoint_distance; +bool autocvar_g_buffs_randomize; +float autocvar_g_buffs_random_lifetime; +bool autocvar_g_buffs_random_location; +int autocvar_g_buffs_random_location_attempts; +int autocvar_g_buffs_spawn_count; +bool autocvar_g_buffs_replace_powerups; +float autocvar_g_buffs_cooldown_activate; +float autocvar_g_buffs_cooldown_respawn; +float autocvar_g_buffs_resistance_blockpercent; +float autocvar_g_buffs_medic_survive_chance; +float autocvar_g_buffs_medic_survive_health; +float autocvar_g_buffs_medic_rot; +float autocvar_g_buffs_medic_max; +float autocvar_g_buffs_medic_regen; +float autocvar_g_buffs_medic_heal_amount = 15; +float autocvar_g_buffs_medic_heal_delay = 1; +float autocvar_g_buffs_medic_heal_range = 400; +float autocvar_g_buffs_vengeance_damage_multiplier; +float autocvar_g_buffs_bash_force; +float autocvar_g_buffs_bash_force_self; +float autocvar_g_buffs_disability_slowtime; +float autocvar_g_buffs_disability_speed; +float autocvar_g_buffs_disability_rate; +float autocvar_g_buffs_disability_weaponspeed; +float autocvar_g_buffs_speed_speed; +float autocvar_g_buffs_speed_rate; +float autocvar_g_buffs_speed_weaponspeed; +float autocvar_g_buffs_speed_damage_take; +float autocvar_g_buffs_speed_regen; +float autocvar_g_buffs_vampire_damage_steal; +float autocvar_g_buffs_invisible_alpha; +float autocvar_g_buffs_jump_height; +float autocvar_g_buffs_inferno_burntime_factor; +float autocvar_g_buffs_inferno_burntime_min_time; +float autocvar_g_buffs_inferno_burntime_target_damage; +float autocvar_g_buffs_inferno_burntime_target_time; +float autocvar_g_buffs_inferno_damagemultiplier; +float autocvar_g_buffs_swapper_range; +float autocvar_g_buffs_magnet_range_item; +float autocvar_g_buffs_magnet_range_buff = 200; +float autocvar_g_buffs_luck_chance = 0.15; +float autocvar_g_buffs_luck_damagemultiplier = 3; + +// ammo +.float buff_ammo_prev_infitems; +.int buff_ammo_prev_clipload; +// invisible +.float buff_invisible_prev_alpha; +// medic +.float buff_medic_healtime; +// disability +.float buff_disability_time; +.float buff_disability_effect_time; +// common buff variables +.float buff_effect_delay; + +// buff definitions +.float buff_active; +.float buff_activetime; +.float buff_activetime_updated; +.entity buff_waypoint; +.int oldbuffs; // for updating effects +.entity buff_model; // controls effects (TODO: make csqc) + +const vector BUFF_MIN = ('-16 -16 -20'); +const vector BUFF_MAX = ('16 16 20'); + +// client side options +.float cvar_cl_buffs_autoreplace; diff --git a/qcsrc/common/mutators/mutator/bugrigs/bugrigs.qc b/qcsrc/common/mutators/mutator/bugrigs/bugrigs.qc index f636696e64..a7df2853c6 100644 --- a/qcsrc/common/mutators/mutator/bugrigs/bugrigs.qc +++ b/qcsrc/common/mutators/mutator/bugrigs/bugrigs.qc @@ -1,4 +1,7 @@ -#ifdef IMPLEMENTATION +#include "bugrigs.qh" + +#ifndef MENUQC + #ifdef SVQC #include #endif @@ -311,4 +314,5 @@ MUTATOR_HOOKFUNCTION(bugrigs, BuildMutatorsPrettyString) } #endif + #endif diff --git a/qcsrc/common/mutators/mutator/bugrigs/bugrigs.qh b/qcsrc/common/mutators/mutator/bugrigs/bugrigs.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/mutators/mutator/bugrigs/bugrigs.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mutators/mutator/bugrigs/module.inc b/qcsrc/common/mutators/mutator/bugrigs/module.inc deleted file mode 100644 index cef744fdcb..0000000000 --- a/qcsrc/common/mutators/mutator/bugrigs/module.inc +++ /dev/null @@ -1,3 +0,0 @@ -#ifndef MENUQC -#include "bugrigs.qc" -#endif diff --git a/qcsrc/common/mutators/mutator/campcheck/_mod.inc b/qcsrc/common/mutators/mutator/campcheck/_mod.inc index 3ddb376fc9..07c2ae2722 100644 --- a/qcsrc/common/mutators/mutator/campcheck/_mod.inc +++ b/qcsrc/common/mutators/mutator/campcheck/_mod.inc @@ -1,2 +1,5 @@ // generated file; do not modify #include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/mutators/mutator/campcheck/_mod.qh b/qcsrc/common/mutators/mutator/campcheck/_mod.qh index 81345f1cff..c52811129b 100644 --- a/qcsrc/common/mutators/mutator/campcheck/_mod.qh +++ b/qcsrc/common/mutators/mutator/campcheck/_mod.qh @@ -1,2 +1,5 @@ // generated file; do not modify #include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/mutators/mutator/campcheck/campcheck.qc b/qcsrc/common/mutators/mutator/campcheck/campcheck.qc index 1ab6fb4624..822d4af780 100644 --- a/qcsrc/common/mutators/mutator/campcheck/campcheck.qc +++ b/qcsrc/common/mutators/mutator/campcheck/campcheck.qc @@ -1,91 +1 @@ -#ifdef IMPLEMENTATION -float autocvar_g_campcheck_damage; -float autocvar_g_campcheck_distance; -float autocvar_g_campcheck_interval; - -REGISTER_MUTATOR(campcheck, cvar("g_campcheck")); - -.float campcheck_nextcheck; -.float campcheck_traveled_distance; - -MUTATOR_HOOKFUNCTION(campcheck, PlayerDies) -{ - entity frag_target = M_ARGV(2, entity); - - Kill_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CPID_CAMPCHECK); -} - -MUTATOR_HOOKFUNCTION(campcheck, PlayerDamage_Calculate) -{ - entity frag_attacker = M_ARGV(1, entity); - entity frag_target = M_ARGV(2, entity); - - if(IS_PLAYER(frag_target)) - if(IS_PLAYER(frag_attacker)) - if(frag_attacker != frag_target) - { - frag_target.campcheck_traveled_distance = autocvar_g_campcheck_distance; - frag_attacker.campcheck_traveled_distance = autocvar_g_campcheck_distance; - } -} - -MUTATOR_HOOKFUNCTION(campcheck, PlayerPreThink) -{ - entity player = M_ARGV(0, entity); - - if(!gameover) - if(!warmup_stage) // don't consider it camping during warmup? - if(time >= game_starttime) - if(IS_PLAYER(player)) - if(IS_REAL_CLIENT(player)) // bots may camp, but that's no reason to constantly kill them - if(!IS_DEAD(player)) - if(!STAT(FROZEN, player)) - if(!PHYS_INPUT_BUTTON_CHAT(player)) - if(autocvar_g_campcheck_interval) - { - vector dist; - - // calculate player movement (in 2 dimensions only, so jumping on one spot doesn't count as movement) - dist = player.prevorigin - player.origin; - dist.z = 0; - player.campcheck_traveled_distance += fabs(vlen(dist)); - - if((autocvar_g_campaign && !campaign_bots_may_start) || (time < game_starttime) || (round_handler_IsActive() && !round_handler_IsRoundStarted())) - { - player.campcheck_nextcheck = time + autocvar_g_campcheck_interval * 2; - player.campcheck_traveled_distance = 0; - } - - if(time > player.campcheck_nextcheck) - { - if(player.campcheck_traveled_distance < autocvar_g_campcheck_distance) - { - Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CAMPCHECK); - if(player.vehicle) - Damage(player.vehicle, NULL, NULL, autocvar_g_campcheck_damage * 2, DEATH_CAMP.m_id, player.vehicle.origin, '0 0 0'); - else - Damage(player, NULL, NULL, bound(0, autocvar_g_campcheck_damage, player.health + player.armorvalue * autocvar_g_balance_armor_blockpercent + 5), DEATH_CAMP.m_id, player.origin, '0 0 0'); - } - player.campcheck_nextcheck = time + autocvar_g_campcheck_interval; - player.campcheck_traveled_distance = 0; - } - - return; - } - - player.campcheck_nextcheck = time + autocvar_g_campcheck_interval; // one of the above checks failed, so keep the timer up to date -} - -MUTATOR_HOOKFUNCTION(campcheck, PlayerSpawn) -{ - entity player = M_ARGV(0, entity); - - player.campcheck_nextcheck = time + autocvar_g_campcheck_interval * 2; - player.campcheck_traveled_distance = 0; -} - -MUTATOR_HOOKFUNCTION(campcheck, BuildMutatorsString) -{ - M_ARGV(0, string) = strcat(M_ARGV(0, string), ":CampCheck"); -} -#endif +#include "campcheck.qh" diff --git a/qcsrc/common/mutators/mutator/campcheck/campcheck.qh b/qcsrc/common/mutators/mutator/campcheck/campcheck.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/mutators/mutator/campcheck/campcheck.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mutators/mutator/campcheck/module.inc b/qcsrc/common/mutators/mutator/campcheck/module.inc deleted file mode 100644 index 9f4181f163..0000000000 --- a/qcsrc/common/mutators/mutator/campcheck/module.inc +++ /dev/null @@ -1,3 +0,0 @@ -#ifdef SVQC -#include "campcheck.qc" -#endif diff --git a/qcsrc/common/mutators/mutator/campcheck/sv_campcheck.qc b/qcsrc/common/mutators/mutator/campcheck/sv_campcheck.qc new file mode 100644 index 0000000000..be7d3a2e2b --- /dev/null +++ b/qcsrc/common/mutators/mutator/campcheck/sv_campcheck.qc @@ -0,0 +1,91 @@ +#include "sv_campcheck.qh" + +float autocvar_g_campcheck_damage; +float autocvar_g_campcheck_distance; +float autocvar_g_campcheck_interval; + +REGISTER_MUTATOR(campcheck, cvar("g_campcheck")); + +.float campcheck_nextcheck; +.float campcheck_traveled_distance; + +MUTATOR_HOOKFUNCTION(campcheck, PlayerDies) +{ + entity frag_target = M_ARGV(2, entity); + + Kill_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CPID_CAMPCHECK); +} + +MUTATOR_HOOKFUNCTION(campcheck, PlayerDamage_Calculate) +{ + entity frag_attacker = M_ARGV(1, entity); + entity frag_target = M_ARGV(2, entity); + + if(IS_PLAYER(frag_target)) + if(IS_PLAYER(frag_attacker)) + if(frag_attacker != frag_target) + { + frag_target.campcheck_traveled_distance = autocvar_g_campcheck_distance; + frag_attacker.campcheck_traveled_distance = autocvar_g_campcheck_distance; + } +} + +MUTATOR_HOOKFUNCTION(campcheck, PlayerPreThink) +{ + entity player = M_ARGV(0, entity); + + if(!gameover) + if(!warmup_stage) // don't consider it camping during warmup? + if(time >= game_starttime) + if(IS_PLAYER(player)) + if(IS_REAL_CLIENT(player)) // bots may camp, but that's no reason to constantly kill them + if(!IS_DEAD(player)) + if(!STAT(FROZEN, player)) + if(!PHYS_INPUT_BUTTON_CHAT(player)) + if(autocvar_g_campcheck_interval) + { + vector dist; + + // calculate player movement (in 2 dimensions only, so jumping on one spot doesn't count as movement) + dist = player.prevorigin - player.origin; + dist.z = 0; + player.campcheck_traveled_distance += fabs(vlen(dist)); + + if((autocvar_g_campaign && !campaign_bots_may_start) || (time < game_starttime) || (round_handler_IsActive() && !round_handler_IsRoundStarted())) + { + player.campcheck_nextcheck = time + autocvar_g_campcheck_interval * 2; + player.campcheck_traveled_distance = 0; + } + + if(time > player.campcheck_nextcheck) + { + if(player.campcheck_traveled_distance < autocvar_g_campcheck_distance) + { + Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CAMPCHECK); + if(player.vehicle) + Damage(player.vehicle, NULL, NULL, autocvar_g_campcheck_damage * 2, DEATH_CAMP.m_id, player.vehicle.origin, '0 0 0'); + else + Damage(player, NULL, NULL, bound(0, autocvar_g_campcheck_damage, player.health + player.armorvalue * autocvar_g_balance_armor_blockpercent + 5), DEATH_CAMP.m_id, player.origin, '0 0 0'); + } + player.campcheck_nextcheck = time + autocvar_g_campcheck_interval; + player.campcheck_traveled_distance = 0; + } + + return; + } + + player.campcheck_nextcheck = time + autocvar_g_campcheck_interval; // one of the above checks failed, so keep the timer up to date +} + +MUTATOR_HOOKFUNCTION(campcheck, PlayerSpawn) +{ + entity player = M_ARGV(0, entity); + + player.campcheck_nextcheck = time + autocvar_g_campcheck_interval * 2; + player.campcheck_traveled_distance = 0; +} + +MUTATOR_HOOKFUNCTION(campcheck, BuildMutatorsString) +{ + M_ARGV(0, string) = strcat(M_ARGV(0, string), ":CampCheck"); +} diff --git a/qcsrc/common/mutators/mutator/campcheck/sv_campcheck.qh b/qcsrc/common/mutators/mutator/campcheck/sv_campcheck.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/mutators/mutator/campcheck/sv_campcheck.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mutators/mutator/cloaked/_mod.inc b/qcsrc/common/mutators/mutator/cloaked/_mod.inc index 7213695225..34fdea7479 100644 --- a/qcsrc/common/mutators/mutator/cloaked/_mod.inc +++ b/qcsrc/common/mutators/mutator/cloaked/_mod.inc @@ -1,2 +1,4 @@ // generated file; do not modify -#include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/mutators/mutator/cloaked/_mod.qh b/qcsrc/common/mutators/mutator/cloaked/_mod.qh index 5606b9aafd..b225a2be5b 100644 --- a/qcsrc/common/mutators/mutator/cloaked/_mod.qh +++ b/qcsrc/common/mutators/mutator/cloaked/_mod.qh @@ -1,2 +1,4 @@ // generated file; do not modify -#include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/mutators/mutator/cloaked/cloaked.qc b/qcsrc/common/mutators/mutator/cloaked/cloaked.qc deleted file mode 100644 index 4fca532768..0000000000 --- a/qcsrc/common/mutators/mutator/cloaked/cloaked.qc +++ /dev/null @@ -1,19 +0,0 @@ -#ifdef IMPLEMENTATION - -REGISTER_MUTATOR(cloaked, cvar("g_cloaked")); - -float autocvar_g_balance_cloaked_alpha; - -MUTATOR_HOOKFUNCTION(cloaked, SetDefaultAlpha) -{ - default_player_alpha = autocvar_g_balance_cloaked_alpha; - default_weapon_alpha = default_player_alpha; - return true; -} - -MUTATOR_HOOKFUNCTION(cloaked, BuildMutatorsPrettyString) -{ - if (!g_cts) M_ARGV(0, string) = strcat(M_ARGV(0, string), ", Cloaked"); -} - -#endif diff --git a/qcsrc/common/mutators/mutator/cloaked/module.inc b/qcsrc/common/mutators/mutator/cloaked/module.inc deleted file mode 100644 index 3d3a042936..0000000000 --- a/qcsrc/common/mutators/mutator/cloaked/module.inc +++ /dev/null @@ -1,3 +0,0 @@ -#ifdef SVQC -#include "cloaked.qc" -#endif diff --git a/qcsrc/common/mutators/mutator/cloaked/sv_cloaked.qc b/qcsrc/common/mutators/mutator/cloaked/sv_cloaked.qc new file mode 100644 index 0000000000..eb45d4baf4 --- /dev/null +++ b/qcsrc/common/mutators/mutator/cloaked/sv_cloaked.qc @@ -0,0 +1,17 @@ +#include "sv_cloaked.qh" + +REGISTER_MUTATOR(cloaked, cvar("g_cloaked")); + +float autocvar_g_balance_cloaked_alpha; + +MUTATOR_HOOKFUNCTION(cloaked, SetDefaultAlpha) +{ + default_player_alpha = autocvar_g_balance_cloaked_alpha; + default_weapon_alpha = default_player_alpha; + return true; +} + +MUTATOR_HOOKFUNCTION(cloaked, BuildMutatorsPrettyString) +{ + if (!g_cts) M_ARGV(0, string) = strcat(M_ARGV(0, string), ", Cloaked"); +} diff --git a/qcsrc/common/mutators/mutator/cloaked/sv_cloaked.qh b/qcsrc/common/mutators/mutator/cloaked/sv_cloaked.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/mutators/mutator/cloaked/sv_cloaked.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mutators/mutator/damagetext/damagetext.qc b/qcsrc/common/mutators/mutator/damagetext/damagetext.qc index 878a854fb7..88af5496b3 100644 --- a/qcsrc/common/mutators/mutator/damagetext/damagetext.qc +++ b/qcsrc/common/mutators/mutator/damagetext/damagetext.qc @@ -1,13 +1,5 @@ -#ifndef MUTATOR_DAMAGETEXT_H -#define MUTATOR_DAMAGETEXT_H +#include "damagetext.qh" -#ifdef MENUQC -#include -#endif - -#endif - -#ifdef IMPLEMENTATION REGISTER_MUTATOR(damagetext, true); #if defined(CSQC) || defined(MENUQC) @@ -157,8 +149,10 @@ NET_HANDLE(damagetext, bool isNew) #endif #ifdef MENUQC + +#include + CLASS(XonoticDamageTextSettings, XonoticTab) - #include REGISTER_SETTINGS(damagetext, NEW(XonoticDamageTextSettings)); ATTRIB(XonoticDamageTextSettings, title, string, _("Damage text")); ATTRIB(XonoticDamageTextSettings, intendedWidth, float, 0.9); @@ -193,7 +187,6 @@ CLASS(XonoticDamageTextSettings, XonoticTab) setDependent(e, "cl_damagetext", 1, 1); this.TR(this); this.TR(this); - // friendly fire this.TD(this, 1, 3, e = makeXonoticCheckBox(0, "cl_damagetext_friendlyfire", _("Draw damage numbers for friendly fire"))); setDependent(e, "cl_damagetext", 1, 1); this.TR(this); @@ -205,4 +198,3 @@ CLASS(XonoticDamageTextSettings, XonoticTab) } ENDCLASS(XonoticDamageTextSettings) #endif -#endif diff --git a/qcsrc/common/mutators/mutator/damagetext/damagetext.qh b/qcsrc/common/mutators/mutator/damagetext/damagetext.qh new file mode 100644 index 0000000000..7228f37ea5 --- /dev/null +++ b/qcsrc/common/mutators/mutator/damagetext/damagetext.qh @@ -0,0 +1,5 @@ +#pragma once + +#ifdef MENUQC +#include +#endif diff --git a/qcsrc/common/mutators/mutator/damagetext/module.inc b/qcsrc/common/mutators/mutator/damagetext/module.inc deleted file mode 100644 index 8e70be7c6d..0000000000 --- a/qcsrc/common/mutators/mutator/damagetext/module.inc +++ /dev/null @@ -1 +0,0 @@ -#include "damagetext.qc" diff --git a/qcsrc/common/mutators/mutator/dodging/_mod.inc b/qcsrc/common/mutators/mutator/dodging/_mod.inc index 4902d5fc1b..80a7828a70 100644 --- a/qcsrc/common/mutators/mutator/dodging/_mod.inc +++ b/qcsrc/common/mutators/mutator/dodging/_mod.inc @@ -1,2 +1,4 @@ // generated file; do not modify -#include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/mutators/mutator/dodging/_mod.qh b/qcsrc/common/mutators/mutator/dodging/_mod.qh index b2b65f02f7..fef7b8e6e4 100644 --- a/qcsrc/common/mutators/mutator/dodging/_mod.qh +++ b/qcsrc/common/mutators/mutator/dodging/_mod.qh @@ -1,2 +1,4 @@ // generated file; do not modify -#include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/mutators/mutator/dodging/dodging.qc b/qcsrc/common/mutators/mutator/dodging/dodging.qc deleted file mode 100644 index 7ddfcea2ba..0000000000 --- a/qcsrc/common/mutators/mutator/dodging/dodging.qc +++ /dev/null @@ -1,292 +0,0 @@ -#ifdef IMPLEMENTATION - -#define PHYS_DODGING STAT(DODGING, this) -#define PHYS_DODGING_DELAY STAT(DODGING_DELAY, this) -#define PHYS_DODGING_DISTANCE_THRESHOLD STAT(DODGING_DISTANCE_THRESHOLD, this) -#define PHYS_DODGING_FROZEN_NODOUBLETAP STAT(DODGING_FROZEN_NO_DOUBLETAP, this) -#define PHYS_DODGING_HEIGHT_THRESHOLD STAT(DODGING_HEIGHT_THRESHOLD, this) -#define PHYS_DODGING_HORIZ_SPEED STAT(DODGING_HORIZ_SPEED, this) -#define PHYS_DODGING_HORIZ_SPEED_FROZEN STAT(DODGING_HORIZ_SPEED_FROZEN, this) -#define PHYS_DODGING_RAMP_TIME STAT(DODGING_RAMP_TIME, this) -#define PHYS_DODGING_UP_SPEED STAT(DODGING_UP_SPEED, this) -#define PHYS_DODGING_WALL STAT(DODGING_WALL, this) -#define PHYS_DODGING_AIR STAT(DODGING_AIR, this) -#define PHYS_DODGING_PRESSED_KEYS(s) (s).pressedkeys - -#ifdef CSQC - #define PHYS_DODGING_FRAMETIME (1 / (frametime <= 0 ? 60 : frametime)) - #define PHYS_DODGING_TIMEOUT(s) STAT(DODGING_TIMEOUT) -#elif defined(SVQC) - #define PHYS_DODGING_FRAMETIME sys_frametime - #define PHYS_DODGING_TIMEOUT(s) s.cvar_cl_dodging_timeout -#endif - -#ifdef SVQC - -bool autocvar_sv_dodging_sound; - -// 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; - -#include -#include - -.float cvar_cl_dodging_timeout = _STAT(DODGING_TIMEOUT); - -REGISTER_MUTATOR(dodging, cvar("g_dodging")) -{ - // this just turns on the cvar. - MUTATOR_ONADD - { - g_dodging = cvar("g_dodging"); - } - - // this just turns off the cvar. - MUTATOR_ONROLLBACK_OR_REMOVE - { - g_dodging = 0; - } - - return false; -} - -#elif defined(CSQC) -REGISTER_MUTATOR(dodging, true); -#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; -.float last_BACKWARD_KEY_time; -.float last_LEFT_KEY_time; -.float last_RIGHT_KEY_time; - -// these store the movement direction at the time of the dodge action happening. -.vector dodging_direction; - -// this indicates the last time a dodge was executed. used to check if another one is allowed -// and to ramp up the dodge acceleration in the physics hook. -.float last_dodging_time; - -// This is the velocity gain to be added over the ramp time. -// It will decrease from frame to frame during dodging_action = 1 -// until it's 0. -.float dodging_velocity_gain; - -#ifdef CSQC -.int pressedkeys; -#endif - -// returns 1 if the player is close to a wall -bool check_close_to_wall(entity this, float threshold) -{ - if (PHYS_DODGING_WALL == 0) { return false; } - -#define X(OFFSET) \ - tracebox(this.origin, this.mins, this.maxs, this.origin + OFFSET, true, this); \ - if(trace_fraction < 1 && vdist(this.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; -} - -bool check_close_to_ground(entity this, float threshold) -{ - return IS_ONGROUND(this) ? true : false; -} - -float PM_dodging_checkpressedkeys(entity this) -{ - if(!PHYS_DODGING) - return false; - - float frozen_dodging = (PHYS_FROZEN(this) && PHYS_DODGING_FROZEN(this)); - float frozen_no_doubletap = (frozen_dodging && !PHYS_DODGING_FROZEN_NODOUBLETAP); - - // first check if the last dodge is far enough back in time so we can dodge again - if ((time - this.last_dodging_time) < PHYS_DODGING_DELAY) - return false; - - makevectors(this.angles); - - if(!PHYS_DODGING_AIR) - if (check_close_to_ground(this, PHYS_DODGING_HEIGHT_THRESHOLD) != 1 - && check_close_to_wall(this, PHYS_DODGING_DISTANCE_THRESHOLD) != 1) - return true; - - float tap_direction_x = 0; - float tap_direction_y = 0; - bool dodge_detected = false; - - #define X(COND,BTN,RESULT) \ - if (this.movement_##COND) \ - /* is this a state change? */ \ - if(!(PHYS_DODGING_PRESSED_KEYS(this) & KEY_##BTN) || frozen_no_doubletap) { \ - tap_direction_##RESULT; \ - if ((time - this.last_##BTN##_KEY_time) < PHYS_DODGING_TIMEOUT(this) || frozen_no_doubletap) \ - dodge_detected = true; \ - this.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) - { - this.last_dodging_time = time; - - this.dodging_action = 1; - this.dodging_single_action = 1; - - this.dodging_velocity_gain = PHYS_DODGING_HORIZ_SPEED; - - this.dodging_direction_x = tap_direction_x; - this.dodging_direction_y = tap_direction_y; - - // normalize the dodging_direction vector.. (unlike UT99) XD - float length = this.dodging_direction_x * this.dodging_direction_x - + this.dodging_direction_y * this.dodging_direction_y; - length = sqrt(length); - - this.dodging_direction_x = this.dodging_direction_x * 1.0 / length; - this.dodging_direction_y = this.dodging_direction_y * 1.0 / length; - return true; - } - return false; -} - -void PM_dodging(entity this) -{ - if (!PHYS_DODGING) - return; - - if (IS_DEAD(this)) - return; - - // when swimming, no dodging allowed.. - if (this.waterlevel >= WATERLEVEL_SWIMMING) - { - this.dodging_action = 0; - this.dodging_direction_x = 0; - this.dodging_direction_y = 0; - return; - } - - // make sure v_up, v_right and v_forward are sane - if(PHYS_DODGING_AIR) - makevectors(this.v_angle); - else - makevectors(this.angles); - - // 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.. - float common_factor = PHYS_DODGING_FRAMETIME / PHYS_DODGING_RAMP_TIME; - - // if ramp time is smaller than frametime we get problems ;D - common_factor = min(common_factor, 1); - - float horiz_speed = PHYS_FROZEN(this) ? PHYS_DODGING_HORIZ_SPEED_FROZEN : PHYS_DODGING_HORIZ_SPEED; - float new_velocity_gain = this.dodging_velocity_gain - (common_factor * horiz_speed); - new_velocity_gain = max(0, new_velocity_gain); - - float velocity_difference = this.dodging_velocity_gain - new_velocity_gain; - - // ramp up dodging speed by adding some velocity each frame.. TODO: do it! :D - if (this.dodging_action == 1) - { - //disable jump key during dodge accel phase - if(this.movement_z > 0) { this.movement_z = 0; } - - this.velocity += ((this.dodging_direction_y * velocity_difference) * v_right) - + ((this.dodging_direction_x * velocity_difference) * v_forward); - - this.dodging_velocity_gain = this.dodging_velocity_gain - velocity_difference; - } - - // the up part of the dodge is a single shot action - if (this.dodging_single_action == 1) - { - UNSET_ONGROUND(this); - - this.velocity += PHYS_DODGING_UP_SPEED * v_up; - -#ifdef SVQC - if (autocvar_sv_dodging_sound) - PlayerSound(this, playersound_jump, CH_PLAYER, VOL_BASE, VOICETYPE_PLAYERSOUND); - - animdecide_setaction(this, ANIMACTION_JUMP, true); -#endif - - this.dodging_single_action = 0; - } - - // are we done with the dodging ramp yet? - if((this.dodging_action == 1) && ((time - this.last_dodging_time) > PHYS_DODGING_RAMP_TIME)) - { - // reset state so next dodge can be done correctly - this.dodging_action = 0; - this.dodging_direction_x = 0; - this.dodging_direction_y = 0; - } -} - -void PM_dodging_GetPressedKeys(entity this) -{ -#ifdef CSQC - if(!PHYS_DODGING) { return; } - - PM_dodging_checkpressedkeys(this); - - int keys = this.pressedkeys; - keys = BITSET(keys, KEY_FORWARD, this.movement.x > 0); - keys = BITSET(keys, KEY_BACKWARD, this.movement.x < 0); - keys = BITSET(keys, KEY_RIGHT, this.movement.y > 0); - keys = BITSET(keys, KEY_LEFT, this.movement.y < 0); - - keys = BITSET(keys, KEY_JUMP, PHYS_INPUT_BUTTON_JUMP(this)); - keys = BITSET(keys, KEY_CROUCH, PHYS_INPUT_BUTTON_CROUCH(this)); - keys = BITSET(keys, KEY_ATCK, PHYS_INPUT_BUTTON_ATCK(this)); - keys = BITSET(keys, KEY_ATCK2, PHYS_INPUT_BUTTON_ATCK2(this)); - this.pressedkeys = keys; -#endif -} - -MUTATOR_HOOKFUNCTION(dodging, PlayerPhysics) -{ - entity player = M_ARGV(0, entity); - - // print("dodging_PlayerPhysics\n"); - PM_dodging_GetPressedKeys(player); - PM_dodging(player); -} - -#ifdef SVQC - -REPLICATE(cvar_cl_dodging_timeout, float, "cl_dodging_timeout"); - -MUTATOR_HOOKFUNCTION(dodging, GetPressedKeys) -{ - entity player = M_ARGV(0, entity); - - PM_dodging_checkpressedkeys(player); -} - -#endif -#endif diff --git a/qcsrc/common/mutators/mutator/dodging/module.inc b/qcsrc/common/mutators/mutator/dodging/module.inc deleted file mode 100644 index 60b193fdf9..0000000000 --- a/qcsrc/common/mutators/mutator/dodging/module.inc +++ /dev/null @@ -1,3 +0,0 @@ -#ifdef SVQC - #include "dodging.qc" -#endif diff --git a/qcsrc/common/mutators/mutator/dodging/sv_dodging.qc b/qcsrc/common/mutators/mutator/dodging/sv_dodging.qc new file mode 100644 index 0000000000..f9cee61f1e --- /dev/null +++ b/qcsrc/common/mutators/mutator/dodging/sv_dodging.qc @@ -0,0 +1,291 @@ +#include "sv_dodging.qh" + +#define PHYS_DODGING STAT(DODGING, this) +#define PHYS_DODGING_DELAY STAT(DODGING_DELAY, this) +#define PHYS_DODGING_DISTANCE_THRESHOLD STAT(DODGING_DISTANCE_THRESHOLD, this) +#define PHYS_DODGING_FROZEN_NODOUBLETAP STAT(DODGING_FROZEN_NO_DOUBLETAP, this) +#define PHYS_DODGING_HEIGHT_THRESHOLD STAT(DODGING_HEIGHT_THRESHOLD, this) +#define PHYS_DODGING_HORIZ_SPEED STAT(DODGING_HORIZ_SPEED, this) +#define PHYS_DODGING_HORIZ_SPEED_FROZEN STAT(DODGING_HORIZ_SPEED_FROZEN, this) +#define PHYS_DODGING_RAMP_TIME STAT(DODGING_RAMP_TIME, this) +#define PHYS_DODGING_UP_SPEED STAT(DODGING_UP_SPEED, this) +#define PHYS_DODGING_WALL STAT(DODGING_WALL, this) +#define PHYS_DODGING_AIR STAT(DODGING_AIR, this) +#define PHYS_DODGING_PRESSED_KEYS(s) (s).pressedkeys + +#ifdef CSQC + #define PHYS_DODGING_FRAMETIME (1 / (frametime <= 0 ? 60 : frametime)) + #define PHYS_DODGING_TIMEOUT(s) STAT(DODGING_TIMEOUT) +#elif defined(SVQC) + #define PHYS_DODGING_FRAMETIME sys_frametime + #define PHYS_DODGING_TIMEOUT(s) s.cvar_cl_dodging_timeout +#endif + +#ifdef SVQC + +bool autocvar_sv_dodging_sound; + +// 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; + +#include +#include + +.float cvar_cl_dodging_timeout = _STAT(DODGING_TIMEOUT); + +REGISTER_MUTATOR(dodging, cvar("g_dodging")) +{ + // this just turns on the cvar. + MUTATOR_ONADD + { + g_dodging = cvar("g_dodging"); + } + + // this just turns off the cvar. + MUTATOR_ONROLLBACK_OR_REMOVE + { + g_dodging = 0; + } + + return false; +} + +#elif defined(CSQC) +REGISTER_MUTATOR(dodging, true); +#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; +.float last_BACKWARD_KEY_time; +.float last_LEFT_KEY_time; +.float last_RIGHT_KEY_time; + +// these store the movement direction at the time of the dodge action happening. +.vector dodging_direction; + +// this indicates the last time a dodge was executed. used to check if another one is allowed +// and to ramp up the dodge acceleration in the physics hook. +.float last_dodging_time; + +// This is the velocity gain to be added over the ramp time. +// It will decrease from frame to frame during dodging_action = 1 +// until it's 0. +.float dodging_velocity_gain; + +#ifdef CSQC +.int pressedkeys; +#endif + +// returns 1 if the player is close to a wall +bool check_close_to_wall(entity this, float threshold) +{ + if (PHYS_DODGING_WALL == 0) { return false; } + +#define X(OFFSET) \ + tracebox(this.origin, this.mins, this.maxs, this.origin + OFFSET, true, this); \ + if(trace_fraction < 1 && vdist(this.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; +} + +bool check_close_to_ground(entity this, float threshold) +{ + return IS_ONGROUND(this) ? true : false; +} + +float PM_dodging_checkpressedkeys(entity this) +{ + if(!PHYS_DODGING) + return false; + + float frozen_dodging = (PHYS_FROZEN(this) && PHYS_DODGING_FROZEN(this)); + float frozen_no_doubletap = (frozen_dodging && !PHYS_DODGING_FROZEN_NODOUBLETAP); + + // first check if the last dodge is far enough back in time so we can dodge again + if ((time - this.last_dodging_time) < PHYS_DODGING_DELAY) + return false; + + makevectors(this.angles); + + if(!PHYS_DODGING_AIR) + if (check_close_to_ground(this, PHYS_DODGING_HEIGHT_THRESHOLD) != 1 + && check_close_to_wall(this, PHYS_DODGING_DISTANCE_THRESHOLD) != 1) + return true; + + float tap_direction_x = 0; + float tap_direction_y = 0; + bool dodge_detected = false; + + #define X(COND,BTN,RESULT) \ + if (this.movement_##COND) \ + /* is this a state change? */ \ + if(!(PHYS_DODGING_PRESSED_KEYS(this) & KEY_##BTN) || frozen_no_doubletap) { \ + tap_direction_##RESULT; \ + if ((time - this.last_##BTN##_KEY_time) < PHYS_DODGING_TIMEOUT(this) || frozen_no_doubletap) \ + dodge_detected = true; \ + this.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) + { + this.last_dodging_time = time; + + this.dodging_action = 1; + this.dodging_single_action = 1; + + this.dodging_velocity_gain = PHYS_DODGING_HORIZ_SPEED; + + this.dodging_direction_x = tap_direction_x; + this.dodging_direction_y = tap_direction_y; + + // normalize the dodging_direction vector.. (unlike UT99) XD + float length = this.dodging_direction_x * this.dodging_direction_x + + this.dodging_direction_y * this.dodging_direction_y; + length = sqrt(length); + + this.dodging_direction_x = this.dodging_direction_x * 1.0 / length; + this.dodging_direction_y = this.dodging_direction_y * 1.0 / length; + return true; + } + return false; +} + +void PM_dodging(entity this) +{ + if (!PHYS_DODGING) + return; + + if (IS_DEAD(this)) + return; + + // when swimming, no dodging allowed.. + if (this.waterlevel >= WATERLEVEL_SWIMMING) + { + this.dodging_action = 0; + this.dodging_direction_x = 0; + this.dodging_direction_y = 0; + return; + } + + // make sure v_up, v_right and v_forward are sane + if(PHYS_DODGING_AIR) + makevectors(this.v_angle); + else + makevectors(this.angles); + + // 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.. + float common_factor = PHYS_DODGING_FRAMETIME / PHYS_DODGING_RAMP_TIME; + + // if ramp time is smaller than frametime we get problems ;D + common_factor = min(common_factor, 1); + + float horiz_speed = PHYS_FROZEN(this) ? PHYS_DODGING_HORIZ_SPEED_FROZEN : PHYS_DODGING_HORIZ_SPEED; + float new_velocity_gain = this.dodging_velocity_gain - (common_factor * horiz_speed); + new_velocity_gain = max(0, new_velocity_gain); + + float velocity_difference = this.dodging_velocity_gain - new_velocity_gain; + + // ramp up dodging speed by adding some velocity each frame.. TODO: do it! :D + if (this.dodging_action == 1) + { + //disable jump key during dodge accel phase + if(this.movement_z > 0) { this.movement_z = 0; } + + this.velocity += ((this.dodging_direction_y * velocity_difference) * v_right) + + ((this.dodging_direction_x * velocity_difference) * v_forward); + + this.dodging_velocity_gain = this.dodging_velocity_gain - velocity_difference; + } + + // the up part of the dodge is a single shot action + if (this.dodging_single_action == 1) + { + UNSET_ONGROUND(this); + + this.velocity += PHYS_DODGING_UP_SPEED * v_up; + +#ifdef SVQC + if (autocvar_sv_dodging_sound) + PlayerSound(this, playersound_jump, CH_PLAYER, VOL_BASE, VOICETYPE_PLAYERSOUND); + + animdecide_setaction(this, ANIMACTION_JUMP, true); +#endif + + this.dodging_single_action = 0; + } + + // are we done with the dodging ramp yet? + if((this.dodging_action == 1) && ((time - this.last_dodging_time) > PHYS_DODGING_RAMP_TIME)) + { + // reset state so next dodge can be done correctly + this.dodging_action = 0; + this.dodging_direction_x = 0; + this.dodging_direction_y = 0; + } +} + +void PM_dodging_GetPressedKeys(entity this) +{ +#ifdef CSQC + if(!PHYS_DODGING) { return; } + + PM_dodging_checkpressedkeys(this); + + int keys = this.pressedkeys; + keys = BITSET(keys, KEY_FORWARD, this.movement.x > 0); + keys = BITSET(keys, KEY_BACKWARD, this.movement.x < 0); + keys = BITSET(keys, KEY_RIGHT, this.movement.y > 0); + keys = BITSET(keys, KEY_LEFT, this.movement.y < 0); + + keys = BITSET(keys, KEY_JUMP, PHYS_INPUT_BUTTON_JUMP(this)); + keys = BITSET(keys, KEY_CROUCH, PHYS_INPUT_BUTTON_CROUCH(this)); + keys = BITSET(keys, KEY_ATCK, PHYS_INPUT_BUTTON_ATCK(this)); + keys = BITSET(keys, KEY_ATCK2, PHYS_INPUT_BUTTON_ATCK2(this)); + this.pressedkeys = keys; +#endif +} + +MUTATOR_HOOKFUNCTION(dodging, PlayerPhysics) +{ + entity player = M_ARGV(0, entity); + + // print("dodging_PlayerPhysics\n"); + PM_dodging_GetPressedKeys(player); + PM_dodging(player); +} + +#ifdef SVQC + +REPLICATE(cvar_cl_dodging_timeout, float, "cl_dodging_timeout"); + +MUTATOR_HOOKFUNCTION(dodging, GetPressedKeys) +{ + entity player = M_ARGV(0, entity); + + PM_dodging_checkpressedkeys(player); +} + +#endif diff --git a/qcsrc/common/mutators/mutator/dodging/sv_dodging.qh b/qcsrc/common/mutators/mutator/dodging/sv_dodging.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/mutators/mutator/dodging/sv_dodging.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mutators/mutator/doublejump/doublejump.qc b/qcsrc/common/mutators/mutator/doublejump/doublejump.qc index d490ecaaff..f9821b13a7 100644 --- a/qcsrc/common/mutators/mutator/doublejump/doublejump.qc +++ b/qcsrc/common/mutators/mutator/doublejump/doublejump.qc @@ -1,4 +1,6 @@ -#ifdef IMPLEMENTATION +#include "doublejump.qh" + +#ifndef MENUQC #ifdef SVQC #include #endif @@ -31,5 +33,4 @@ MUTATOR_HOOKFUNCTION(doublejump, PlayerJump) } } } - #endif diff --git a/qcsrc/common/mutators/mutator/doublejump/doublejump.qh b/qcsrc/common/mutators/mutator/doublejump/doublejump.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/mutators/mutator/doublejump/doublejump.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mutators/mutator/doublejump/module.inc b/qcsrc/common/mutators/mutator/doublejump/module.inc deleted file mode 100644 index f4c695be0e..0000000000 --- a/qcsrc/common/mutators/mutator/doublejump/module.inc +++ /dev/null @@ -1,3 +0,0 @@ -#ifndef MENUQC -#include "doublejump.qc" -#endif diff --git a/qcsrc/common/mutators/mutator/globalforces/_mod.inc b/qcsrc/common/mutators/mutator/globalforces/_mod.inc index 006a5d5c7e..2302d85fb1 100644 --- a/qcsrc/common/mutators/mutator/globalforces/_mod.inc +++ b/qcsrc/common/mutators/mutator/globalforces/_mod.inc @@ -1,2 +1,4 @@ // generated file; do not modify -#include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/mutators/mutator/globalforces/_mod.qh b/qcsrc/common/mutators/mutator/globalforces/_mod.qh index ac5cfd2442..c31ee5cfcb 100644 --- a/qcsrc/common/mutators/mutator/globalforces/_mod.qh +++ b/qcsrc/common/mutators/mutator/globalforces/_mod.qh @@ -1,2 +1,4 @@ // generated file; do not modify -#include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/mutators/mutator/globalforces/globalforces.qc b/qcsrc/common/mutators/mutator/globalforces/globalforces.qc deleted file mode 100644 index 49ac468ba5..0000000000 --- a/qcsrc/common/mutators/mutator/globalforces/globalforces.qc +++ /dev/null @@ -1,33 +0,0 @@ -#ifdef IMPLEMENTATION - -AUTOCVAR(g_globalforces, float, false, "Global forces: knockback affects everyone"); -AUTOCVAR(g_globalforces_noself, bool, true, "Global forces: ignore self damage"); -AUTOCVAR(g_globalforces_self, float, 1, "Global forces: knockback self scale"); -AUTOCVAR(g_globalforces_range, float, 1000, "Global forces: max range of effect"); -REGISTER_MUTATOR(mutator_globalforces, autocvar_g_globalforces); - -MUTATOR_HOOKFUNCTION(mutator_globalforces, BuildMutatorsString) { - M_ARGV(0, string) = strcat(M_ARGV(0, string), ":GlobalForces"); -} - -MUTATOR_HOOKFUNCTION(mutator_globalforces, BuildMutatorsPrettyString) { - M_ARGV(0, string) = strcat(M_ARGV(0, string), ", Global forces"); -} - -MUTATOR_HOOKFUNCTION(mutator_globalforces, PlayerDamage_SplitHealthArmor) { - entity frag_attacker = M_ARGV(1, entity); - entity frag_target = M_ARGV(2, entity); - if (autocvar_g_globalforces_noself && frag_target == frag_attacker) return; - vector damage_force = M_ARGV(3, vector) * autocvar_g_globalforces; - FOREACH_CLIENT(IS_PLAYER(it) && it != frag_target, { - if (autocvar_g_globalforces_range) { - if (vdist(it.origin - frag_target.origin, >, autocvar_g_globalforces_range)) { - continue; - } - } - float f = (it == frag_attacker) ? autocvar_g_globalforces_self : 1; - it.velocity += damage_explosion_calcpush(f * it.damageforcescale * damage_force, it.velocity, autocvar_g_balance_damagepush_speedfactor); - }); -} - -#endif diff --git a/qcsrc/common/mutators/mutator/globalforces/module.inc b/qcsrc/common/mutators/mutator/globalforces/module.inc deleted file mode 100644 index 2a0eec8bff..0000000000 --- a/qcsrc/common/mutators/mutator/globalforces/module.inc +++ /dev/null @@ -1,3 +0,0 @@ -#ifdef SVQC - #include "globalforces.qc" -#endif diff --git a/qcsrc/common/mutators/mutator/globalforces/sv_globalforces.qc b/qcsrc/common/mutators/mutator/globalforces/sv_globalforces.qc new file mode 100644 index 0000000000..3e9cd0d51c --- /dev/null +++ b/qcsrc/common/mutators/mutator/globalforces/sv_globalforces.qc @@ -0,0 +1,31 @@ +#include "sv_globalforces.qh" + +AUTOCVAR(g_globalforces, float, false, "Global forces: knockback affects everyone"); +AUTOCVAR(g_globalforces_noself, bool, true, "Global forces: ignore self damage"); +AUTOCVAR(g_globalforces_self, float, 1, "Global forces: knockback self scale"); +AUTOCVAR(g_globalforces_range, float, 1000, "Global forces: max range of effect"); +REGISTER_MUTATOR(mutator_globalforces, autocvar_g_globalforces); + +MUTATOR_HOOKFUNCTION(mutator_globalforces, BuildMutatorsString) { + M_ARGV(0, string) = strcat(M_ARGV(0, string), ":GlobalForces"); +} + +MUTATOR_HOOKFUNCTION(mutator_globalforces, BuildMutatorsPrettyString) { + M_ARGV(0, string) = strcat(M_ARGV(0, string), ", Global forces"); +} + +MUTATOR_HOOKFUNCTION(mutator_globalforces, PlayerDamage_SplitHealthArmor) { + entity frag_attacker = M_ARGV(1, entity); + entity frag_target = M_ARGV(2, entity); + if (autocvar_g_globalforces_noself && frag_target == frag_attacker) return; + vector damage_force = M_ARGV(3, vector) * autocvar_g_globalforces; + FOREACH_CLIENT(IS_PLAYER(it) && it != frag_target, { + if (autocvar_g_globalforces_range) { + if (vdist(it.origin - frag_target.origin, >, autocvar_g_globalforces_range)) { + continue; + } + } + float f = (it == frag_attacker) ? autocvar_g_globalforces_self : 1; + it.velocity += damage_explosion_calcpush(f * it.damageforcescale * damage_force, it.velocity, autocvar_g_balance_damagepush_speedfactor); + }); +} diff --git a/qcsrc/common/mutators/mutator/globalforces/sv_globalforces.qh b/qcsrc/common/mutators/mutator/globalforces/sv_globalforces.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/mutators/mutator/globalforces/sv_globalforces.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mutators/mutator/hook/_mod.inc b/qcsrc/common/mutators/mutator/hook/_mod.inc index ec6da662e7..e5e68b6106 100644 --- a/qcsrc/common/mutators/mutator/hook/_mod.inc +++ b/qcsrc/common/mutators/mutator/hook/_mod.inc @@ -1,2 +1,4 @@ // generated file; do not modify -#include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/mutators/mutator/hook/_mod.qh b/qcsrc/common/mutators/mutator/hook/_mod.qh index 50c0c13726..5a5d26e817 100644 --- a/qcsrc/common/mutators/mutator/hook/_mod.qh +++ b/qcsrc/common/mutators/mutator/hook/_mod.qh @@ -1,2 +1,4 @@ // generated file; do not modify -#include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/mutators/mutator/hook/hook.qc b/qcsrc/common/mutators/mutator/hook/hook.qc deleted file mode 100644 index e72134e0e7..0000000000 --- a/qcsrc/common/mutators/mutator/hook/hook.qc +++ /dev/null @@ -1,47 +0,0 @@ -#ifdef IMPLEMENTATION -AUTOCVAR(g_grappling_hook, bool, false, _("let players spawn with the grappling hook which allows them to pull themselves up")); -#ifdef SVQC -REGISTER_MUTATOR(hook, autocvar_g_grappling_hook) { - MUTATOR_ONADD { - g_grappling_hook = true; - WEP_HOOK.ammo_factor = 0; - } - MUTATOR_ONROLLBACK_OR_REMOVE { - g_grappling_hook = false; - WEP_HOOK.ammo_factor = 1; - } - - return false; -} - -MUTATOR_HOOKFUNCTION(hook, BuildMutatorsString) -{ - M_ARGV(0, string) = strcat(M_ARGV(0, string), ":grappling_hook"); -} - -MUTATOR_HOOKFUNCTION(hook, BuildMutatorsPrettyString) -{ - M_ARGV(0, string) = strcat(M_ARGV(0, string), ", Hook"); -} - -MUTATOR_HOOKFUNCTION(hook, BuildGameplayTipsString) -{ - M_ARGV(0, string) = strcat(M_ARGV(0, string), "\n\n^3grappling hook^8 is enabled, press 'e' to use it\n"); -} - -MUTATOR_HOOKFUNCTION(hook, PlayerSpawn) -{ - entity player = M_ARGV(0, entity); - - player.offhand = OFFHAND_HOOK; -} - -MUTATOR_HOOKFUNCTION(hook, FilterItem) -{ - entity item = M_ARGV(0, entity); - - return item.weapon == WEP_HOOK.m_id; -} - -#endif -#endif diff --git a/qcsrc/common/mutators/mutator/hook/module.inc b/qcsrc/common/mutators/mutator/hook/module.inc deleted file mode 100644 index 61600c47b1..0000000000 --- a/qcsrc/common/mutators/mutator/hook/module.inc +++ /dev/null @@ -1,3 +0,0 @@ -#ifdef SVQC -#include "hook.qc" -#endif diff --git a/qcsrc/common/mutators/mutator/hook/sv_hook.qc b/qcsrc/common/mutators/mutator/hook/sv_hook.qc new file mode 100644 index 0000000000..b5a196c511 --- /dev/null +++ b/qcsrc/common/mutators/mutator/hook/sv_hook.qc @@ -0,0 +1,47 @@ +#include "sv_hook.qh" + +AUTOCVAR(g_grappling_hook, bool, false, _("let players spawn with the grappling hook which allows them to pull themselves up")); +#ifdef SVQC +REGISTER_MUTATOR(hook, autocvar_g_grappling_hook) { + MUTATOR_ONADD { + g_grappling_hook = true; + WEP_HOOK.ammo_factor = 0; + } + MUTATOR_ONROLLBACK_OR_REMOVE { + g_grappling_hook = false; + WEP_HOOK.ammo_factor = 1; + } + + return false; +} + +MUTATOR_HOOKFUNCTION(hook, BuildMutatorsString) +{ + M_ARGV(0, string) = strcat(M_ARGV(0, string), ":grappling_hook"); +} + +MUTATOR_HOOKFUNCTION(hook, BuildMutatorsPrettyString) +{ + M_ARGV(0, string) = strcat(M_ARGV(0, string), ", Hook"); +} + +MUTATOR_HOOKFUNCTION(hook, BuildGameplayTipsString) +{ + M_ARGV(0, string) = strcat(M_ARGV(0, string), "\n\n^3grappling hook^8 is enabled, press 'e' to use it\n"); +} + +MUTATOR_HOOKFUNCTION(hook, PlayerSpawn) +{ + entity player = M_ARGV(0, entity); + + player.offhand = OFFHAND_HOOK; +} + +MUTATOR_HOOKFUNCTION(hook, FilterItem) +{ + entity item = M_ARGV(0, entity); + + return item.weapon == WEP_HOOK.m_id; +} + +#endif diff --git a/qcsrc/common/mutators/mutator/hook/sv_hook.qh b/qcsrc/common/mutators/mutator/hook/sv_hook.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/mutators/mutator/hook/sv_hook.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mutators/mutator/instagib/_mod.inc b/qcsrc/common/mutators/mutator/instagib/_mod.inc index dad005fe49..2195111f0f 100644 --- a/qcsrc/common/mutators/mutator/instagib/_mod.inc +++ b/qcsrc/common/mutators/mutator/instagib/_mod.inc @@ -1,3 +1,5 @@ // generated file; do not modify -#include #include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/mutators/mutator/instagib/_mod.qh b/qcsrc/common/mutators/mutator/instagib/_mod.qh index 2e88f427e9..7097eaf390 100644 --- a/qcsrc/common/mutators/mutator/instagib/_mod.qh +++ b/qcsrc/common/mutators/mutator/instagib/_mod.qh @@ -1,3 +1,5 @@ // generated file; do not modify -#include #include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/mutators/mutator/instagib/instagib.qc b/qcsrc/common/mutators/mutator/instagib/instagib.qc deleted file mode 100644 index 25edbb225c..0000000000 --- a/qcsrc/common/mutators/mutator/instagib/instagib.qc +++ /dev/null @@ -1,505 +0,0 @@ -#ifndef MUTATOR_INSTAGIB_H -#define MUTATOR_INSTAGIB_H - -#include "items.qc" - -#ifdef SVQC -float autocvar_g_instagib_invis_alpha; -#endif - -#endif - -#ifdef IMPLEMENTATION -#ifdef SVQC - -int autocvar_g_instagib_ammo_drop; -int autocvar_g_instagib_extralives; -float autocvar_g_instagib_speed_highspeed; - -#include - -#include - -REGISTER_MUTATOR(mutator_instagib, cvar("g_instagib") && !g_nexball); - -spawnfunc(item_minst_cells) -{ - if (!g_instagib) { delete(this); return; } - if (!this.ammo_cells) this.ammo_cells = autocvar_g_instagib_ammo_drop; - StartItem(this, ITEM_VaporizerCells); -} - -void instagib_invisibility(entity this) -{ - this.strength_finished = autocvar_g_balance_powerup_strength_time; - StartItem(this, ITEM_Invisibility); -} - -void instagib_extralife(entity this) -{ - StartItem(this, ITEM_ExtraLife); -} - -void instagib_speed(entity this) -{ - this.invincible_finished = autocvar_g_balance_powerup_invincible_time; - StartItem(this, ITEM_Speed); -} - -.float instagib_nextthink; -.float instagib_needammo; -void instagib_stop_countdown(entity e) -{ - if (!e.instagib_needammo) - return; - Kill_Notification(NOTIF_ONE_ONLY, e, MSG_CENTER, CPID_INSTAGIB_FINDAMMO); - e.instagib_needammo = false; -} -void instagib_ammocheck(entity this) -{ - if(time < this.instagib_nextthink) - return; - if(!IS_PLAYER(this)) - return; // not a player - - if(IS_DEAD(this) || gameover) - instagib_stop_countdown(this); - else if (this.ammo_cells > 0 || (this.items & IT_UNLIMITED_WEAPON_AMMO) || (this.flags & FL_GODMODE)) - instagib_stop_countdown(this); - else if(autocvar_g_rm && autocvar_g_rm_laser) - { - if(!this.instagib_needammo) - { - Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_INSTAGIB_DOWNGRADE); - this.instagib_needammo = true; - } - } - else - { - this.instagib_needammo = true; - if (this.health <= 5) - { - Damage(this, this, this, 5, DEATH_NOAMMO.m_id, this.origin, '0 0 0'); - Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_INSTAGIB_TERMINATED); - } - else if (this.health <= 10) - { - Damage(this, this, this, 5, DEATH_NOAMMO.m_id, this.origin, '0 0 0'); - Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_NUM_1); - } - else if (this.health <= 20) - { - Damage(this, this, this, 10, DEATH_NOAMMO.m_id, this.origin, '0 0 0'); - Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_NUM_2); - } - else if (this.health <= 30) - { - Damage(this, this, this, 10, DEATH_NOAMMO.m_id, this.origin, '0 0 0'); - Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_NUM_3); - } - else if (this.health <= 40) - { - Damage(this, this, this, 10, DEATH_NOAMMO.m_id, this.origin, '0 0 0'); - Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_NUM_4); - } - else if (this.health <= 50) - { - Damage(this, this, this, 10, DEATH_NOAMMO.m_id, this.origin, '0 0 0'); - Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_NUM_5); - } - else if (this.health <= 60) - { - Damage(this, this, this, 10, DEATH_NOAMMO.m_id, this.origin, '0 0 0'); - Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_NUM_6); - } - else if (this.health <= 70) - { - Damage(this, this, this, 10, DEATH_NOAMMO.m_id, this.origin, '0 0 0'); - Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_NUM_7); - } - else if (this.health <= 80) - { - Damage(this, this, this, 10, DEATH_NOAMMO.m_id, this.origin, '0 0 0'); - Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_NUM_8); - } - else if (this.health <= 90) - { - Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_INSTAGIB_FINDAMMO); - Damage(this, this, this, 10, DEATH_NOAMMO.m_id, this.origin, '0 0 0'); - Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_NUM_9); - } - else - { - Send_Notification(NOTIF_ONE_ONLY, this, MSG_MULTI, MULTI_INSTAGIB_FINDAMMO); - Damage(this, this, this, 10, DEATH_NOAMMO.m_id, this.origin, '0 0 0'); - } - } - this.instagib_nextthink = time + 1; -} - -MUTATOR_HOOKFUNCTION(mutator_instagib, MatchEnd) -{ - FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(instagib_stop_countdown(it))); -} - -MUTATOR_HOOKFUNCTION(mutator_instagib, MonsterDropItem) -{ - entity item = M_ARGV(1, entity); - - item.monster_loot = spawnfunc_item_minst_cells; -} - -MUTATOR_HOOKFUNCTION(mutator_instagib, MonsterSpawn) -{ - entity mon = M_ARGV(0, entity); - - // always refill ammo - if(mon.monsterid == MON_MAGE.monsterid) - mon.skin = 1; -} - -MUTATOR_HOOKFUNCTION(mutator_instagib, BotShouldAttack) -{ - entity targ = M_ARGV(1, entity); - - if (targ.items & ITEM_Invisibility.m_itemid) - return true; -} - -MUTATOR_HOOKFUNCTION(mutator_instagib, MakePlayerObserver) -{ - entity player = M_ARGV(0, entity); - - instagib_stop_countdown(player); -} - -MUTATOR_HOOKFUNCTION(mutator_instagib, PlayerSpawn) -{ - entity player = M_ARGV(0, entity); - - player.effects |= EF_FULLBRIGHT; -} - -MUTATOR_HOOKFUNCTION(mutator_instagib, PlayerPreThink) -{ - entity player = M_ARGV(0, entity); - - instagib_ammocheck(player); -} - -MUTATOR_HOOKFUNCTION(mutator_instagib, PlayerRegen) -{ - // no regeneration in instagib - return true; -} - -MUTATOR_HOOKFUNCTION(mutator_instagib, PlayerPowerups) -{ - entity player = M_ARGV(0, entity); - - if (!(player.effects & EF_FULLBRIGHT)) - player.effects |= EF_FULLBRIGHT; - - if (player.items & ITEM_Invisibility.m_itemid) - { - play_countdown(player, player.strength_finished, SND_POWEROFF); - if (time > player.strength_finished) - { - player.alpha = default_player_alpha; - player.exteriorweaponentity.alpha = default_weapon_alpha; - player.items &= ~ITEM_Invisibility.m_itemid; - Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_POWERDOWN_INVISIBILITY); - } - } - else - { - if (time < player.strength_finished) - { - player.alpha = autocvar_g_instagib_invis_alpha; - player.exteriorweaponentity.alpha = autocvar_g_instagib_invis_alpha; - player.items |= ITEM_Invisibility.m_itemid; - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_POWERUP_INVISIBILITY, player.netname); - Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_POWERUP_INVISIBILITY); - } - } - - if (player.items & ITEM_Speed.m_itemid) - { - play_countdown(player, player.invincible_finished, SND_POWEROFF); - if (time > player.invincible_finished) - { - player.items &= ~ITEM_Speed.m_itemid; - Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_POWERDOWN_SPEED); - } - } - else - { - if (time < player.invincible_finished) - { - player.items |= ITEM_Speed.m_itemid; - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_POWERUP_SPEED, player.netname); - Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_POWERUP_SPEED); - } - } -} - -.float stat_sv_maxspeed; - -MUTATOR_HOOKFUNCTION(mutator_instagib, PlayerPhysics) -{ - entity player = M_ARGV(0, entity); - - if(player.items & ITEM_Speed.m_itemid) - player.stat_sv_maxspeed = player.stat_sv_maxspeed * autocvar_g_instagib_speed_highspeed; -} - -MUTATOR_HOOKFUNCTION(mutator_instagib, PlayerDamage_SplitHealthArmor) -{ - M_ARGV(4, float) = M_ARGV(7, float); // take = damage - M_ARGV(5, float) = 0; // save -} - -MUTATOR_HOOKFUNCTION(mutator_instagib, ForbidThrowCurrentWeapon) -{ - // weapon dropping on death handled by FilterItem - return true; -} - -MUTATOR_HOOKFUNCTION(mutator_instagib, PlayerDamage_Calculate) -{ - entity frag_attacker = M_ARGV(1, entity); - entity frag_target = M_ARGV(2, entity); - float frag_deathtype = M_ARGV(3, float); - float frag_damage = M_ARGV(4, float); - float frag_mirrordamage = M_ARGV(5, float); - vector frag_force = M_ARGV(6, vector); - - if(autocvar_g_friendlyfire == 0 && SAME_TEAM(frag_target, frag_attacker) && IS_PLAYER(frag_target) && IS_PLAYER(frag_attacker)) - frag_damage = 0; - - if(IS_PLAYER(frag_target)) - { - if(frag_deathtype == DEATH_FALL.m_id) - frag_damage = 0; // never count fall damage - - if(!autocvar_g_instagib_damagedbycontents) - switch(DEATH_ENT(frag_deathtype)) - { - case DEATH_DROWN: - case DEATH_SLIME: - case DEATH_LAVA: - frag_damage = 0; - break; - } - - if(IS_PLAYER(frag_attacker)) - if(DEATH_ISWEAPON(frag_deathtype, WEP_VAPORIZER)) - { - if(!autocvar_g_instagib_friendlypush && SAME_TEAM(frag_target, frag_attacker)) - frag_force = '0 0 0'; - - if(frag_target.armorvalue) - { - frag_target.armorvalue -= 1; - frag_damage = 0; - frag_target.damage_dealt += 1; - frag_attacker.damage_dealt += 1; - Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_INSTAGIB_LIVES_REMAINING, frag_target.armorvalue); - } - } - - if(IS_PLAYER(frag_attacker) && DEATH_ISWEAPON(frag_deathtype, WEP_BLASTER)) - { - if(frag_deathtype & HITTYPE_SECONDARY) - { - if(!autocvar_g_instagib_blaster_keepdamage || frag_attacker == frag_target) - { - frag_damage = 0; - if(!autocvar_g_instagib_mirrordamage) - frag_mirrordamage = 0; // never do mirror damage on enemies - } - - if(frag_target != frag_attacker) - { - if(frag_damage <= 0 && frag_target.health > 0) { Send_Notification(NOTIF_ONE, frag_attacker, MSG_CENTER, CENTER_SECONDARY_NODAMAGE); } - if(!autocvar_g_instagib_blaster_keepforce) - frag_force = '0 0 0'; - } - } - } - } - - if(!autocvar_g_instagib_mirrordamage) // only apply the taking lives hack if we don't want to support real damage mirroring - if(IS_PLAYER(frag_attacker)) - if(frag_mirrordamage > 0) - { - // just lose extra LIVES, don't kill the player for mirror damage - if(frag_attacker.armorvalue > 0) - { - frag_attacker.armorvalue -= 1; - Send_Notification(NOTIF_ONE, frag_attacker, MSG_CENTER, CENTER_INSTAGIB_LIVES_REMAINING, frag_attacker.armorvalue); - frag_attacker.damage_dealt += frag_mirrordamage; - } - frag_mirrordamage = 0; - } - - if(frag_target.alpha && frag_target.alpha < 1) - if(IS_PLAYER(frag_target)) - yoda = 1; - - M_ARGV(4, float) = frag_damage; - M_ARGV(5, float) = frag_mirrordamage; - M_ARGV(6, vector) = frag_force; -} - -MUTATOR_HOOKFUNCTION(mutator_instagib, SetStartItems) -{ - start_health = warmup_start_health = 100; - start_armorvalue = warmup_start_armorvalue = 0; - - start_ammo_shells = warmup_start_ammo_shells = 0; - start_ammo_nails = warmup_start_ammo_nails = 0; - start_ammo_cells = warmup_start_ammo_cells = cvar("g_instagib_ammo_start"); - start_ammo_plasma = warmup_start_ammo_plasma = 0; - start_ammo_rockets = warmup_start_ammo_rockets = 0; - start_ammo_fuel = warmup_start_ammo_fuel = 0; - - start_weapons = warmup_start_weapons = WEPSET(VAPORIZER); - start_items |= IT_UNLIMITED_SUPERWEAPONS; -} - -MUTATOR_HOOKFUNCTION(mutator_instagib, FilterItem) -{ - entity item = M_ARGV(0, entity); - - if(item.classname == "item_cells") - return true; // no normal cells? - - if(item.weapon == WEP_VAPORIZER.m_id && item.classname == "droppedweapon") - { - item.ammo_cells = autocvar_g_instagib_ammo_drop; - return false; - } - - if(item.weapon == WEP_DEVASTATOR.m_id || item.weapon == WEP_VORTEX.m_id) - { - entity e = spawn(); - setorigin(e, item.origin); - e.noalign = item.noalign; - e.cnt = item.cnt; - e.team = item.team; - e.spawnfunc_checked = true; - spawnfunc_item_minst_cells(e); - return true; - } - - if(item.flags & FL_POWERUP) - return false; - - if(item.ammo_cells > autocvar_g_instagib_ammo_drop && item.classname != "item_minst_cells") - item.ammo_cells = autocvar_g_instagib_ammo_drop; - - if(item.ammo_cells && !item.weapon) - return false; - - return true; -} - -MUTATOR_HOOKFUNCTION(mutator_instagib, CustomizeWaypoint) -{ - entity wp = M_ARGV(0, entity); - entity player = M_ARGV(1, entity); - - entity e = WaypointSprite_getviewentity(player); - - // if you have the invisibility powerup, sprites ALWAYS are restricted to your team - // but only apply this to real players, not to spectators - if((wp.owner.flags & FL_CLIENT) && (wp.owner.items & ITEM_Invisibility.m_itemid) && (e == player)) - if(DIFF_TEAM(wp.owner, e)) - return true; -} - -MUTATOR_HOOKFUNCTION(mutator_instagib, PlayerDies) -{ - float frag_deathtype = M_ARGV(3, float); - - if(DEATH_ISWEAPON(frag_deathtype, WEP_VAPORIZER)) - M_ARGV(4, float) = 1000; // always gib if it was a vaporizer death -} - -MUTATOR_HOOKFUNCTION(mutator_instagib, ItemTouch) -{ - entity item = M_ARGV(0, entity); - entity toucher = M_ARGV(1, entity); - - if(item.ammo_cells) - { - // play some cool sounds ;) - if (IS_CLIENT(toucher)) - { - if(toucher.health <= 5) - Send_Notification(NOTIF_ONE, toucher, MSG_ANNCE, ANNCE_INSTAGIB_LASTSECOND); - else if(toucher.health < 50) - Send_Notification(NOTIF_ONE, toucher, MSG_ANNCE, ANNCE_INSTAGIB_NARROWLY); - } - - if(toucher.health < 100) - toucher.health = 100; - - return MUT_ITEMTOUCH_CONTINUE; - } - - if(item.itemdef == ITEM_ExtraLife) - { - toucher.armorvalue = bound(toucher.armorvalue, 999, toucher.armorvalue + autocvar_g_instagib_extralives); - Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_EXTRALIVES); - return MUT_ITEMTOUCH_PICKUP; - } - - return MUT_ITEMTOUCH_CONTINUE; -} - -MUTATOR_HOOKFUNCTION(mutator_instagib, OnEntityPreSpawn) -{ - if (!autocvar_g_powerups) { return; } - entity ent = M_ARGV(0, entity); - // Can't use .itemdef here - if (!(ent.classname == "item_strength" || ent.classname == "item_invincible" || ent.classname == "item_health_mega")) - return; - - entity e = spawn(); - - float r = random(); - if (r < 0.3) - setthink(e, instagib_invisibility); - else if (r < 0.6) - setthink(e, instagib_extralife); - else - setthink(e, instagib_speed); - - e.nextthink = time + 0.1; - e.spawnflags = ent.spawnflags; - e.noalign = ent.noalign; - setorigin(e, ent.origin); - - return true; -} - -MUTATOR_HOOKFUNCTION(mutator_instagib, BuildMutatorsString) -{ - M_ARGV(0, string) = strcat(M_ARGV(0, string), ":instagib"); -} - -MUTATOR_HOOKFUNCTION(mutator_instagib, BuildMutatorsPrettyString) -{ - M_ARGV(0, string) = strcat(M_ARGV(0, string), ", instagib"); -} - -MUTATOR_HOOKFUNCTION(mutator_instagib, SetModname) -{ - M_ARGV(0, string) = "InstaGib"; - return true; -} - -#endif -#endif diff --git a/qcsrc/common/mutators/mutator/instagib/items.qc b/qcsrc/common/mutators/mutator/instagib/items.qc index 269ec2d245..b0205a5f7b 100644 --- a/qcsrc/common/mutators/mutator/instagib/items.qc +++ b/qcsrc/common/mutators/mutator/instagib/items.qc @@ -1,84 +1 @@ -#pragma once - -#include -#include -#include - -float instagib_respawntime_ammo = 45; -float instagib_respawntimejitter_ammo = 0; -GETTER(float, instagib_respawntime_ammo) -GETTER(float, instagib_respawntimejitter_ammo) - -#ifndef MENUQC -MODEL(VaporizerCells_ITEM, Item_Model("a_cells.md3")); -SOUND(VaporizerCells, "misc/itempickup"); -#endif - -REGISTER_ITEM(VaporizerCells, Ammo) { -#ifndef MENUQC - this.m_model = MDL_VaporizerCells_ITEM; - this.m_sound = SND_VaporizerCells; -#endif - this.m_name = "Vaporizer Ammo"; - this.m_icon = "ammo_supercells"; -#ifdef SVQC - this.m_botvalue = 100; - this.m_itemid = IT_CELLS; - this.m_respawntime = GET(instagib_respawntime_ammo); - this.m_respawntimejitter = GET(instagib_respawntimejitter_ammo); -#endif -} - -#ifndef MENUQC -MODEL(ExtraLife_ITEM, Item_Model("g_h100.md3")); -SOUND(ExtraLife, "misc/megahealth"); -#endif - -REGISTER_ITEM(ExtraLife, Powerup) { -#ifndef MENUQC - this.m_model = MDL_ExtraLife_ITEM; - this.m_sound = SND_ExtraLife; -#endif - this.m_name = "Extra life"; - this.m_icon = "item_mega_health"; - this.m_color = '1 0 0'; - this.m_waypoint = _("Extra life"); - this.m_waypointblink = 2; - this.m_itemid = IT_NAILS; -} - -#ifndef MENUQC -MODEL(Invisibility_ITEM, Item_Model("g_strength.md3")); -SOUND(Invisibility, "misc/powerup"); -#endif - -REGISTER_ITEM(Invisibility, Powerup) { -#ifndef MENUQC - this.m_model = MDL_Invisibility_ITEM; - this.m_sound = SND_Invisibility; -#endif - this.m_name = "Invisibility"; - this.m_icon = "strength"; - this.m_color = '0 0 1'; - this.m_waypoint = _("Invisibility"); - this.m_waypointblink = 2; - this.m_itemid = IT_STRENGTH; -} - -#ifndef MENUQC -MODEL(Speed_ITEM, Item_Model("g_invincible.md3")); -SOUND(Speed, "misc/powerup_shield"); -#endif - -REGISTER_ITEM(Speed, Powerup) { -#ifndef MENUQC - this.m_model = MDL_Speed_ITEM; - this.m_sound = SND_Speed; -#endif - this.m_name = "Speed"; - this.m_icon = "shield"; - this.m_color = '1 0 1'; - this.m_waypoint = _("Speed"); - this.m_waypointblink = 2; - this.m_itemid = IT_INVINCIBLE; -} +#include "items.qh" diff --git a/qcsrc/common/mutators/mutator/instagib/items.qh b/qcsrc/common/mutators/mutator/instagib/items.qh new file mode 100644 index 0000000000..269ec2d245 --- /dev/null +++ b/qcsrc/common/mutators/mutator/instagib/items.qh @@ -0,0 +1,84 @@ +#pragma once + +#include +#include +#include + +float instagib_respawntime_ammo = 45; +float instagib_respawntimejitter_ammo = 0; +GETTER(float, instagib_respawntime_ammo) +GETTER(float, instagib_respawntimejitter_ammo) + +#ifndef MENUQC +MODEL(VaporizerCells_ITEM, Item_Model("a_cells.md3")); +SOUND(VaporizerCells, "misc/itempickup"); +#endif + +REGISTER_ITEM(VaporizerCells, Ammo) { +#ifndef MENUQC + this.m_model = MDL_VaporizerCells_ITEM; + this.m_sound = SND_VaporizerCells; +#endif + this.m_name = "Vaporizer Ammo"; + this.m_icon = "ammo_supercells"; +#ifdef SVQC + this.m_botvalue = 100; + this.m_itemid = IT_CELLS; + this.m_respawntime = GET(instagib_respawntime_ammo); + this.m_respawntimejitter = GET(instagib_respawntimejitter_ammo); +#endif +} + +#ifndef MENUQC +MODEL(ExtraLife_ITEM, Item_Model("g_h100.md3")); +SOUND(ExtraLife, "misc/megahealth"); +#endif + +REGISTER_ITEM(ExtraLife, Powerup) { +#ifndef MENUQC + this.m_model = MDL_ExtraLife_ITEM; + this.m_sound = SND_ExtraLife; +#endif + this.m_name = "Extra life"; + this.m_icon = "item_mega_health"; + this.m_color = '1 0 0'; + this.m_waypoint = _("Extra life"); + this.m_waypointblink = 2; + this.m_itemid = IT_NAILS; +} + +#ifndef MENUQC +MODEL(Invisibility_ITEM, Item_Model("g_strength.md3")); +SOUND(Invisibility, "misc/powerup"); +#endif + +REGISTER_ITEM(Invisibility, Powerup) { +#ifndef MENUQC + this.m_model = MDL_Invisibility_ITEM; + this.m_sound = SND_Invisibility; +#endif + this.m_name = "Invisibility"; + this.m_icon = "strength"; + this.m_color = '0 0 1'; + this.m_waypoint = _("Invisibility"); + this.m_waypointblink = 2; + this.m_itemid = IT_STRENGTH; +} + +#ifndef MENUQC +MODEL(Speed_ITEM, Item_Model("g_invincible.md3")); +SOUND(Speed, "misc/powerup_shield"); +#endif + +REGISTER_ITEM(Speed, Powerup) { +#ifndef MENUQC + this.m_model = MDL_Speed_ITEM; + this.m_sound = SND_Speed; +#endif + this.m_name = "Speed"; + this.m_icon = "shield"; + this.m_color = '1 0 1'; + this.m_waypoint = _("Speed"); + this.m_waypointblink = 2; + this.m_itemid = IT_INVINCIBLE; +} diff --git a/qcsrc/common/mutators/mutator/instagib/module.inc b/qcsrc/common/mutators/mutator/instagib/module.inc deleted file mode 100644 index 7270067d35..0000000000 --- a/qcsrc/common/mutators/mutator/instagib/module.inc +++ /dev/null @@ -1 +0,0 @@ -#include "instagib.qc" diff --git a/qcsrc/common/mutators/mutator/instagib/sv_instagib.qc b/qcsrc/common/mutators/mutator/instagib/sv_instagib.qc new file mode 100644 index 0000000000..6176ebbabb --- /dev/null +++ b/qcsrc/common/mutators/mutator/instagib/sv_instagib.qc @@ -0,0 +1,490 @@ +#include "sv_instagib.qh" + +int autocvar_g_instagib_ammo_drop; +int autocvar_g_instagib_extralives; +float autocvar_g_instagib_speed_highspeed; + +#include + +#include + +REGISTER_MUTATOR(mutator_instagib, cvar("g_instagib") && !g_nexball); + +spawnfunc(item_minst_cells) +{ + if (!g_instagib) { delete(this); return; } + if (!this.ammo_cells) this.ammo_cells = autocvar_g_instagib_ammo_drop; + StartItem(this, ITEM_VaporizerCells); +} + +void instagib_invisibility(entity this) +{ + this.strength_finished = autocvar_g_balance_powerup_strength_time; + StartItem(this, ITEM_Invisibility); +} + +void instagib_extralife(entity this) +{ + StartItem(this, ITEM_ExtraLife); +} + +void instagib_speed(entity this) +{ + this.invincible_finished = autocvar_g_balance_powerup_invincible_time; + StartItem(this, ITEM_Speed); +} + +.float instagib_nextthink; +.float instagib_needammo; +void instagib_stop_countdown(entity e) +{ + if (!e.instagib_needammo) + return; + Kill_Notification(NOTIF_ONE_ONLY, e, MSG_CENTER, CPID_INSTAGIB_FINDAMMO); + e.instagib_needammo = false; +} +void instagib_ammocheck(entity this) +{ + if(time < this.instagib_nextthink) + return; + if(!IS_PLAYER(this)) + return; // not a player + + if(IS_DEAD(this) || gameover) + instagib_stop_countdown(this); + else if (this.ammo_cells > 0 || (this.items & IT_UNLIMITED_WEAPON_AMMO) || (this.flags & FL_GODMODE)) + instagib_stop_countdown(this); + else if(autocvar_g_rm && autocvar_g_rm_laser) + { + if(!this.instagib_needammo) + { + Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_INSTAGIB_DOWNGRADE); + this.instagib_needammo = true; + } + } + else + { + this.instagib_needammo = true; + if (this.health <= 5) + { + Damage(this, this, this, 5, DEATH_NOAMMO.m_id, this.origin, '0 0 0'); + Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_INSTAGIB_TERMINATED); + } + else if (this.health <= 10) + { + Damage(this, this, this, 5, DEATH_NOAMMO.m_id, this.origin, '0 0 0'); + Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_NUM_1); + } + else if (this.health <= 20) + { + Damage(this, this, this, 10, DEATH_NOAMMO.m_id, this.origin, '0 0 0'); + Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_NUM_2); + } + else if (this.health <= 30) + { + Damage(this, this, this, 10, DEATH_NOAMMO.m_id, this.origin, '0 0 0'); + Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_NUM_3); + } + else if (this.health <= 40) + { + Damage(this, this, this, 10, DEATH_NOAMMO.m_id, this.origin, '0 0 0'); + Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_NUM_4); + } + else if (this.health <= 50) + { + Damage(this, this, this, 10, DEATH_NOAMMO.m_id, this.origin, '0 0 0'); + Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_NUM_5); + } + else if (this.health <= 60) + { + Damage(this, this, this, 10, DEATH_NOAMMO.m_id, this.origin, '0 0 0'); + Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_NUM_6); + } + else if (this.health <= 70) + { + Damage(this, this, this, 10, DEATH_NOAMMO.m_id, this.origin, '0 0 0'); + Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_NUM_7); + } + else if (this.health <= 80) + { + Damage(this, this, this, 10, DEATH_NOAMMO.m_id, this.origin, '0 0 0'); + Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_NUM_8); + } + else if (this.health <= 90) + { + Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_INSTAGIB_FINDAMMO); + Damage(this, this, this, 10, DEATH_NOAMMO.m_id, this.origin, '0 0 0'); + Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_NUM_9); + } + else + { + Send_Notification(NOTIF_ONE_ONLY, this, MSG_MULTI, MULTI_INSTAGIB_FINDAMMO); + Damage(this, this, this, 10, DEATH_NOAMMO.m_id, this.origin, '0 0 0'); + } + } + this.instagib_nextthink = time + 1; +} + +MUTATOR_HOOKFUNCTION(mutator_instagib, MatchEnd) +{ + FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(instagib_stop_countdown(it))); +} + +MUTATOR_HOOKFUNCTION(mutator_instagib, MonsterDropItem) +{ + entity item = M_ARGV(1, entity); + + item.monster_loot = spawnfunc_item_minst_cells; +} + +MUTATOR_HOOKFUNCTION(mutator_instagib, MonsterSpawn) +{ + entity mon = M_ARGV(0, entity); + + // always refill ammo + if(mon.monsterid == MON_MAGE.monsterid) + mon.skin = 1; +} + +MUTATOR_HOOKFUNCTION(mutator_instagib, BotShouldAttack) +{ + entity targ = M_ARGV(1, entity); + + if (targ.items & ITEM_Invisibility.m_itemid) + return true; +} + +MUTATOR_HOOKFUNCTION(mutator_instagib, MakePlayerObserver) +{ + entity player = M_ARGV(0, entity); + + instagib_stop_countdown(player); +} + +MUTATOR_HOOKFUNCTION(mutator_instagib, PlayerSpawn) +{ + entity player = M_ARGV(0, entity); + + player.effects |= EF_FULLBRIGHT; +} + +MUTATOR_HOOKFUNCTION(mutator_instagib, PlayerPreThink) +{ + entity player = M_ARGV(0, entity); + + instagib_ammocheck(player); +} + +MUTATOR_HOOKFUNCTION(mutator_instagib, PlayerRegen) +{ + // no regeneration in instagib + return true; +} + +MUTATOR_HOOKFUNCTION(mutator_instagib, PlayerPowerups) +{ + entity player = M_ARGV(0, entity); + + if (!(player.effects & EF_FULLBRIGHT)) + player.effects |= EF_FULLBRIGHT; + + if (player.items & ITEM_Invisibility.m_itemid) + { + play_countdown(player, player.strength_finished, SND_POWEROFF); + if (time > player.strength_finished) + { + player.alpha = default_player_alpha; + player.exteriorweaponentity.alpha = default_weapon_alpha; + player.items &= ~ITEM_Invisibility.m_itemid; + Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_POWERDOWN_INVISIBILITY); + } + } + else + { + if (time < player.strength_finished) + { + player.alpha = autocvar_g_instagib_invis_alpha; + player.exteriorweaponentity.alpha = autocvar_g_instagib_invis_alpha; + player.items |= ITEM_Invisibility.m_itemid; + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_POWERUP_INVISIBILITY, player.netname); + Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_POWERUP_INVISIBILITY); + } + } + + if (player.items & ITEM_Speed.m_itemid) + { + play_countdown(player, player.invincible_finished, SND_POWEROFF); + if (time > player.invincible_finished) + { + player.items &= ~ITEM_Speed.m_itemid; + Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_POWERDOWN_SPEED); + } + } + else + { + if (time < player.invincible_finished) + { + player.items |= ITEM_Speed.m_itemid; + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_POWERUP_SPEED, player.netname); + Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_POWERUP_SPEED); + } + } +} + +.float stat_sv_maxspeed; + +MUTATOR_HOOKFUNCTION(mutator_instagib, PlayerPhysics) +{ + entity player = M_ARGV(0, entity); + + if(player.items & ITEM_Speed.m_itemid) + player.stat_sv_maxspeed = player.stat_sv_maxspeed * autocvar_g_instagib_speed_highspeed; +} + +MUTATOR_HOOKFUNCTION(mutator_instagib, PlayerDamage_SplitHealthArmor) +{ + M_ARGV(4, float) = M_ARGV(7, float); // take = damage + M_ARGV(5, float) = 0; // save +} + +MUTATOR_HOOKFUNCTION(mutator_instagib, ForbidThrowCurrentWeapon) +{ + // weapon dropping on death handled by FilterItem + return true; +} + +MUTATOR_HOOKFUNCTION(mutator_instagib, PlayerDamage_Calculate) +{ + entity frag_attacker = M_ARGV(1, entity); + entity frag_target = M_ARGV(2, entity); + float frag_deathtype = M_ARGV(3, float); + float frag_damage = M_ARGV(4, float); + float frag_mirrordamage = M_ARGV(5, float); + vector frag_force = M_ARGV(6, vector); + + if(autocvar_g_friendlyfire == 0 && SAME_TEAM(frag_target, frag_attacker) && IS_PLAYER(frag_target) && IS_PLAYER(frag_attacker)) + frag_damage = 0; + + if(IS_PLAYER(frag_target)) + { + if(frag_deathtype == DEATH_FALL.m_id) + frag_damage = 0; // never count fall damage + + if(!autocvar_g_instagib_damagedbycontents) + switch(DEATH_ENT(frag_deathtype)) + { + case DEATH_DROWN: + case DEATH_SLIME: + case DEATH_LAVA: + frag_damage = 0; + break; + } + + if(IS_PLAYER(frag_attacker)) + if(DEATH_ISWEAPON(frag_deathtype, WEP_VAPORIZER)) + { + if(!autocvar_g_instagib_friendlypush && SAME_TEAM(frag_target, frag_attacker)) + frag_force = '0 0 0'; + + if(frag_target.armorvalue) + { + frag_target.armorvalue -= 1; + frag_damage = 0; + frag_target.damage_dealt += 1; + frag_attacker.damage_dealt += 1; + Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_INSTAGIB_LIVES_REMAINING, frag_target.armorvalue); + } + } + + if(IS_PLAYER(frag_attacker) && DEATH_ISWEAPON(frag_deathtype, WEP_BLASTER)) + { + if(frag_deathtype & HITTYPE_SECONDARY) + { + if(!autocvar_g_instagib_blaster_keepdamage || frag_attacker == frag_target) + { + frag_damage = 0; + if(!autocvar_g_instagib_mirrordamage) + frag_mirrordamage = 0; // never do mirror damage on enemies + } + + if(frag_target != frag_attacker) + { + if(frag_damage <= 0 && frag_target.health > 0) { Send_Notification(NOTIF_ONE, frag_attacker, MSG_CENTER, CENTER_SECONDARY_NODAMAGE); } + if(!autocvar_g_instagib_blaster_keepforce) + frag_force = '0 0 0'; + } + } + } + } + + if(!autocvar_g_instagib_mirrordamage) // only apply the taking lives hack if we don't want to support real damage mirroring + if(IS_PLAYER(frag_attacker)) + if(frag_mirrordamage > 0) + { + // just lose extra LIVES, don't kill the player for mirror damage + if(frag_attacker.armorvalue > 0) + { + frag_attacker.armorvalue -= 1; + Send_Notification(NOTIF_ONE, frag_attacker, MSG_CENTER, CENTER_INSTAGIB_LIVES_REMAINING, frag_attacker.armorvalue); + frag_attacker.damage_dealt += frag_mirrordamage; + } + frag_mirrordamage = 0; + } + + if(frag_target.alpha && frag_target.alpha < 1) + if(IS_PLAYER(frag_target)) + yoda = 1; + + M_ARGV(4, float) = frag_damage; + M_ARGV(5, float) = frag_mirrordamage; + M_ARGV(6, vector) = frag_force; +} + +MUTATOR_HOOKFUNCTION(mutator_instagib, SetStartItems) +{ + start_health = warmup_start_health = 100; + start_armorvalue = warmup_start_armorvalue = 0; + + start_ammo_shells = warmup_start_ammo_shells = 0; + start_ammo_nails = warmup_start_ammo_nails = 0; + start_ammo_cells = warmup_start_ammo_cells = cvar("g_instagib_ammo_start"); + start_ammo_plasma = warmup_start_ammo_plasma = 0; + start_ammo_rockets = warmup_start_ammo_rockets = 0; + start_ammo_fuel = warmup_start_ammo_fuel = 0; + + start_weapons = warmup_start_weapons = WEPSET(VAPORIZER); + start_items |= IT_UNLIMITED_SUPERWEAPONS; +} + +MUTATOR_HOOKFUNCTION(mutator_instagib, FilterItem) +{ + entity item = M_ARGV(0, entity); + + if(item.classname == "item_cells") + return true; // no normal cells? + + if(item.weapon == WEP_VAPORIZER.m_id && item.classname == "droppedweapon") + { + item.ammo_cells = autocvar_g_instagib_ammo_drop; + return false; + } + + if(item.weapon == WEP_DEVASTATOR.m_id || item.weapon == WEP_VORTEX.m_id) + { + entity e = spawn(); + setorigin(e, item.origin); + e.noalign = item.noalign; + e.cnt = item.cnt; + e.team = item.team; + e.spawnfunc_checked = true; + spawnfunc_item_minst_cells(e); + return true; + } + + if(item.flags & FL_POWERUP) + return false; + + if(item.ammo_cells > autocvar_g_instagib_ammo_drop && item.classname != "item_minst_cells") + item.ammo_cells = autocvar_g_instagib_ammo_drop; + + if(item.ammo_cells && !item.weapon) + return false; + + return true; +} + +MUTATOR_HOOKFUNCTION(mutator_instagib, CustomizeWaypoint) +{ + entity wp = M_ARGV(0, entity); + entity player = M_ARGV(1, entity); + + entity e = WaypointSprite_getviewentity(player); + + // if you have the invisibility powerup, sprites ALWAYS are restricted to your team + // but only apply this to real players, not to spectators + if((wp.owner.flags & FL_CLIENT) && (wp.owner.items & ITEM_Invisibility.m_itemid) && (e == player)) + if(DIFF_TEAM(wp.owner, e)) + return true; +} + +MUTATOR_HOOKFUNCTION(mutator_instagib, PlayerDies) +{ + float frag_deathtype = M_ARGV(3, float); + + if(DEATH_ISWEAPON(frag_deathtype, WEP_VAPORIZER)) + M_ARGV(4, float) = 1000; // always gib if it was a vaporizer death +} + +MUTATOR_HOOKFUNCTION(mutator_instagib, ItemTouch) +{ + entity item = M_ARGV(0, entity); + entity toucher = M_ARGV(1, entity); + + if(item.ammo_cells) + { + // play some cool sounds ;) + if (IS_CLIENT(toucher)) + { + if(toucher.health <= 5) + Send_Notification(NOTIF_ONE, toucher, MSG_ANNCE, ANNCE_INSTAGIB_LASTSECOND); + else if(toucher.health < 50) + Send_Notification(NOTIF_ONE, toucher, MSG_ANNCE, ANNCE_INSTAGIB_NARROWLY); + } + + if(toucher.health < 100) + toucher.health = 100; + + return MUT_ITEMTOUCH_CONTINUE; + } + + if(item.itemdef == ITEM_ExtraLife) + { + toucher.armorvalue = bound(toucher.armorvalue, 999, toucher.armorvalue + autocvar_g_instagib_extralives); + Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_EXTRALIVES); + return MUT_ITEMTOUCH_PICKUP; + } + + return MUT_ITEMTOUCH_CONTINUE; +} + +MUTATOR_HOOKFUNCTION(mutator_instagib, OnEntityPreSpawn) +{ + if (!autocvar_g_powerups) { return; } + entity ent = M_ARGV(0, entity); + // Can't use .itemdef here + if (!(ent.classname == "item_strength" || ent.classname == "item_invincible" || ent.classname == "item_health_mega")) + return; + + entity e = spawn(); + + float r = random(); + if (r < 0.3) + setthink(e, instagib_invisibility); + else if (r < 0.6) + setthink(e, instagib_extralife); + else + setthink(e, instagib_speed); + + e.nextthink = time + 0.1; + e.spawnflags = ent.spawnflags; + e.noalign = ent.noalign; + setorigin(e, ent.origin); + + return true; +} + +MUTATOR_HOOKFUNCTION(mutator_instagib, BuildMutatorsString) +{ + M_ARGV(0, string) = strcat(M_ARGV(0, string), ":instagib"); +} + +MUTATOR_HOOKFUNCTION(mutator_instagib, BuildMutatorsPrettyString) +{ + M_ARGV(0, string) = strcat(M_ARGV(0, string), ", instagib"); +} + +MUTATOR_HOOKFUNCTION(mutator_instagib, SetModname) +{ + M_ARGV(0, string) = "InstaGib"; + return true; +} diff --git a/qcsrc/common/mutators/mutator/instagib/sv_instagib.qh b/qcsrc/common/mutators/mutator/instagib/sv_instagib.qh new file mode 100644 index 0000000000..4c6d20b129 --- /dev/null +++ b/qcsrc/common/mutators/mutator/instagib/sv_instagib.qh @@ -0,0 +1,5 @@ +#pragma once + +#include "items.qh" + +float autocvar_g_instagib_invis_alpha; diff --git a/qcsrc/common/mutators/mutator/invincibleproj/_mod.inc b/qcsrc/common/mutators/mutator/invincibleproj/_mod.inc index 68d313e95c..eb8c95fcb8 100644 --- a/qcsrc/common/mutators/mutator/invincibleproj/_mod.inc +++ b/qcsrc/common/mutators/mutator/invincibleproj/_mod.inc @@ -1,2 +1,4 @@ // generated file; do not modify -#include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/mutators/mutator/invincibleproj/_mod.qh b/qcsrc/common/mutators/mutator/invincibleproj/_mod.qh index dc3f32f10e..2d59a0891a 100644 --- a/qcsrc/common/mutators/mutator/invincibleproj/_mod.qh +++ b/qcsrc/common/mutators/mutator/invincibleproj/_mod.qh @@ -1,2 +1,4 @@ // generated file; do not modify -#include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/mutators/mutator/invincibleproj/invincibleproj.qc b/qcsrc/common/mutators/mutator/invincibleproj/invincibleproj.qc deleted file mode 100644 index 5bdafa1f12..0000000000 --- a/qcsrc/common/mutators/mutator/invincibleproj/invincibleproj.qc +++ /dev/null @@ -1,24 +0,0 @@ -#ifdef IMPLEMENTATION -REGISTER_MUTATOR(invincibleprojectiles, cvar("g_invincible_projectiles")); - -MUTATOR_HOOKFUNCTION(invincibleprojectiles, EditProjectile) -{ - entity proj = M_ARGV(1, entity); - - if(proj.health) - { - // disable health which in effect disables damage calculations - proj.health = 0; - } -} - -MUTATOR_HOOKFUNCTION(invincibleprojectiles, BuildMutatorsString) -{ - M_ARGV(0, string) = strcat(M_ARGV(0, string), ":InvincibleProjectiles"); -} - -MUTATOR_HOOKFUNCTION(invincibleprojectiles, BuildMutatorsPrettyString) -{ - M_ARGV(0, string) = strcat(M_ARGV(0, string), ", Invincible Projectiles"); -} -#endif diff --git a/qcsrc/common/mutators/mutator/invincibleproj/module.inc b/qcsrc/common/mutators/mutator/invincibleproj/module.inc deleted file mode 100644 index 61d038350f..0000000000 --- a/qcsrc/common/mutators/mutator/invincibleproj/module.inc +++ /dev/null @@ -1,3 +0,0 @@ -#ifdef SVQC -#include "invincibleproj.qc" -#endif diff --git a/qcsrc/common/mutators/mutator/invincibleproj/sv_invincibleproj.qc b/qcsrc/common/mutators/mutator/invincibleproj/sv_invincibleproj.qc new file mode 100644 index 0000000000..23e0d0d850 --- /dev/null +++ b/qcsrc/common/mutators/mutator/invincibleproj/sv_invincibleproj.qc @@ -0,0 +1,24 @@ +#include "sv_invincibleproj.qh" + +REGISTER_MUTATOR(invincibleprojectiles, cvar("g_invincible_projectiles")); + +MUTATOR_HOOKFUNCTION(invincibleprojectiles, EditProjectile) +{ + entity proj = M_ARGV(1, entity); + + if(proj.health) + { + // disable health which in effect disables damage calculations + proj.health = 0; + } +} + +MUTATOR_HOOKFUNCTION(invincibleprojectiles, BuildMutatorsString) +{ + M_ARGV(0, string) = strcat(M_ARGV(0, string), ":InvincibleProjectiles"); +} + +MUTATOR_HOOKFUNCTION(invincibleprojectiles, BuildMutatorsPrettyString) +{ + M_ARGV(0, string) = strcat(M_ARGV(0, string), ", Invincible Projectiles"); +} diff --git a/qcsrc/common/mutators/mutator/invincibleproj/sv_invincibleproj.qh b/qcsrc/common/mutators/mutator/invincibleproj/sv_invincibleproj.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/mutators/mutator/invincibleproj/sv_invincibleproj.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mutators/mutator/itemstime.qc b/qcsrc/common/mutators/mutator/itemstime.qc deleted file mode 100644 index c6afa33161..0000000000 --- a/qcsrc/common/mutators/mutator/itemstime.qc +++ /dev/null @@ -1,434 +0,0 @@ -#ifdef IMPLEMENTATION -REGISTER_MUTATOR(itemstime, true); - -REGISTER_NET_TEMP(itemstime) - -#ifdef SVQC -void IT_Write(entity e, int i, float f) { - if (!IS_REAL_CLIENT(e)) return; - msg_entity = e; - WriteHeader(MSG_ONE, itemstime); - WriteByte(MSG_ONE, i); - WriteFloat(MSG_ONE, f); -} -#endif - -#ifdef CSQC -// reserve one more spot for superweapons time -float ItemsTime_time[Items_MAX + 1]; -float ItemsTime_availableTime[Items_MAX + 1]; -NET_HANDLE(itemstime, bool isNew) -{ - int i = ReadByte(); - float f = ReadFloat(); - return = true; - ItemsTime_time[i] = f; -} -#endif - -#ifdef CSQC -void Item_ItemsTime_Init() -{ - FOREACH(Items, true, LAMBDA( - ItemsTime_time[it.m_id] = -1; - )); - ItemsTime_time[Items_MAX] = -1; -} - -STATIC_INIT(ItemsTime_Init) { - Item_ItemsTime_Init(); -} - -int autocvar_hud_panel_itemstime = 2; -float autocvar_hud_panel_itemstime_dynamicsize = 1; -float autocvar_hud_panel_itemstime_ratio = 2; -int autocvar_hud_panel_itemstime_iconalign; -bool autocvar_hud_panel_itemstime_progressbar = 0; -float autocvar_hud_panel_itemstime_progressbar_maxtime = 30; -string autocvar_hud_panel_itemstime_progressbar_name = "progressbar"; -float autocvar_hud_panel_itemstime_progressbar_reduced; -bool autocvar_hud_panel_itemstime_hidespawned = 1; -bool autocvar_hud_panel_itemstime_hidelarge = false; -int autocvar_hud_panel_itemstime_text = 1; -#define hud_panel_itemstime_hidelarge autocvar_hud_panel_itemstime_hidelarge -#else -#define hud_panel_itemstime_hidelarge false -#endif - -bool Item_ItemsTime_SpectatorOnly(GameItem it) -{ - return (false - || it == ITEM_ArmorMega || (it == ITEM_ArmorLarge && !hud_panel_itemstime_hidelarge) - || it == ITEM_HealthMega || (it == ITEM_HealthLarge && !hud_panel_itemstime_hidelarge) - ); -} - -bool Item_ItemsTime_Allow(GameItem it) -{ - return (false - || it.instanceOfPowerup - || Item_ItemsTime_SpectatorOnly(it) - ); -} - -#ifdef SVQC - -// reserve one more spot for superweapons time -float it_times[Items_MAX + 1]; - -void Item_ItemsTime_Init() -{ - FOREACH(Items, Item_ItemsTime_Allow(it), LAMBDA( - it_times[it.m_id] = -1; - )); - it_times[Items_MAX] = -1; -} - -STATIC_INIT(ItemsTime_Init) { - // items time - Item_ItemsTime_Init(); -} - -void Item_ItemsTime_ResetTimes() -{ - FOREACH(Items, Item_ItemsTime_Allow(it), LAMBDA( - it_times[it.m_id] = (it_times[it.m_id] == -1) ? -1 : 0; - )); - it_times[Items_MAX] = (it_times[Items_MAX] == -1) ? -1 : 0; -} - -void Item_ItemsTime_ResetTimesForPlayer(entity e) -{ - FOREACH(Items, Item_ItemsTime_Allow(it), LAMBDA( - IT_Write(e, it.m_id, (it_times[it.m_id] == -1) ? -1 : 0); - )); - IT_Write(e, Items_MAX, (it_times[Items_MAX] == -1) ? -1 : 0); -} - -void Item_ItemsTime_SetTimesForPlayer(entity e) -{ - FOREACH(Items, Item_ItemsTime_Allow(it), LAMBDA( - IT_Write(e, it.m_id, it_times[it.m_id]); - )); - IT_Write(e, Items_MAX, it_times[Items_MAX]); -} - -void Item_ItemsTime_SetTime(entity e, float t) -{ - if (!autocvar_sv_itemstime) - return; - - GameItem item = e.itemdef; - if (item.instanceOfGameItem) - { - if (!item.instanceOfWeaponPickup) - it_times[item.m_id] = t; - else if (e.weapons & WEPSET_SUPERWEAPONS) - it_times[Items_MAX] = t; - } -} - -void Item_ItemsTime_SetTimesForAllPlayers() -{ - FOREACH_CLIENT(IS_REAL_CLIENT(it) && (warmup_stage || !IS_PLAYER(it) || autocvar_sv_itemstime == 2), LAMBDA(Item_ItemsTime_SetTimesForPlayer(it))); -} - -float Item_ItemsTime_UpdateTime(entity e, float t) -{ - bool isavailable = (t == 0); - FOREACH_ENTITY_FLOAT(pure_data, false, - { - if(!(it.itemdef == e.itemdef || ((e.weapons & WEPSET_SUPERWEAPONS) && (it.weapons & WEPSET_SUPERWEAPONS) && clienttype(it) == CLIENTTYPE_NOTACLIENT))) - continue; - if (e == it) continue; - if (it.scheduledrespawntime <= time) - isavailable = true; - else if (t == 0 || it.scheduledrespawntime < t) - t = it.scheduledrespawntime; - }); - if (isavailable) - t = -t; // let know the client there's another available item - return t; -} - -MUTATOR_HOOKFUNCTION(itemstime, reset_map_global) -{ - Item_ItemsTime_ResetTimes(); - // ALL the times need to be reset before .reset()ing each item - // since Item_Reset schedules respawn of superweapons and powerups - FOREACH_ENTITY_FLOAT(pure_data, false, - { - if(IS_CLIENT(it)) - continue; - if (it.reset) Item_ItemsTime_SetTime(it, 0); - }); - Item_ItemsTime_SetTimesForAllPlayers(); -} - -MUTATOR_HOOKFUNCTION(itemstime, MakePlayerObserver) -{ - entity player = M_ARGV(0, entity); - - Item_ItemsTime_SetTimesForPlayer(player); -} - -MUTATOR_HOOKFUNCTION(itemstime, ClientConnect, CBC_ORDER_LAST) -{ - entity player = M_ARGV(0, entity); - - if(IS_PLAYER(player)) - { - // client became player on connection skipping putObserverInServer step - if (IS_REAL_CLIENT(player)) - if (warmup_stage || autocvar_sv_itemstime == 2) - Item_ItemsTime_SetTimesForPlayer(player); - } -} - -MUTATOR_HOOKFUNCTION(itemstime, PlayerSpawn) -{ - if (warmup_stage || autocvar_sv_itemstime == 2) return; - entity player = M_ARGV(0, entity); - - Item_ItemsTime_ResetTimesForPlayer(player); -} - -#endif - -#ifdef CSQC - -void DrawItemsTimeItem(vector myPos, vector mySize, float ar, string item_icon, float item_time, bool item_available, float item_availableTime) -{ - float t = 0; - vector color = '0 0 0'; - float picalpha; - - if (autocvar_hud_panel_itemstime_hidespawned == 2) - picalpha = 1; - else if (item_available) - { - float BLINK_FACTOR = 0.15; - float BLINK_BASE = 0.85; - float BLINK_FREQ = 5; - picalpha = BLINK_BASE + BLINK_FACTOR * cos(time * BLINK_FREQ); - } - else - picalpha = 0.5; - t = floor(item_time - time + 0.999); - if (t < 5) - color = '0.7 0 0'; - else if (t < 10) - color = '0.7 0.7 0'; - else - color = '1 1 1'; - - vector picpos, numpos; - if (autocvar_hud_panel_itemstime_iconalign) - { - numpos = myPos; - picpos = myPos + eX * (ar - 1) * mySize_y; - } - else - { - numpos = myPos + eX * mySize_y; - picpos = myPos; - } - - if (t > 0 && autocvar_hud_panel_itemstime_progressbar) - { - vector p_pos, p_size; - if (autocvar_hud_panel_itemstime_progressbar_reduced) - { - p_pos = numpos; - p_size = eX * ((ar - 1)/ar) * mySize_x + eY * mySize_y; - } - else - { - p_pos = myPos; - p_size = mySize; - } - HUD_Panel_DrawProgressBar(p_pos, p_size, autocvar_hud_panel_itemstime_progressbar_name, t/autocvar_hud_panel_itemstime_progressbar_maxtime, 0, autocvar_hud_panel_itemstime_iconalign, color, autocvar_hud_progressbar_alpha * panel_fg_alpha, DRAWFLAG_NORMAL); - } - - if(autocvar_hud_panel_itemstime_text) - { - if(t > 0) - drawstring_aspect(numpos, ftos(t), eX * ((ar - 1)/ar) * mySize_x + eY * mySize_y, color, panel_fg_alpha, DRAWFLAG_NORMAL); - else if(precache_pic("gfx/hud/default/checkmark")) // COMPAT: check if this image exists, as 0.8.1 clients lack it - drawpic_aspect_skin(numpos, "checkmark", eX * (ar - 1) * mySize_y + eY * mySize_y, '1 1 1', panel_fg_alpha * picalpha, DRAWFLAG_NORMAL); - else // legacy code, if the image is missing just center the icon - picpos.x = myPos.x + mySize.x / 2 - mySize.y / 2; - } - if (item_availableTime) - drawpic_aspect_skin_expanding(picpos, item_icon, '1 1 0' * mySize_y, '1 1 1', panel_fg_alpha * picalpha, DRAWFLAG_NORMAL, item_availableTime); - drawpic_aspect_skin(picpos, item_icon, '1 1 0' * mySize_y, '1 1 1', panel_fg_alpha * picalpha, DRAWFLAG_NORMAL); -} - -void HUD_ItemsTime() -{ - if (!autocvar__hud_configure) - { - if (!( - (autocvar_hud_panel_itemstime == 1 && spectatee_status != 0) - || (autocvar_hud_panel_itemstime == 2 && (spectatee_status != 0 || warmup_stage || STAT(ITEMSTIME) == 2)) - )) { return; } - } - else - { - ItemsTime_time[ITEM_ArmorMega.m_id] = time + 0; - ItemsTime_time[ITEM_HealthMega.m_id] = time + 8; - ItemsTime_time[ITEM_Strength.m_id] = time + 0; - ItemsTime_time[ITEM_Shield.m_id] = time + 4; - } - - int count = 0; - if (autocvar_hud_panel_itemstime_hidespawned == 1) - { - FOREACH(Items, Item_ItemsTime_Allow(it), LAMBDA( - count += (ItemsTime_time[it.m_id] > time || -ItemsTime_time[it.m_id] > time); - )); - count += (ItemsTime_time[Items_MAX] > time || -ItemsTime_time[Items_MAX] > time); - } - else if (autocvar_hud_panel_itemstime_hidespawned == 2) - { - FOREACH(Items, Item_ItemsTime_Allow(it), LAMBDA( - count += (ItemsTime_time[it.m_id] > time); - )); - count += (ItemsTime_time[Items_MAX] > time); - } - else - { - FOREACH(Items, Item_ItemsTime_Allow(it), LAMBDA( - count += (ItemsTime_time[it.m_id] != -1); - )); - count += (ItemsTime_time[Items_MAX] != -1); - } - if (count == 0) - return; - - HUD_Panel_UpdateCvars(); - - vector pos, mySize; - pos = panel_pos; - mySize = panel_size; - - if (panel_bg_padding) - { - pos += '1 1 0' * panel_bg_padding; - mySize -= '2 2 0' * panel_bg_padding; - } - - float rows, columns; - float ar = max(2, autocvar_hud_panel_itemstime_ratio) + 1; - rows = HUD_GetRowCount(count, mySize, ar); - columns = ceil(count/rows); - - vector itemstime_size = eX * mySize.x*(1/columns) + eY * mySize.y*(1/rows); - - vector offset = '0 0 0'; - float newSize; - if (autocvar_hud_panel_itemstime_dynamicsize) - { - if (autocvar__hud_configure) - if (menu_enabled != 2) - HUD_Panel_DrawBg(1); // also draw the bg of the entire panel - - // reduce panel to avoid spacing items - if (itemstime_size.x / itemstime_size.y < ar) - { - newSize = rows * itemstime_size.x / ar; - pos.y += (mySize.y - newSize) / 2; - mySize.y = newSize; - itemstime_size.y = mySize.y / rows; - } - else - { - newSize = columns * itemstime_size.y * ar; - pos.x += (mySize.x - newSize) / 2; - mySize.x = newSize; - itemstime_size.x = mySize.x / columns; - } - panel_pos = pos - '1 1 0' * panel_bg_padding; - panel_size = mySize + '2 2 0' * panel_bg_padding; - } - else - { - if (itemstime_size.x/itemstime_size.y > ar) - { - newSize = ar * itemstime_size.y; - offset.x = itemstime_size.x - newSize; - pos.x += offset.x/2; - itemstime_size.x = newSize; - } - else - { - newSize = 1/ar * itemstime_size.x; - offset.y = itemstime_size.y - newSize; - pos.y += offset.y/2; - itemstime_size.y = newSize; - } - } - - HUD_Scale_Enable(); - HUD_Panel_DrawBg(1); - - float row = 0, column = 0; - bool item_available; - int id = 0; - string icon = ""; - FOREACH(Items, Item_ItemsTime_Allow(it) && ItemsTime_time[it.m_id] != -1, LAMBDA( - id = it.m_id; - icon = it.m_icon; - -LABEL(iteration) - float item_time = ItemsTime_time[id]; - if (item_time < -1) - { - item_available = true; - item_time = -item_time; - } - else - item_available = (item_time <= time); - - if (ItemsTime_time[id] >= 0) - { - if (time <= ItemsTime_time[id]) - ItemsTime_availableTime[id] = 0; - else if (ItemsTime_availableTime[id] == 0) - ItemsTime_availableTime[id] = time; - } - else if (ItemsTime_availableTime[id] == 0) - ItemsTime_availableTime[id] = time; - - float f = (time - ItemsTime_availableTime[id]) * 2; - f = (f > 1) ? 0 : bound(0, f, 1); - - if (autocvar_hud_panel_itemstime_hidespawned == 1) - if (!(ItemsTime_time[id] > time || -ItemsTime_time[id] > time)) - continue; - - if (autocvar_hud_panel_itemstime_hidespawned == 2) - if (!(ItemsTime_time[id] > time)) - continue; - - DrawItemsTimeItem(pos + eX * column * (itemstime_size.x + offset.x) + eY * row * (itemstime_size.y + offset.y), itemstime_size, ar, icon, item_time, item_available, f); - ++row; - if (row >= rows) - { - row = 0; - column = column + 1; - } - if(id == Items_MAX) // can happen only in the last fake iteration - break; - )); - // add another fake iteration for superweapons time - if(id < Items_MAX && ItemsTime_time[Items_MAX] != -1) - { - id = Items_MAX; - icon = "superweapons"; - goto iteration; - } -} - -#endif -#endif diff --git a/qcsrc/common/mutators/mutator/itemstime/_mod.inc b/qcsrc/common/mutators/mutator/itemstime/_mod.inc new file mode 100644 index 0000000000..5b34dd6210 --- /dev/null +++ b/qcsrc/common/mutators/mutator/itemstime/_mod.inc @@ -0,0 +1,2 @@ +// generated file; do not modify +#include diff --git a/qcsrc/common/mutators/mutator/itemstime/_mod.qh b/qcsrc/common/mutators/mutator/itemstime/_mod.qh new file mode 100644 index 0000000000..5c73eea2ec --- /dev/null +++ b/qcsrc/common/mutators/mutator/itemstime/_mod.qh @@ -0,0 +1,2 @@ +// generated file; do not modify +#include diff --git a/qcsrc/common/mutators/mutator/itemstime/itemstime.qc b/qcsrc/common/mutators/mutator/itemstime/itemstime.qc new file mode 100644 index 0000000000..0c348b6241 --- /dev/null +++ b/qcsrc/common/mutators/mutator/itemstime/itemstime.qc @@ -0,0 +1,434 @@ +#include "itemstime.qh" + +REGISTER_MUTATOR(itemstime, true); + +REGISTER_NET_TEMP(itemstime) + +#ifdef SVQC +void IT_Write(entity e, int i, float f) { + if (!IS_REAL_CLIENT(e)) return; + msg_entity = e; + WriteHeader(MSG_ONE, itemstime); + WriteByte(MSG_ONE, i); + WriteFloat(MSG_ONE, f); +} +#endif + +#ifdef CSQC +// reserve one more spot for superweapons time +float ItemsTime_time[Items_MAX + 1]; +float ItemsTime_availableTime[Items_MAX + 1]; +NET_HANDLE(itemstime, bool isNew) +{ + int i = ReadByte(); + float f = ReadFloat(); + return = true; + ItemsTime_time[i] = f; +} +#endif + +#ifdef CSQC +void Item_ItemsTime_Init() +{ + FOREACH(Items, true, LAMBDA( + ItemsTime_time[it.m_id] = -1; + )); + ItemsTime_time[Items_MAX] = -1; +} + +STATIC_INIT(ItemsTime_Init) { + Item_ItemsTime_Init(); +} + +int autocvar_hud_panel_itemstime = 2; +float autocvar_hud_panel_itemstime_dynamicsize = 1; +float autocvar_hud_panel_itemstime_ratio = 2; +int autocvar_hud_panel_itemstime_iconalign; +bool autocvar_hud_panel_itemstime_progressbar = 0; +float autocvar_hud_panel_itemstime_progressbar_maxtime = 30; +string autocvar_hud_panel_itemstime_progressbar_name = "progressbar"; +float autocvar_hud_panel_itemstime_progressbar_reduced; +bool autocvar_hud_panel_itemstime_hidespawned = 1; +bool autocvar_hud_panel_itemstime_hidelarge = false; +int autocvar_hud_panel_itemstime_text = 1; +#define hud_panel_itemstime_hidelarge autocvar_hud_panel_itemstime_hidelarge +#else +#define hud_panel_itemstime_hidelarge false +#endif + +bool Item_ItemsTime_SpectatorOnly(GameItem it) +{ + return (false + || it == ITEM_ArmorMega || (it == ITEM_ArmorLarge && !hud_panel_itemstime_hidelarge) + || it == ITEM_HealthMega || (it == ITEM_HealthLarge && !hud_panel_itemstime_hidelarge) + ); +} + +bool Item_ItemsTime_Allow(GameItem it) +{ + return (false + || it.instanceOfPowerup + || Item_ItemsTime_SpectatorOnly(it) + ); +} + +#ifdef SVQC + +// reserve one more spot for superweapons time +float it_times[Items_MAX + 1]; + +void Item_ItemsTime_Init() +{ + FOREACH(Items, Item_ItemsTime_Allow(it), LAMBDA( + it_times[it.m_id] = -1; + )); + it_times[Items_MAX] = -1; +} + +STATIC_INIT(ItemsTime_Init) { + // items time + Item_ItemsTime_Init(); +} + +void Item_ItemsTime_ResetTimes() +{ + FOREACH(Items, Item_ItemsTime_Allow(it), LAMBDA( + it_times[it.m_id] = (it_times[it.m_id] == -1) ? -1 : 0; + )); + it_times[Items_MAX] = (it_times[Items_MAX] == -1) ? -1 : 0; +} + +void Item_ItemsTime_ResetTimesForPlayer(entity e) +{ + FOREACH(Items, Item_ItemsTime_Allow(it), LAMBDA( + IT_Write(e, it.m_id, (it_times[it.m_id] == -1) ? -1 : 0); + )); + IT_Write(e, Items_MAX, (it_times[Items_MAX] == -1) ? -1 : 0); +} + +void Item_ItemsTime_SetTimesForPlayer(entity e) +{ + FOREACH(Items, Item_ItemsTime_Allow(it), LAMBDA( + IT_Write(e, it.m_id, it_times[it.m_id]); + )); + IT_Write(e, Items_MAX, it_times[Items_MAX]); +} + +void Item_ItemsTime_SetTime(entity e, float t) +{ + if (!autocvar_sv_itemstime) + return; + + GameItem item = e.itemdef; + if (item.instanceOfGameItem) + { + if (!item.instanceOfWeaponPickup) + it_times[item.m_id] = t; + else if (e.weapons & WEPSET_SUPERWEAPONS) + it_times[Items_MAX] = t; + } +} + +void Item_ItemsTime_SetTimesForAllPlayers() +{ + FOREACH_CLIENT(IS_REAL_CLIENT(it) && (warmup_stage || !IS_PLAYER(it) || autocvar_sv_itemstime == 2), LAMBDA(Item_ItemsTime_SetTimesForPlayer(it))); +} + +float Item_ItemsTime_UpdateTime(entity e, float t) +{ + bool isavailable = (t == 0); + FOREACH_ENTITY_FLOAT(pure_data, false, + { + if(!(it.itemdef == e.itemdef || ((e.weapons & WEPSET_SUPERWEAPONS) && (it.weapons & WEPSET_SUPERWEAPONS) && clienttype(it) == CLIENTTYPE_NOTACLIENT))) + continue; + if (e == it) continue; + if (it.scheduledrespawntime <= time) + isavailable = true; + else if (t == 0 || it.scheduledrespawntime < t) + t = it.scheduledrespawntime; + }); + if (isavailable) + t = -t; // let know the client there's another available item + return t; +} + +MUTATOR_HOOKFUNCTION(itemstime, reset_map_global) +{ + Item_ItemsTime_ResetTimes(); + // ALL the times need to be reset before .reset()ing each item + // since Item_Reset schedules respawn of superweapons and powerups + FOREACH_ENTITY_FLOAT(pure_data, false, + { + if(IS_CLIENT(it)) + continue; + if (it.reset) Item_ItemsTime_SetTime(it, 0); + }); + Item_ItemsTime_SetTimesForAllPlayers(); +} + +MUTATOR_HOOKFUNCTION(itemstime, MakePlayerObserver) +{ + entity player = M_ARGV(0, entity); + + Item_ItemsTime_SetTimesForPlayer(player); +} + +MUTATOR_HOOKFUNCTION(itemstime, ClientConnect, CBC_ORDER_LAST) +{ + entity player = M_ARGV(0, entity); + + if(IS_PLAYER(player)) + { + // client became player on connection skipping putObserverInServer step + if (IS_REAL_CLIENT(player)) + if (warmup_stage || autocvar_sv_itemstime == 2) + Item_ItemsTime_SetTimesForPlayer(player); + } +} + +MUTATOR_HOOKFUNCTION(itemstime, PlayerSpawn) +{ + if (warmup_stage || autocvar_sv_itemstime == 2) return; + entity player = M_ARGV(0, entity); + + Item_ItemsTime_ResetTimesForPlayer(player); +} + +#endif + +#ifdef CSQC + +void DrawItemsTimeItem(vector myPos, vector mySize, float ar, string item_icon, float item_time, bool item_available, float item_availableTime) +{ + float t = 0; + vector color = '0 0 0'; + float picalpha; + + if (autocvar_hud_panel_itemstime_hidespawned == 2) + picalpha = 1; + else if (item_available) + { + float BLINK_FACTOR = 0.15; + float BLINK_BASE = 0.85; + float BLINK_FREQ = 5; + picalpha = BLINK_BASE + BLINK_FACTOR * cos(time * BLINK_FREQ); + } + else + picalpha = 0.5; + t = floor(item_time - time + 0.999); + if (t < 5) + color = '0.7 0 0'; + else if (t < 10) + color = '0.7 0.7 0'; + else + color = '1 1 1'; + + vector picpos, numpos; + if (autocvar_hud_panel_itemstime_iconalign) + { + numpos = myPos; + picpos = myPos + eX * (ar - 1) * mySize_y; + } + else + { + numpos = myPos + eX * mySize_y; + picpos = myPos; + } + + if (t > 0 && autocvar_hud_panel_itemstime_progressbar) + { + vector p_pos, p_size; + if (autocvar_hud_panel_itemstime_progressbar_reduced) + { + p_pos = numpos; + p_size = eX * ((ar - 1)/ar) * mySize_x + eY * mySize_y; + } + else + { + p_pos = myPos; + p_size = mySize; + } + HUD_Panel_DrawProgressBar(p_pos, p_size, autocvar_hud_panel_itemstime_progressbar_name, t/autocvar_hud_panel_itemstime_progressbar_maxtime, 0, autocvar_hud_panel_itemstime_iconalign, color, autocvar_hud_progressbar_alpha * panel_fg_alpha, DRAWFLAG_NORMAL); + } + + if(autocvar_hud_panel_itemstime_text) + { + if(t > 0) + drawstring_aspect(numpos, ftos(t), eX * ((ar - 1)/ar) * mySize_x + eY * mySize_y, color, panel_fg_alpha, DRAWFLAG_NORMAL); + else if(precache_pic("gfx/hud/default/checkmark")) // COMPAT: check if this image exists, as 0.8.1 clients lack it + drawpic_aspect_skin(numpos, "checkmark", eX * (ar - 1) * mySize_y + eY * mySize_y, '1 1 1', panel_fg_alpha * picalpha, DRAWFLAG_NORMAL); + else // legacy code, if the image is missing just center the icon + picpos.x = myPos.x + mySize.x / 2 - mySize.y / 2; + } + if (item_availableTime) + drawpic_aspect_skin_expanding(picpos, item_icon, '1 1 0' * mySize_y, '1 1 1', panel_fg_alpha * picalpha, DRAWFLAG_NORMAL, item_availableTime); + drawpic_aspect_skin(picpos, item_icon, '1 1 0' * mySize_y, '1 1 1', panel_fg_alpha * picalpha, DRAWFLAG_NORMAL); +} + +void HUD_ItemsTime() +{ + if (!autocvar__hud_configure) + { + if (!( + (autocvar_hud_panel_itemstime == 1 && spectatee_status != 0) + || (autocvar_hud_panel_itemstime == 2 && (spectatee_status != 0 || warmup_stage || STAT(ITEMSTIME) == 2)) + )) { return; } + } + else + { + ItemsTime_time[ITEM_ArmorMega.m_id] = time + 0; + ItemsTime_time[ITEM_HealthMega.m_id] = time + 8; + ItemsTime_time[ITEM_Strength.m_id] = time + 0; + ItemsTime_time[ITEM_Shield.m_id] = time + 4; + } + + int count = 0; + if (autocvar_hud_panel_itemstime_hidespawned == 1) + { + FOREACH(Items, Item_ItemsTime_Allow(it), LAMBDA( + count += (ItemsTime_time[it.m_id] > time || -ItemsTime_time[it.m_id] > time); + )); + count += (ItemsTime_time[Items_MAX] > time || -ItemsTime_time[Items_MAX] > time); + } + else if (autocvar_hud_panel_itemstime_hidespawned == 2) + { + FOREACH(Items, Item_ItemsTime_Allow(it), LAMBDA( + count += (ItemsTime_time[it.m_id] > time); + )); + count += (ItemsTime_time[Items_MAX] > time); + } + else + { + FOREACH(Items, Item_ItemsTime_Allow(it), LAMBDA( + count += (ItemsTime_time[it.m_id] != -1); + )); + count += (ItemsTime_time[Items_MAX] != -1); + } + if (count == 0) + return; + + HUD_Panel_UpdateCvars(); + + vector pos, mySize; + pos = panel_pos; + mySize = panel_size; + + if (panel_bg_padding) + { + pos += '1 1 0' * panel_bg_padding; + mySize -= '2 2 0' * panel_bg_padding; + } + + float rows, columns; + float ar = max(2, autocvar_hud_panel_itemstime_ratio) + 1; + rows = HUD_GetRowCount(count, mySize, ar); + columns = ceil(count/rows); + + vector itemstime_size = eX * mySize.x*(1/columns) + eY * mySize.y*(1/rows); + + vector offset = '0 0 0'; + float newSize; + if (autocvar_hud_panel_itemstime_dynamicsize) + { + if (autocvar__hud_configure) + if (menu_enabled != 2) + HUD_Panel_DrawBg(1); // also draw the bg of the entire panel + + // reduce panel to avoid spacing items + if (itemstime_size.x / itemstime_size.y < ar) + { + newSize = rows * itemstime_size.x / ar; + pos.y += (mySize.y - newSize) / 2; + mySize.y = newSize; + itemstime_size.y = mySize.y / rows; + } + else + { + newSize = columns * itemstime_size.y * ar; + pos.x += (mySize.x - newSize) / 2; + mySize.x = newSize; + itemstime_size.x = mySize.x / columns; + } + panel_pos = pos - '1 1 0' * panel_bg_padding; + panel_size = mySize + '2 2 0' * panel_bg_padding; + } + else + { + if (itemstime_size.x/itemstime_size.y > ar) + { + newSize = ar * itemstime_size.y; + offset.x = itemstime_size.x - newSize; + pos.x += offset.x/2; + itemstime_size.x = newSize; + } + else + { + newSize = 1/ar * itemstime_size.x; + offset.y = itemstime_size.y - newSize; + pos.y += offset.y/2; + itemstime_size.y = newSize; + } + } + + HUD_Scale_Enable(); + HUD_Panel_DrawBg(1); + + float row = 0, column = 0; + bool item_available; + int id = 0; + string icon = ""; + FOREACH(Items, Item_ItemsTime_Allow(it) && ItemsTime_time[it.m_id] != -1, LAMBDA( + id = it.m_id; + icon = it.m_icon; + +LABEL(iteration) + float item_time = ItemsTime_time[id]; + if (item_time < -1) + { + item_available = true; + item_time = -item_time; + } + else + item_available = (item_time <= time); + + if (ItemsTime_time[id] >= 0) + { + if (time <= ItemsTime_time[id]) + ItemsTime_availableTime[id] = 0; + else if (ItemsTime_availableTime[id] == 0) + ItemsTime_availableTime[id] = time; + } + else if (ItemsTime_availableTime[id] == 0) + ItemsTime_availableTime[id] = time; + + float f = (time - ItemsTime_availableTime[id]) * 2; + f = (f > 1) ? 0 : bound(0, f, 1); + + if (autocvar_hud_panel_itemstime_hidespawned == 1) + if (!(ItemsTime_time[id] > time || -ItemsTime_time[id] > time)) + continue; + + if (autocvar_hud_panel_itemstime_hidespawned == 2) + if (!(ItemsTime_time[id] > time)) + continue; + + DrawItemsTimeItem(pos + eX * column * (itemstime_size.x + offset.x) + eY * row * (itemstime_size.y + offset.y), itemstime_size, ar, icon, item_time, item_available, f); + ++row; + if (row >= rows) + { + row = 0; + column = column + 1; + } + if(id == Items_MAX) // can happen only in the last fake iteration + break; + )); + // add another fake iteration for superweapons time + if(id < Items_MAX && ItemsTime_time[Items_MAX] != -1) + { + id = Items_MAX; + icon = "superweapons"; + goto iteration; + } +} + +#endif diff --git a/qcsrc/common/mutators/mutator/itemstime/itemstime.qh b/qcsrc/common/mutators/mutator/itemstime/itemstime.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/mutators/mutator/itemstime/itemstime.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mutators/mutator/melee_only/_mod.inc b/qcsrc/common/mutators/mutator/melee_only/_mod.inc index db31be3fdd..da02f08086 100644 --- a/qcsrc/common/mutators/mutator/melee_only/_mod.inc +++ b/qcsrc/common/mutators/mutator/melee_only/_mod.inc @@ -1,2 +1,4 @@ // generated file; do not modify -#include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/mutators/mutator/melee_only/_mod.qh b/qcsrc/common/mutators/mutator/melee_only/_mod.qh index 2228d64f18..297bb2b962 100644 --- a/qcsrc/common/mutators/mutator/melee_only/_mod.qh +++ b/qcsrc/common/mutators/mutator/melee_only/_mod.qh @@ -1,2 +1,4 @@ // generated file; do not modify -#include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/mutators/mutator/melee_only/melee_only.qc b/qcsrc/common/mutators/mutator/melee_only/melee_only.qc deleted file mode 100644 index ecd5fc7c8e..0000000000 --- a/qcsrc/common/mutators/mutator/melee_only/melee_only.qc +++ /dev/null @@ -1,38 +0,0 @@ -#ifdef IMPLEMENTATION -REGISTER_MUTATOR(melee_only, cvar("g_melee_only") && !cvar("g_instagib") && !g_nexball); - -MUTATOR_HOOKFUNCTION(melee_only, SetStartItems) -{ - start_ammo_shells = warmup_start_ammo_shells = 0; - start_weapons = warmup_start_weapons = WEPSET(SHOTGUN); -} - -MUTATOR_HOOKFUNCTION(melee_only, ForbidThrowCurrentWeapon) -{ - return true; -} - -MUTATOR_HOOKFUNCTION(melee_only, FilterItem) -{ - entity item = M_ARGV(0, entity); - - switch (item.items) - { - case ITEM_HealthSmall.m_itemid: - case ITEM_ArmorSmall.m_itemid: - return false; - } - - return true; -} - -MUTATOR_HOOKFUNCTION(melee_only, BuildMutatorsString) -{ - M_ARGV(0, string) = strcat(M_ARGV(0, string), ":MeleeOnly"); -} - -MUTATOR_HOOKFUNCTION(melee_only, BuildMutatorsPrettyString) -{ - M_ARGV(0, string) = strcat(M_ARGV(0, string), ", Melee Only Arena"); -} -#endif diff --git a/qcsrc/common/mutators/mutator/melee_only/module.inc b/qcsrc/common/mutators/mutator/melee_only/module.inc deleted file mode 100644 index c711556ccf..0000000000 --- a/qcsrc/common/mutators/mutator/melee_only/module.inc +++ /dev/null @@ -1,3 +0,0 @@ -#ifdef SVQC -#include "melee_only.qc" -#endif diff --git a/qcsrc/common/mutators/mutator/melee_only/sv_melee_only.qc b/qcsrc/common/mutators/mutator/melee_only/sv_melee_only.qc new file mode 100644 index 0000000000..e07034bafa --- /dev/null +++ b/qcsrc/common/mutators/mutator/melee_only/sv_melee_only.qc @@ -0,0 +1,38 @@ +#include "sv_melee_only.qh" + +REGISTER_MUTATOR(melee_only, cvar("g_melee_only") && !cvar("g_instagib") && !g_nexball); + +MUTATOR_HOOKFUNCTION(melee_only, SetStartItems) +{ + start_ammo_shells = warmup_start_ammo_shells = 0; + start_weapons = warmup_start_weapons = WEPSET(SHOTGUN); +} + +MUTATOR_HOOKFUNCTION(melee_only, ForbidThrowCurrentWeapon) +{ + return true; +} + +MUTATOR_HOOKFUNCTION(melee_only, FilterItem) +{ + entity item = M_ARGV(0, entity); + + switch (item.items) + { + case ITEM_HealthSmall.m_itemid: + case ITEM_ArmorSmall.m_itemid: + return false; + } + + return true; +} + +MUTATOR_HOOKFUNCTION(melee_only, BuildMutatorsString) +{ + M_ARGV(0, string) = strcat(M_ARGV(0, string), ":MeleeOnly"); +} + +MUTATOR_HOOKFUNCTION(melee_only, BuildMutatorsPrettyString) +{ + M_ARGV(0, string) = strcat(M_ARGV(0, string), ", Melee Only Arena"); +} diff --git a/qcsrc/common/mutators/mutator/melee_only/sv_melee_only.qh b/qcsrc/common/mutators/mutator/melee_only/sv_melee_only.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/mutators/mutator/melee_only/sv_melee_only.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mutators/mutator/midair/_mod.inc b/qcsrc/common/mutators/mutator/midair/_mod.inc index 8fcc96ea21..b144ca4b6a 100644 --- a/qcsrc/common/mutators/mutator/midair/_mod.inc +++ b/qcsrc/common/mutators/mutator/midair/_mod.inc @@ -1,2 +1,4 @@ // generated file; do not modify -#include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/mutators/mutator/midair/_mod.qh b/qcsrc/common/mutators/mutator/midair/_mod.qh index 48272b8709..f96da13867 100644 --- a/qcsrc/common/mutators/mutator/midair/_mod.qh +++ b/qcsrc/common/mutators/mutator/midair/_mod.qh @@ -1,2 +1,4 @@ // generated file; do not modify -#include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/mutators/mutator/midair/midair.qc b/qcsrc/common/mutators/mutator/midair/midair.qc deleted file mode 100644 index 1fa69a60e1..0000000000 --- a/qcsrc/common/mutators/mutator/midair/midair.qc +++ /dev/null @@ -1,49 +0,0 @@ -#ifdef IMPLEMENTATION - -float autocvar_g_midair_shieldtime; - -REGISTER_MUTATOR(midair, cvar("g_midair")); - -.float midair_shieldtime; - -MUTATOR_HOOKFUNCTION(midair, PlayerDamage_Calculate) -{ - entity frag_attacker = M_ARGV(1, entity); - entity frag_target = M_ARGV(2, entity); - - if(IS_PLAYER(frag_attacker)) - if(IS_PLAYER(frag_target)) - if(time < frag_target.midair_shieldtime) - M_ARGV(4, float) = 0; -} - -MUTATOR_HOOKFUNCTION(midair, PlayerPowerups) -{ - entity player = M_ARGV(0, entity); - - if(time >= game_starttime) - if(IS_ONGROUND(player)) - { - player.effects |= (EF_ADDITIVE | EF_FULLBRIGHT); - player.midair_shieldtime = max(player.midair_shieldtime, time + autocvar_g_midair_shieldtime); - } -} - -MUTATOR_HOOKFUNCTION(midair, PlayerSpawn) -{ - entity player = M_ARGV(0, entity); - - if(IS_BOT_CLIENT(player)) - player.bot_moveskill = 0; // disable bunnyhopping -} - -MUTATOR_HOOKFUNCTION(midair, BuildMutatorsString) -{ - M_ARGV(0, string) = strcat(M_ARGV(0, string), ":midair"); -} - -MUTATOR_HOOKFUNCTION(midair, BuildMutatorsPrettyString) -{ - M_ARGV(0, string) = strcat(M_ARGV(0, string), ", Midair"); -} -#endif diff --git a/qcsrc/common/mutators/mutator/midair/module.inc b/qcsrc/common/mutators/mutator/midair/module.inc deleted file mode 100644 index 10b1789159..0000000000 --- a/qcsrc/common/mutators/mutator/midair/module.inc +++ /dev/null @@ -1,3 +0,0 @@ -#ifdef SVQC -#include "midair.qc" -#endif diff --git a/qcsrc/common/mutators/mutator/midair/sv_midair.qc b/qcsrc/common/mutators/mutator/midair/sv_midair.qc new file mode 100644 index 0000000000..40747ff056 --- /dev/null +++ b/qcsrc/common/mutators/mutator/midair/sv_midair.qc @@ -0,0 +1,48 @@ +#include "sv_midair.qh" + +float autocvar_g_midair_shieldtime; + +REGISTER_MUTATOR(midair, cvar("g_midair")); + +.float midair_shieldtime; + +MUTATOR_HOOKFUNCTION(midair, PlayerDamage_Calculate) +{ + entity frag_attacker = M_ARGV(1, entity); + entity frag_target = M_ARGV(2, entity); + + if(IS_PLAYER(frag_attacker)) + if(IS_PLAYER(frag_target)) + if(time < frag_target.midair_shieldtime) + M_ARGV(4, float) = 0; +} + +MUTATOR_HOOKFUNCTION(midair, PlayerPowerups) +{ + entity player = M_ARGV(0, entity); + + if(time >= game_starttime) + if(IS_ONGROUND(player)) + { + player.effects |= (EF_ADDITIVE | EF_FULLBRIGHT); + player.midair_shieldtime = max(player.midair_shieldtime, time + autocvar_g_midair_shieldtime); + } +} + +MUTATOR_HOOKFUNCTION(midair, PlayerSpawn) +{ + entity player = M_ARGV(0, entity); + + if(IS_BOT_CLIENT(player)) + player.bot_moveskill = 0; // disable bunnyhopping +} + +MUTATOR_HOOKFUNCTION(midair, BuildMutatorsString) +{ + M_ARGV(0, string) = strcat(M_ARGV(0, string), ":midair"); +} + +MUTATOR_HOOKFUNCTION(midair, BuildMutatorsPrettyString) +{ + M_ARGV(0, string) = strcat(M_ARGV(0, string), ", Midair"); +} diff --git a/qcsrc/common/mutators/mutator/midair/sv_midair.qh b/qcsrc/common/mutators/mutator/midair/sv_midair.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/mutators/mutator/midair/sv_midair.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mutators/mutator/multijump/module.inc b/qcsrc/common/mutators/mutator/multijump/module.inc deleted file mode 100644 index 3103320990..0000000000 --- a/qcsrc/common/mutators/mutator/multijump/module.inc +++ /dev/null @@ -1,3 +0,0 @@ -#ifndef MENUQC -#include "multijump.qc" -#endif diff --git a/qcsrc/common/mutators/mutator/multijump/multijump.qc b/qcsrc/common/mutators/mutator/multijump/multijump.qc index d6dc30cee4..aeee45e121 100644 --- a/qcsrc/common/mutators/mutator/multijump/multijump.qc +++ b/qcsrc/common/mutators/mutator/multijump/multijump.qc @@ -1,4 +1,7 @@ -#ifdef IMPLEMENTATION +#include "multijump.qh" + +#ifndef MENUQC + #ifdef SVQC #include #endif @@ -126,4 +129,5 @@ MUTATOR_HOOKFUNCTION(multijump, BuildMutatorsPrettyString) } #endif + #endif diff --git a/qcsrc/common/mutators/mutator/multijump/multijump.qh b/qcsrc/common/mutators/mutator/multijump/multijump.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/mutators/mutator/multijump/multijump.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mutators/mutator/nades/module.inc b/qcsrc/common/mutators/mutator/nades/module.inc deleted file mode 100644 index e03900d6a5..0000000000 --- a/qcsrc/common/mutators/mutator/nades/module.inc +++ /dev/null @@ -1,4 +0,0 @@ -#include "nades.qc" -#ifndef MENUQC -#include "net.qc" -#endif diff --git a/qcsrc/common/mutators/mutator/nades/nades.qc b/qcsrc/common/mutators/mutator/nades/nades.qc index d785124a76..b8d1007864 100644 --- a/qcsrc/common/mutators/mutator/nades/nades.qc +++ b/qcsrc/common/mutators/mutator/nades/nades.qc @@ -1,7 +1,5 @@ #include "nades.qh" -#ifdef IMPLEMENTATION - #ifdef SVQC bool autocvar_g_nades_nade_small; float autocvar_g_nades_spread = 0.04; @@ -147,7 +145,7 @@ void DrawAmmoNades(vector myPos, vector mySize, bool draw_expanding, float expan #ifdef SVQC -#include +#include #include #include #include @@ -1486,4 +1484,3 @@ MUTATOR_HOOKFUNCTION(nades, BuildGameplayTipsString) } #endif -#endif diff --git a/qcsrc/common/mutators/mutator/nades/nades.qh b/qcsrc/common/mutators/mutator/nades/nades.qh index d4f6f7e365..0402295e25 100644 --- a/qcsrc/common/mutators/mutator/nades/nades.qh +++ b/qcsrc/common/mutators/mutator/nades/nades.qh @@ -1,5 +1,4 @@ -#ifndef NADES_ALL_H -#define NADES_ALL_H +#pragma once #include @@ -102,5 +101,3 @@ void nades_GiveBonus(entity player, float score); MUTATOR_HOOKABLE(Nade_Damage, EV_Nade_Damage); #endif - -#endif diff --git a/qcsrc/common/mutators/mutator/nades/net.qc b/qcsrc/common/mutators/mutator/nades/net.qc index e2659c7af8..ea78745808 100644 --- a/qcsrc/common/mutators/mutator/nades/net.qc +++ b/qcsrc/common/mutators/mutator/nades/net.qc @@ -1,6 +1,8 @@ -#include "nades.qh" +#include "net.qh" + +#ifndef MENUQC -#ifdef IMPLEMENTATION +#include "nades.qh" #ifdef CSQC .float ltime; diff --git a/qcsrc/common/mutators/mutator/nades/net.qh b/qcsrc/common/mutators/mutator/nades/net.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/mutators/mutator/nades/net.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mutators/mutator/new_toys/_mod.inc b/qcsrc/common/mutators/mutator/new_toys/_mod.inc index 90e9811ea6..67ee4f5345 100644 --- a/qcsrc/common/mutators/mutator/new_toys/_mod.inc +++ b/qcsrc/common/mutators/mutator/new_toys/_mod.inc @@ -1,2 +1,4 @@ // generated file; do not modify -#include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/mutators/mutator/new_toys/_mod.qh b/qcsrc/common/mutators/mutator/new_toys/_mod.qh index ec3b8105fe..97f88a5192 100644 --- a/qcsrc/common/mutators/mutator/new_toys/_mod.qh +++ b/qcsrc/common/mutators/mutator/new_toys/_mod.qh @@ -1,2 +1,4 @@ // generated file; do not modify -#include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/mutators/mutator/new_toys/module.inc b/qcsrc/common/mutators/mutator/new_toys/module.inc deleted file mode 100644 index 1217177c58..0000000000 --- a/qcsrc/common/mutators/mutator/new_toys/module.inc +++ /dev/null @@ -1,3 +0,0 @@ -#ifdef SVQC -#include "new_toys.qc" -#endif diff --git a/qcsrc/common/mutators/mutator/new_toys/new_toys.qc b/qcsrc/common/mutators/mutator/new_toys/new_toys.qc deleted file mode 100644 index 27d1795062..0000000000 --- a/qcsrc/common/mutators/mutator/new_toys/new_toys.qc +++ /dev/null @@ -1,229 +0,0 @@ -#ifdef IMPLEMENTATION -/* - -CORE laser vortex lg rl cry gl elec hagar fireb hook - vaporizer porto - tuba - -NEW rifle hlac minel seeker -IDEAS OPEN flak OPEN FUN FUN FUN FUN - - - -How this mutator works: - ======================= - -When a gun tries to spawn, this mutator is called. It will provide alternate -weaponreplace lists. - -Entity: - -{ -"classname" "weapon_vortex" -"new_toys" "rifle" -} --> This will spawn as Rifle in this mutator ONLY, and as Vortex otherwise. - -{ -"classname" "weapon_vortext" -"new_toys" "vortex rifle" -} --> This will spawn as either Vortex or Rifle in this mutator ONLY, and as Vortex otherwise. - -{ -"classname" "weapon_vortex" -"new_toys" "vortex" -} --> This is always a Vortex. - -If the map specifies no "new_toys" argument - -There will be two default replacements selectable: "replace all" and "replace random". -In "replace all" mode, e.g. Vortex will have the default replacement "rifle". -In "replace random" mode, Vortex will have the default replacement "vortex rifle". - -This mutator's replacements run BEFORE regular weaponreplace! - -The New Toys guns do NOT get a spawn function, so they can only ever be spawned -when this mutator is active. - -Likewise, warmup, give all, give ALL and impulse 99 will not give them unless -this mutator is active. - -Outside this mutator, they still can be spawned by: -- setting their start weapon cvar to 1 -- give weaponname -- weaponreplace -- weaponarena (but all and most weapons arena again won't include them) - -This mutator performs the default replacements on the DEFAULTS of the -start weapon selection. - -These weapons appear in the menu's priority list, BUT get a suffix -"(Mutator weapon)". - -Picking up a "new toys" weapon will not play standard weapon pickup sound, but -roflsound "New toys, new toys!" sound. - -*/ - -bool nt_IsNewToy(int w); - -REGISTER_MUTATOR(nt, cvar("g_new_toys") && !cvar("g_instagib") && !cvar("g_overkill")) -{ - MUTATOR_ONADD - { - if(time > 1) // game loads at time 1 - error("This cannot be added at runtime\n"); - - // mark the guns as ok to use by e.g. impulse 99 - FOREACH(Weapons, it != WEP_Null, LAMBDA( - if(nt_IsNewToy(it.m_id)) - it.spawnflags &= ~WEP_FLAG_MUTATORBLOCKED; - )); - } - - MUTATOR_ONROLLBACK_OR_REMOVE - { - FOREACH(Weapons, it != WEP_Null, LAMBDA( - if(nt_IsNewToy(it.m_id)) - it.spawnflags |= WEP_FLAG_MUTATORBLOCKED; - )); - } - - MUTATOR_ONREMOVE - { - LOG_INFO("This cannot be removed at runtime\n"); - return -1; - } - - return 0; -} - -.string new_toys; - -float autocvar_g_new_toys_autoreplace; -bool autocvar_g_new_toys_use_pickupsound = true; -const float NT_AUTOREPLACE_NEVER = 0; -const float NT_AUTOREPLACE_ALWAYS = 1; -const float NT_AUTOREPLACE_RANDOM = 2; - -MUTATOR_HOOKFUNCTION(nt, SetModname) -{ - M_ARGV(0, string) = "NewToys"; -} - -bool nt_IsNewToy(int w) -{ - switch(w) - { - case WEP_SEEKER.m_id: - case WEP_MINE_LAYER.m_id: - case WEP_HLAC.m_id: - case WEP_RIFLE.m_id: - case WEP_SHOCKWAVE.m_id: - return true; - default: - return false; - } -} - -string nt_GetFullReplacement(string w) -{ - switch(w) - { - case "hagar": return "seeker"; - case "devastator": return "minelayer"; - case "machinegun": return "hlac"; - case "vortex": return "rifle"; - //case "shotgun": return "shockwave"; - default: return string_null; - } -} - -string nt_GetReplacement(string w, float m) -{ - if(m == NT_AUTOREPLACE_NEVER) - return w; - string s = nt_GetFullReplacement(w); - if (!s) - return w; - if(m == NT_AUTOREPLACE_RANDOM) - s = strcat(w, " ", s); - return s; -} - -MUTATOR_HOOKFUNCTION(nt, SetStartItems) -{ - // rearrange start_weapon_default - // apply those bits that are set by start_weapon_defaultmask - // same for warmup - - float j, n; - - WepSet newdefault; - WepSet warmup_newdefault; - - newdefault = '0 0 0'; - warmup_newdefault = '0 0 0'; - - WepSet seti = '0 0 0'; - - FOREACH(Weapons, it != WEP_Null, LAMBDA( - seti = it.m_wepset; - n = tokenize_console(nt_GetReplacement(it.netname, autocvar_g_new_toys_autoreplace)); - - for(j = 0; j < n; ++j) - FOREACH(Weapons, it != WEP_Null, LAMBDA( - if(it.netname == argv(j)) - { - WepSet setk = it.m_wepset; - if(start_weapons & seti) newdefault |= setk; - if(warmup_start_weapons & seti) warmup_newdefault |= setk; - } - )); - )); - - newdefault &= start_weapons_defaultmask; - start_weapons &= ~start_weapons_defaultmask; - start_weapons |= newdefault; - - warmup_newdefault &= warmup_start_weapons_defaultmask; - warmup_start_weapons &= ~warmup_start_weapons_defaultmask; - warmup_start_weapons |= warmup_newdefault; -} - -MUTATOR_HOOKFUNCTION(nt, SetWeaponreplace) -{ - entity wep = M_ARGV(0, entity); - entity wepinfo = M_ARGV(1, entity); - string ret_string = M_ARGV(2, string); - - // otherwise, we do replace - if(wep.new_toys) - { - // map defined replacement: - ret_string = wep.new_toys; - } - else - { - // auto replacement: - ret_string = nt_GetReplacement(wepinfo.netname, autocvar_g_new_toys_autoreplace); - } - - // apply regular weaponreplace - ret_string = W_Apply_Weaponreplace(ret_string); - - M_ARGV(2, string) = ret_string; -} - -MUTATOR_HOOKFUNCTION(nt, FilterItem) -{ - entity item = M_ARGV(0, entity); - - if(nt_IsNewToy(item.weapon) && autocvar_g_new_toys_use_pickupsound) { - item.item_pickupsound = string_null; - item.item_pickupsound_ent = SND_WEAPONPICKUP_NEW_TOYS; - } -} -#endif diff --git a/qcsrc/common/mutators/mutator/new_toys/sv_new_toys.qc b/qcsrc/common/mutators/mutator/new_toys/sv_new_toys.qc new file mode 100644 index 0000000000..6c0647b1e6 --- /dev/null +++ b/qcsrc/common/mutators/mutator/new_toys/sv_new_toys.qc @@ -0,0 +1,229 @@ +#include "sv_new_toys.qh" + +/* + +CORE laser vortex lg rl cry gl elec hagar fireb hook + vaporizer porto + tuba + +NEW rifle hlac minel seeker +IDEAS OPEN flak OPEN FUN FUN FUN FUN + + + +How this mutator works: + ======================= + +When a gun tries to spawn, this mutator is called. It will provide alternate +weaponreplace lists. + +Entity: + +{ +"classname" "weapon_vortex" +"new_toys" "rifle" +} +-> This will spawn as Rifle in this mutator ONLY, and as Vortex otherwise. + +{ +"classname" "weapon_vortext" +"new_toys" "vortex rifle" +} +-> This will spawn as either Vortex or Rifle in this mutator ONLY, and as Vortex otherwise. + +{ +"classname" "weapon_vortex" +"new_toys" "vortex" +} +-> This is always a Vortex. + +If the map specifies no "new_toys" argument + +There will be two default replacements selectable: "replace all" and "replace random". +In "replace all" mode, e.g. Vortex will have the default replacement "rifle". +In "replace random" mode, Vortex will have the default replacement "vortex rifle". + +This mutator's replacements run BEFORE regular weaponreplace! + +The New Toys guns do NOT get a spawn function, so they can only ever be spawned +when this mutator is active. + +Likewise, warmup, give all, give ALL and impulse 99 will not give them unless +this mutator is active. + +Outside this mutator, they still can be spawned by: +- setting their start weapon cvar to 1 +- give weaponname +- weaponreplace +- weaponarena (but all and most weapons arena again won't include them) + +This mutator performs the default replacements on the DEFAULTS of the +start weapon selection. + +These weapons appear in the menu's priority list, BUT get a suffix +"(Mutator weapon)". + +Picking up a "new toys" weapon will not play standard weapon pickup sound, but +roflsound "New toys, new toys!" sound. + +*/ + +bool nt_IsNewToy(int w); + +REGISTER_MUTATOR(nt, cvar("g_new_toys") && !cvar("g_instagib") && !cvar("g_overkill")) +{ + MUTATOR_ONADD + { + if(time > 1) // game loads at time 1 + error("This cannot be added at runtime\n"); + + // mark the guns as ok to use by e.g. impulse 99 + FOREACH(Weapons, it != WEP_Null, LAMBDA( + if(nt_IsNewToy(it.m_id)) + it.spawnflags &= ~WEP_FLAG_MUTATORBLOCKED; + )); + } + + MUTATOR_ONROLLBACK_OR_REMOVE + { + FOREACH(Weapons, it != WEP_Null, LAMBDA( + if(nt_IsNewToy(it.m_id)) + it.spawnflags |= WEP_FLAG_MUTATORBLOCKED; + )); + } + + MUTATOR_ONREMOVE + { + LOG_INFO("This cannot be removed at runtime\n"); + return -1; + } + + return 0; +} + +.string new_toys; + +float autocvar_g_new_toys_autoreplace; +bool autocvar_g_new_toys_use_pickupsound = true; +const float NT_AUTOREPLACE_NEVER = 0; +const float NT_AUTOREPLACE_ALWAYS = 1; +const float NT_AUTOREPLACE_RANDOM = 2; + +MUTATOR_HOOKFUNCTION(nt, SetModname) +{ + M_ARGV(0, string) = "NewToys"; +} + +bool nt_IsNewToy(int w) +{ + switch(w) + { + case WEP_SEEKER.m_id: + case WEP_MINE_LAYER.m_id: + case WEP_HLAC.m_id: + case WEP_RIFLE.m_id: + case WEP_SHOCKWAVE.m_id: + return true; + default: + return false; + } +} + +string nt_GetFullReplacement(string w) +{ + switch(w) + { + case "hagar": return "seeker"; + case "devastator": return "minelayer"; + case "machinegun": return "hlac"; + case "vortex": return "rifle"; + //case "shotgun": return "shockwave"; + default: return string_null; + } +} + +string nt_GetReplacement(string w, float m) +{ + if(m == NT_AUTOREPLACE_NEVER) + return w; + string s = nt_GetFullReplacement(w); + if (!s) + return w; + if(m == NT_AUTOREPLACE_RANDOM) + s = strcat(w, " ", s); + return s; +} + +MUTATOR_HOOKFUNCTION(nt, SetStartItems) +{ + // rearrange start_weapon_default + // apply those bits that are set by start_weapon_defaultmask + // same for warmup + + float j, n; + + WepSet newdefault; + WepSet warmup_newdefault; + + newdefault = '0 0 0'; + warmup_newdefault = '0 0 0'; + + WepSet seti = '0 0 0'; + + FOREACH(Weapons, it != WEP_Null, LAMBDA( + seti = it.m_wepset; + n = tokenize_console(nt_GetReplacement(it.netname, autocvar_g_new_toys_autoreplace)); + + for(j = 0; j < n; ++j) + FOREACH(Weapons, it != WEP_Null, LAMBDA( + if(it.netname == argv(j)) + { + WepSet setk = it.m_wepset; + if(start_weapons & seti) newdefault |= setk; + if(warmup_start_weapons & seti) warmup_newdefault |= setk; + } + )); + )); + + newdefault &= start_weapons_defaultmask; + start_weapons &= ~start_weapons_defaultmask; + start_weapons |= newdefault; + + warmup_newdefault &= warmup_start_weapons_defaultmask; + warmup_start_weapons &= ~warmup_start_weapons_defaultmask; + warmup_start_weapons |= warmup_newdefault; +} + +MUTATOR_HOOKFUNCTION(nt, SetWeaponreplace) +{ + entity wep = M_ARGV(0, entity); + entity wepinfo = M_ARGV(1, entity); + string ret_string = M_ARGV(2, string); + + // otherwise, we do replace + if(wep.new_toys) + { + // map defined replacement: + ret_string = wep.new_toys; + } + else + { + // auto replacement: + ret_string = nt_GetReplacement(wepinfo.netname, autocvar_g_new_toys_autoreplace); + } + + // apply regular weaponreplace + ret_string = W_Apply_Weaponreplace(ret_string); + + M_ARGV(2, string) = ret_string; +} + +MUTATOR_HOOKFUNCTION(nt, FilterItem) +{ + entity item = M_ARGV(0, entity); + + if(nt_IsNewToy(item.weapon) && autocvar_g_new_toys_use_pickupsound) { + item.item_pickupsound = string_null; + item.item_pickupsound_ent = SND_WEAPONPICKUP_NEW_TOYS; + } +} diff --git a/qcsrc/common/mutators/mutator/new_toys/sv_new_toys.qh b/qcsrc/common/mutators/mutator/new_toys/sv_new_toys.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/mutators/mutator/new_toys/sv_new_toys.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mutators/mutator/nix/_mod.inc b/qcsrc/common/mutators/mutator/nix/_mod.inc index a669175da2..3c3141d6fb 100644 --- a/qcsrc/common/mutators/mutator/nix/_mod.inc +++ b/qcsrc/common/mutators/mutator/nix/_mod.inc @@ -1,2 +1,4 @@ // generated file; do not modify -#include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/mutators/mutator/nix/_mod.qh b/qcsrc/common/mutators/mutator/nix/_mod.qh index 6c012fe65c..affbae20a9 100644 --- a/qcsrc/common/mutators/mutator/nix/_mod.qh +++ b/qcsrc/common/mutators/mutator/nix/_mod.qh @@ -1,2 +1,4 @@ // generated file; do not modify -#include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/mutators/mutator/nix/module.inc b/qcsrc/common/mutators/mutator/nix/module.inc deleted file mode 100644 index fb4f9ec2bc..0000000000 --- a/qcsrc/common/mutators/mutator/nix/module.inc +++ /dev/null @@ -1,3 +0,0 @@ -#ifdef SVQC -#include "nix.qc" -#endif diff --git a/qcsrc/common/mutators/mutator/nix/nix.qc b/qcsrc/common/mutators/mutator/nix/nix.qc deleted file mode 100644 index f52d75a9e9..0000000000 --- a/qcsrc/common/mutators/mutator/nix/nix.qc +++ /dev/null @@ -1,288 +0,0 @@ -#ifdef IMPLEMENTATION -int autocvar_g_balance_nix_ammo_cells; -int autocvar_g_balance_nix_ammo_plasma; -int autocvar_g_balance_nix_ammo_fuel; -int autocvar_g_balance_nix_ammo_nails; -int autocvar_g_balance_nix_ammo_rockets; -int autocvar_g_balance_nix_ammo_shells; -int autocvar_g_balance_nix_ammoincr_cells; -int autocvar_g_balance_nix_ammoincr_plasma; -int autocvar_g_balance_nix_ammoincr_fuel; -int autocvar_g_balance_nix_ammoincr_nails; -int autocvar_g_balance_nix_ammoincr_rockets; -int autocvar_g_balance_nix_ammoincr_shells; -float autocvar_g_balance_nix_incrtime; -float autocvar_g_balance_nix_roundtime; -bool autocvar_g_nix_with_healtharmor; -bool autocvar_g_nix_with_blaster; -bool autocvar_g_nix_with_powerups; -int autocvar_g_pickup_cells_max; -int autocvar_g_pickup_plasma_max; -int autocvar_g_pickup_fuel_max; -int autocvar_g_pickup_nails_max; -int autocvar_g_pickup_rockets_max; -int autocvar_g_pickup_shells_max; - -float g_nix_with_blaster; -// WEAPONTODO -int nix_weapon; -float nix_nextchange; -float nix_nextweapon; -.float nix_lastchange_id; -.float nix_lastinfotime; -.float nix_nextincr; - -bool NIX_CanChooseWeapon(int wpn); - -REGISTER_MUTATOR(nix, cvar("g_nix") && !cvar("g_instagib") && !cvar("g_overkill")) -{ - MUTATOR_ONADD - { - g_nix_with_blaster = autocvar_g_nix_with_blaster; - - nix_nextchange = 0; - nix_nextweapon = 0; - - FOREACH(Weapons, it != WEP_Null && NIX_CanChooseWeapon(it.m_id), LAMBDA(it.wr_init(it))); - } - - MUTATOR_ONROLLBACK_OR_REMOVE - { - // nothing to roll back - } - - MUTATOR_ONREMOVE - { - // as the PlayerSpawn hook will no longer run, NIX is turned off by this! - FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it), { - it.ammo_cells = start_ammo_cells; - it.ammo_plasma = start_ammo_plasma; - it.ammo_shells = start_ammo_shells; - it.ammo_nails = start_ammo_nails; - it.ammo_rockets = start_ammo_rockets; - it.ammo_fuel = start_ammo_fuel; - it.weapons = start_weapons; - if(!client_hasweapon(it, PS(it).m_weapon, true, false)) - PS(it).m_switchweapon = w_getbestweapon(it); - }); - } - - return false; -} - -bool NIX_CanChooseWeapon(int wpn) -{ - entity e = Weapons_from(wpn); - if (e == WEP_Null) return false; // skip dummies - if(g_weaponarena) - { - if(!(g_weaponarena_weapons & e.m_wepset)) - return false; - } - else - { - if(wpn == WEP_BLASTER.m_id && g_nix_with_blaster) - return false; - if(e.spawnflags & WEP_FLAG_MUTATORBLOCKED) - return false; - if (!(e.spawnflags & WEP_FLAG_NORMAL)) - return false; - } - return true; -} -void NIX_ChooseNextWeapon() -{ - RandomSelection_Init(); - FOREACH(Weapons, it != WEP_Null, LAMBDA( - if(NIX_CanChooseWeapon(it.m_id)) - RandomSelection_Add(NULL, it.m_id, string_null, 1, (it.m_id != nix_weapon)); - )); - nix_nextweapon = RandomSelection_chosen_float; -} - -void NIX_GiveCurrentWeapon(entity this) -{ - float dt; - - if(!nix_nextweapon) - NIX_ChooseNextWeapon(); - - dt = ceil(nix_nextchange - time); - - if(dt <= 0) - { - nix_weapon = nix_nextweapon; - nix_nextweapon = 0; - if (!nix_nextchange) // no round played yet? - nix_nextchange = time; // start the first round now! - else - nix_nextchange = time + autocvar_g_balance_nix_roundtime; - // Weapon w = Weapons_from(nix_weapon); - // w.wr_init(w); // forget it, too slow - } - - // get weapon info - entity e = Weapons_from(nix_weapon); - - if(nix_nextchange != this.nix_lastchange_id) // this shall only be called once per round! - { - this.ammo_shells = this.ammo_nails = this.ammo_rockets = this.ammo_cells = this.ammo_plasma = this.ammo_fuel = 0; - - if(this.items & IT_UNLIMITED_WEAPON_AMMO) - { - switch(e.ammo_field) - { - case ammo_shells: this.ammo_shells = autocvar_g_pickup_shells_max; break; - case ammo_nails: this.ammo_nails = autocvar_g_pickup_nails_max; break; - case ammo_rockets: this.ammo_rockets = autocvar_g_pickup_rockets_max; break; - case ammo_cells: this.ammo_cells = autocvar_g_pickup_cells_max; break; - case ammo_plasma: this.ammo_plasma = autocvar_g_pickup_plasma_max; break; - case ammo_fuel: this.ammo_fuel = autocvar_g_pickup_fuel_max; break; - } - } - else - { - switch(e.ammo_field) - { - case ammo_shells: this.ammo_shells = autocvar_g_balance_nix_ammo_shells; break; - case ammo_nails: this.ammo_nails = autocvar_g_balance_nix_ammo_nails; break; - case ammo_rockets: this.ammo_rockets = autocvar_g_balance_nix_ammo_rockets; break; - case ammo_cells: this.ammo_cells = autocvar_g_balance_nix_ammo_cells; break; - case ammo_plasma: this.ammo_plasma = autocvar_g_balance_nix_ammo_plasma; break; - case ammo_fuel: this.ammo_fuel = autocvar_g_balance_nix_ammo_fuel; break; - } - } - - this.nix_nextincr = time + autocvar_g_balance_nix_incrtime; - if(dt >= 1 && dt <= 5) - this.nix_lastinfotime = -42; - else - Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_NIX_NEWWEAPON, nix_weapon); - - e.wr_resetplayer(e, this); - - // all weapons must be fully loaded when we spawn - if(e.spawnflags & WEP_FLAG_RELOADABLE) // prevent accessing undefined cvars - this.(weapon_load[nix_weapon]) = e.reloading_ammo; - - // vortex too - if(WEP_CVAR(vortex, charge)) - { - if(WEP_CVAR_SEC(vortex, chargepool)) - this.vortex_chargepool_ammo = 1; - this.vortex_charge = WEP_CVAR(vortex, charge_start); - } - - // set last change info - this.nix_lastchange_id = nix_nextchange; - } - if(this.nix_lastinfotime != dt) - { - this.nix_lastinfotime = dt; // initial value 0 should count as "not seen" - if(dt >= 1 && dt <= 5) - Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_NIX_COUNTDOWN, nix_nextweapon, dt); - } - - if(!(this.items & IT_UNLIMITED_WEAPON_AMMO) && time > this.nix_nextincr) - { - switch(e.ammo_field) - { - case ammo_shells: this.ammo_shells += autocvar_g_balance_nix_ammoincr_shells; break; - case ammo_nails: this.ammo_nails += autocvar_g_balance_nix_ammoincr_nails; break; - case ammo_rockets: this.ammo_rockets += autocvar_g_balance_nix_ammoincr_rockets; break; - case ammo_cells: this.ammo_cells += autocvar_g_balance_nix_ammoincr_cells; break; - case ammo_plasma: this.ammo_plasma += autocvar_g_balance_nix_ammoincr_plasma; break; - case ammo_fuel: this.ammo_fuel += autocvar_g_balance_nix_ammoincr_fuel; break; - } - - this.nix_nextincr = time + autocvar_g_balance_nix_incrtime; - } - - this.weapons = '0 0 0'; - if(g_nix_with_blaster) - this.weapons |= WEPSET(BLASTER); - this.weapons |= e.m_wepset; - - Weapon w = Weapons_from(nix_weapon); - if(PS(this).m_switchweapon != w) - if(!client_hasweapon(this, PS(this).m_switchweapon, true, false)) - { - if(client_hasweapon(this, w, true, false)) - W_SwitchWeapon(this, w); - } -} - -MUTATOR_HOOKFUNCTION(nix, ForbidThrowCurrentWeapon) -{ - return true; // no throwing in NIX -} - -MUTATOR_HOOKFUNCTION(nix, BuildMutatorsString) -{ - M_ARGV(0, string) = strcat(M_ARGV(0, string), ":NIX"); -} - -MUTATOR_HOOKFUNCTION(nix, BuildMutatorsPrettyString) -{ - M_ARGV(0, string) = strcat(M_ARGV(0, string), ", NIX"); -} - -MUTATOR_HOOKFUNCTION(nix, FilterItem) -{ - entity item = M_ARGV(0, entity); - - switch (item.items) - { - case ITEM_HealthSmall.m_itemid: - case ITEM_HealthMedium.m_itemid: - case ITEM_HealthLarge.m_itemid: - case ITEM_HealthMega.m_itemid: - case ITEM_ArmorSmall.m_itemid: - case ITEM_ArmorMedium.m_itemid: - case ITEM_ArmorLarge.m_itemid: - case ITEM_ArmorMega.m_itemid: - if (autocvar_g_nix_with_healtharmor) - return false; - break; - case ITEM_Strength.m_itemid: - case ITEM_Shield.m_itemid: - if (autocvar_g_nix_with_powerups) - return false; - break; - } - - return true; // delete all other items -} - -MUTATOR_HOOKFUNCTION(nix, OnEntityPreSpawn) -{ - entity ent = M_ARGV(0, entity); - - if(ent.classname == "target_items") // items triggers cannot work in nix (as they change weapons/ammo) - return true; -} - -MUTATOR_HOOKFUNCTION(nix, PlayerPreThink) -{ - entity player = M_ARGV(0, entity); - - if(!intermission_running) - if(!IS_DEAD(player)) - if(IS_PLAYER(player)) - NIX_GiveCurrentWeapon(player); -} - -MUTATOR_HOOKFUNCTION(nix, PlayerSpawn) -{ - entity player = M_ARGV(0, entity); - - player.nix_lastchange_id = -1; - NIX_GiveCurrentWeapon(player); // overrides the weapons you got when spawning - player.items |= IT_UNLIMITED_SUPERWEAPONS; -} - -MUTATOR_HOOKFUNCTION(nix, SetModname, CBC_ORDER_LAST) -{ - M_ARGV(0, string) = "NIX"; -} -#endif diff --git a/qcsrc/common/mutators/mutator/nix/sv_nix.qc b/qcsrc/common/mutators/mutator/nix/sv_nix.qc new file mode 100644 index 0000000000..97ed4361ba --- /dev/null +++ b/qcsrc/common/mutators/mutator/nix/sv_nix.qc @@ -0,0 +1,288 @@ +#include "sv_nix.qh" + +int autocvar_g_balance_nix_ammo_cells; +int autocvar_g_balance_nix_ammo_plasma; +int autocvar_g_balance_nix_ammo_fuel; +int autocvar_g_balance_nix_ammo_nails; +int autocvar_g_balance_nix_ammo_rockets; +int autocvar_g_balance_nix_ammo_shells; +int autocvar_g_balance_nix_ammoincr_cells; +int autocvar_g_balance_nix_ammoincr_plasma; +int autocvar_g_balance_nix_ammoincr_fuel; +int autocvar_g_balance_nix_ammoincr_nails; +int autocvar_g_balance_nix_ammoincr_rockets; +int autocvar_g_balance_nix_ammoincr_shells; +float autocvar_g_balance_nix_incrtime; +float autocvar_g_balance_nix_roundtime; +bool autocvar_g_nix_with_healtharmor; +bool autocvar_g_nix_with_blaster; +bool autocvar_g_nix_with_powerups; +int autocvar_g_pickup_cells_max; +int autocvar_g_pickup_plasma_max; +int autocvar_g_pickup_fuel_max; +int autocvar_g_pickup_nails_max; +int autocvar_g_pickup_rockets_max; +int autocvar_g_pickup_shells_max; + +float g_nix_with_blaster; +// WEAPONTODO +int nix_weapon; +float nix_nextchange; +float nix_nextweapon; +.float nix_lastchange_id; +.float nix_lastinfotime; +.float nix_nextincr; + +bool NIX_CanChooseWeapon(int wpn); + +REGISTER_MUTATOR(nix, cvar("g_nix") && !cvar("g_instagib") && !cvar("g_overkill")) +{ + MUTATOR_ONADD + { + g_nix_with_blaster = autocvar_g_nix_with_blaster; + + nix_nextchange = 0; + nix_nextweapon = 0; + + FOREACH(Weapons, it != WEP_Null && NIX_CanChooseWeapon(it.m_id), LAMBDA(it.wr_init(it))); + } + + MUTATOR_ONROLLBACK_OR_REMOVE + { + // nothing to roll back + } + + MUTATOR_ONREMOVE + { + // as the PlayerSpawn hook will no longer run, NIX is turned off by this! + FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it), { + it.ammo_cells = start_ammo_cells; + it.ammo_plasma = start_ammo_plasma; + it.ammo_shells = start_ammo_shells; + it.ammo_nails = start_ammo_nails; + it.ammo_rockets = start_ammo_rockets; + it.ammo_fuel = start_ammo_fuel; + it.weapons = start_weapons; + if(!client_hasweapon(it, PS(it).m_weapon, true, false)) + PS(it).m_switchweapon = w_getbestweapon(it); + }); + } + + return false; +} + +bool NIX_CanChooseWeapon(int wpn) +{ + entity e = Weapons_from(wpn); + if (e == WEP_Null) return false; // skip dummies + if(g_weaponarena) + { + if(!(g_weaponarena_weapons & e.m_wepset)) + return false; + } + else + { + if(wpn == WEP_BLASTER.m_id && g_nix_with_blaster) + return false; + if(e.spawnflags & WEP_FLAG_MUTATORBLOCKED) + return false; + if (!(e.spawnflags & WEP_FLAG_NORMAL)) + return false; + } + return true; +} +void NIX_ChooseNextWeapon() +{ + RandomSelection_Init(); + FOREACH(Weapons, it != WEP_Null, LAMBDA( + if(NIX_CanChooseWeapon(it.m_id)) + RandomSelection_Add(NULL, it.m_id, string_null, 1, (it.m_id != nix_weapon)); + )); + nix_nextweapon = RandomSelection_chosen_float; +} + +void NIX_GiveCurrentWeapon(entity this) +{ + float dt; + + if(!nix_nextweapon) + NIX_ChooseNextWeapon(); + + dt = ceil(nix_nextchange - time); + + if(dt <= 0) + { + nix_weapon = nix_nextweapon; + nix_nextweapon = 0; + if (!nix_nextchange) // no round played yet? + nix_nextchange = time; // start the first round now! + else + nix_nextchange = time + autocvar_g_balance_nix_roundtime; + // Weapon w = Weapons_from(nix_weapon); + // w.wr_init(w); // forget it, too slow + } + + // get weapon info + entity e = Weapons_from(nix_weapon); + + if(nix_nextchange != this.nix_lastchange_id) // this shall only be called once per round! + { + this.ammo_shells = this.ammo_nails = this.ammo_rockets = this.ammo_cells = this.ammo_plasma = this.ammo_fuel = 0; + + if(this.items & IT_UNLIMITED_WEAPON_AMMO) + { + switch(e.ammo_field) + { + case ammo_shells: this.ammo_shells = autocvar_g_pickup_shells_max; break; + case ammo_nails: this.ammo_nails = autocvar_g_pickup_nails_max; break; + case ammo_rockets: this.ammo_rockets = autocvar_g_pickup_rockets_max; break; + case ammo_cells: this.ammo_cells = autocvar_g_pickup_cells_max; break; + case ammo_plasma: this.ammo_plasma = autocvar_g_pickup_plasma_max; break; + case ammo_fuel: this.ammo_fuel = autocvar_g_pickup_fuel_max; break; + } + } + else + { + switch(e.ammo_field) + { + case ammo_shells: this.ammo_shells = autocvar_g_balance_nix_ammo_shells; break; + case ammo_nails: this.ammo_nails = autocvar_g_balance_nix_ammo_nails; break; + case ammo_rockets: this.ammo_rockets = autocvar_g_balance_nix_ammo_rockets; break; + case ammo_cells: this.ammo_cells = autocvar_g_balance_nix_ammo_cells; break; + case ammo_plasma: this.ammo_plasma = autocvar_g_balance_nix_ammo_plasma; break; + case ammo_fuel: this.ammo_fuel = autocvar_g_balance_nix_ammo_fuel; break; + } + } + + this.nix_nextincr = time + autocvar_g_balance_nix_incrtime; + if(dt >= 1 && dt <= 5) + this.nix_lastinfotime = -42; + else + Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_NIX_NEWWEAPON, nix_weapon); + + e.wr_resetplayer(e, this); + + // all weapons must be fully loaded when we spawn + if(e.spawnflags & WEP_FLAG_RELOADABLE) // prevent accessing undefined cvars + this.(weapon_load[nix_weapon]) = e.reloading_ammo; + + // vortex too + if(WEP_CVAR(vortex, charge)) + { + if(WEP_CVAR_SEC(vortex, chargepool)) + this.vortex_chargepool_ammo = 1; + this.vortex_charge = WEP_CVAR(vortex, charge_start); + } + + // set last change info + this.nix_lastchange_id = nix_nextchange; + } + if(this.nix_lastinfotime != dt) + { + this.nix_lastinfotime = dt; // initial value 0 should count as "not seen" + if(dt >= 1 && dt <= 5) + Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_NIX_COUNTDOWN, nix_nextweapon, dt); + } + + if(!(this.items & IT_UNLIMITED_WEAPON_AMMO) && time > this.nix_nextincr) + { + switch(e.ammo_field) + { + case ammo_shells: this.ammo_shells += autocvar_g_balance_nix_ammoincr_shells; break; + case ammo_nails: this.ammo_nails += autocvar_g_balance_nix_ammoincr_nails; break; + case ammo_rockets: this.ammo_rockets += autocvar_g_balance_nix_ammoincr_rockets; break; + case ammo_cells: this.ammo_cells += autocvar_g_balance_nix_ammoincr_cells; break; + case ammo_plasma: this.ammo_plasma += autocvar_g_balance_nix_ammoincr_plasma; break; + case ammo_fuel: this.ammo_fuel += autocvar_g_balance_nix_ammoincr_fuel; break; + } + + this.nix_nextincr = time + autocvar_g_balance_nix_incrtime; + } + + this.weapons = '0 0 0'; + if(g_nix_with_blaster) + this.weapons |= WEPSET(BLASTER); + this.weapons |= e.m_wepset; + + Weapon w = Weapons_from(nix_weapon); + if(PS(this).m_switchweapon != w) + if(!client_hasweapon(this, PS(this).m_switchweapon, true, false)) + { + if(client_hasweapon(this, w, true, false)) + W_SwitchWeapon(this, w); + } +} + +MUTATOR_HOOKFUNCTION(nix, ForbidThrowCurrentWeapon) +{ + return true; // no throwing in NIX +} + +MUTATOR_HOOKFUNCTION(nix, BuildMutatorsString) +{ + M_ARGV(0, string) = strcat(M_ARGV(0, string), ":NIX"); +} + +MUTATOR_HOOKFUNCTION(nix, BuildMutatorsPrettyString) +{ + M_ARGV(0, string) = strcat(M_ARGV(0, string), ", NIX"); +} + +MUTATOR_HOOKFUNCTION(nix, FilterItem) +{ + entity item = M_ARGV(0, entity); + + switch (item.items) + { + case ITEM_HealthSmall.m_itemid: + case ITEM_HealthMedium.m_itemid: + case ITEM_HealthLarge.m_itemid: + case ITEM_HealthMega.m_itemid: + case ITEM_ArmorSmall.m_itemid: + case ITEM_ArmorMedium.m_itemid: + case ITEM_ArmorLarge.m_itemid: + case ITEM_ArmorMega.m_itemid: + if (autocvar_g_nix_with_healtharmor) + return false; + break; + case ITEM_Strength.m_itemid: + case ITEM_Shield.m_itemid: + if (autocvar_g_nix_with_powerups) + return false; + break; + } + + return true; // delete all other items +} + +MUTATOR_HOOKFUNCTION(nix, OnEntityPreSpawn) +{ + entity ent = M_ARGV(0, entity); + + if(ent.classname == "target_items") // items triggers cannot work in nix (as they change weapons/ammo) + return true; +} + +MUTATOR_HOOKFUNCTION(nix, PlayerPreThink) +{ + entity player = M_ARGV(0, entity); + + if(!intermission_running) + if(!IS_DEAD(player)) + if(IS_PLAYER(player)) + NIX_GiveCurrentWeapon(player); +} + +MUTATOR_HOOKFUNCTION(nix, PlayerSpawn) +{ + entity player = M_ARGV(0, entity); + + player.nix_lastchange_id = -1; + NIX_GiveCurrentWeapon(player); // overrides the weapons you got when spawning + player.items |= IT_UNLIMITED_SUPERWEAPONS; +} + +MUTATOR_HOOKFUNCTION(nix, SetModname, CBC_ORDER_LAST) +{ + M_ARGV(0, string) = "NIX"; +} diff --git a/qcsrc/common/mutators/mutator/nix/sv_nix.qh b/qcsrc/common/mutators/mutator/nix/sv_nix.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/mutators/mutator/nix/sv_nix.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mutators/mutator/overkill/_mod.inc b/qcsrc/common/mutators/mutator/overkill/_mod.inc index 5b42a4dd11..0552173c1a 100644 --- a/qcsrc/common/mutators/mutator/overkill/_mod.inc +++ b/qcsrc/common/mutators/mutator/overkill/_mod.inc @@ -1,4 +1,10 @@ // generated file; do not modify #include #include +#ifdef CSQC + #include +#endif +#ifdef SVQC + #include +#endif #include diff --git a/qcsrc/common/mutators/mutator/overkill/_mod.qh b/qcsrc/common/mutators/mutator/overkill/_mod.qh index 7a46694446..13e42431b3 100644 --- a/qcsrc/common/mutators/mutator/overkill/_mod.qh +++ b/qcsrc/common/mutators/mutator/overkill/_mod.qh @@ -1,4 +1,10 @@ // generated file; do not modify #include #include +#ifdef CSQC + #include +#endif +#ifdef SVQC + #include +#endif #include diff --git a/qcsrc/common/mutators/mutator/overkill/cl_overkill.qc b/qcsrc/common/mutators/mutator/overkill/cl_overkill.qc new file mode 100644 index 0000000000..eb21953955 --- /dev/null +++ b/qcsrc/common/mutators/mutator/overkill/cl_overkill.qc @@ -0,0 +1,11 @@ +#include "cl_overkill.qh" + +REGISTER_MUTATOR(ok, false) +{ + MUTATOR_ONADD { + cvar_settemp("g_overkill", "1"); + WEP_SHOTGUN.mdl = "ok_shotgun"; + WEP_MACHINEGUN.mdl = "ok_mg"; + WEP_VORTEX.mdl = "ok_sniper"; + } +} diff --git a/qcsrc/common/mutators/mutator/overkill/cl_overkill.qh b/qcsrc/common/mutators/mutator/overkill/cl_overkill.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/mutators/mutator/overkill/cl_overkill.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mutators/mutator/overkill/hmg.qc b/qcsrc/common/mutators/mutator/overkill/hmg.qc index 1512992c25..024fdb1cc7 100644 --- a/qcsrc/common/mutators/mutator/overkill/hmg.qc +++ b/qcsrc/common/mutators/mutator/overkill/hmg.qc @@ -1,47 +1,5 @@ -#ifndef IMPLEMENTATION -CLASS(HeavyMachineGun, Weapon) -/* ammotype */ ATTRIB(HeavyMachineGun, ammo_field, .int, ammo_nails); -/* impulse */ ATTRIB(HeavyMachineGun, impulse, int, 3); -/* flags */ ATTRIB(HeavyMachineGun, spawnflags, int, WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_HIDDEN | WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN | WEP_FLAG_SUPERWEAPON); -/* rating */ ATTRIB(HeavyMachineGun, bot_pickupbasevalue, float, BOT_PICKUP_RATING_HIGH); -/* color */ ATTRIB(HeavyMachineGun, wpcolor, vector, '0.5 0.5 0'); -/* modelname */ ATTRIB(HeavyMachineGun, mdl, string, "ok_hmg"); -#ifndef MENUQC -/* model */ ATTRIB(HeavyMachineGun, m_model, Model, MDL_HMG_ITEM); -#endif -/* crosshair */ ATTRIB(HeavyMachineGun, w_crosshair, string, "gfx/crosshairuzi"); -/* crosshair */ ATTRIB(HeavyMachineGun, w_crosshair_size, float, 0.6); -/* wepimg */ ATTRIB(HeavyMachineGun, model2, string, "weaponhmg"); -/* refname */ ATTRIB(HeavyMachineGun, netname, string, "hmg"); -/* wepname */ ATTRIB(HeavyMachineGun, m_name, string, _("Heavy Machine Gun")); - -#define X(BEGIN, P, END, class, prefix) \ - BEGIN(class) \ - P(class, prefix, ammo, float, NONE) \ - P(class, prefix, damage, float, NONE) \ - P(class, prefix, force, float, NONE) \ - P(class, prefix, refire, float, NONE) \ - P(class, prefix, reload_ammo, float, NONE) \ - P(class, prefix, reload_time, float, NONE) \ - P(class, prefix, solidpenetration, float, NONE) \ - P(class, prefix, spread_add, float, NONE) \ - P(class, prefix, spread_max, float, NONE) \ - P(class, prefix, spread_min, float, NONE) \ - P(class, prefix, switchdelay_drop, float, NONE) \ - P(class, prefix, switchdelay_raise, float, NONE) \ - P(class, prefix, weaponreplace, string, NONE) \ - P(class, prefix, weaponstartoverride, float, NONE) \ - P(class, prefix, weaponstart, float, NONE) \ - P(class, prefix, weaponthrowable, float, NONE) \ - END() - W_PROPS(X, HeavyMachineGun, hmg) -#undef X - -ENDCLASS(HeavyMachineGun) -REGISTER_WEAPON(HMG, hmg, NEW(HeavyMachineGun)); +#include "hmg.qh" -#endif -#ifdef IMPLEMENTATION #ifdef SVQC REGISTER_MUTATOR(hmg_nadesupport, true); @@ -174,4 +132,3 @@ METHOD(HeavyMachineGun, wr_impacteffect, void(entity thiswep, entity actor)) } #endif -#endif diff --git a/qcsrc/common/mutators/mutator/overkill/hmg.qh b/qcsrc/common/mutators/mutator/overkill/hmg.qh new file mode 100644 index 0000000000..671caa47b9 --- /dev/null +++ b/qcsrc/common/mutators/mutator/overkill/hmg.qh @@ -0,0 +1,42 @@ +#pragma once + +CLASS(HeavyMachineGun, Weapon) +/* ammotype */ ATTRIB(HeavyMachineGun, ammo_field, .int, ammo_nails); +/* impulse */ ATTRIB(HeavyMachineGun, impulse, int, 3); +/* flags */ ATTRIB(HeavyMachineGun, spawnflags, int, WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_HIDDEN | WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN | WEP_FLAG_SUPERWEAPON); +/* rating */ ATTRIB(HeavyMachineGun, bot_pickupbasevalue, float, BOT_PICKUP_RATING_HIGH); +/* color */ ATTRIB(HeavyMachineGun, wpcolor, vector, '0.5 0.5 0'); +/* modelname */ ATTRIB(HeavyMachineGun, mdl, string, "ok_hmg"); +#ifndef MENUQC +/* model */ ATTRIB(HeavyMachineGun, m_model, Model, MDL_HMG_ITEM); +#endif +/* crosshair */ ATTRIB(HeavyMachineGun, w_crosshair, string, "gfx/crosshairuzi"); +/* crosshair */ ATTRIB(HeavyMachineGun, w_crosshair_size, float, 0.6); +/* wepimg */ ATTRIB(HeavyMachineGun, model2, string, "weaponhmg"); +/* refname */ ATTRIB(HeavyMachineGun, netname, string, "hmg"); +/* wepname */ ATTRIB(HeavyMachineGun, m_name, string, _("Heavy Machine Gun")); + +#define X(BEGIN, P, END, class, prefix) \ + BEGIN(class) \ + P(class, prefix, ammo, float, NONE) \ + P(class, prefix, damage, float, NONE) \ + P(class, prefix, force, float, NONE) \ + P(class, prefix, refire, float, NONE) \ + P(class, prefix, reload_ammo, float, NONE) \ + P(class, prefix, reload_time, float, NONE) \ + P(class, prefix, solidpenetration, float, NONE) \ + P(class, prefix, spread_add, float, NONE) \ + P(class, prefix, spread_max, float, NONE) \ + P(class, prefix, spread_min, float, NONE) \ + P(class, prefix, switchdelay_drop, float, NONE) \ + P(class, prefix, switchdelay_raise, float, NONE) \ + P(class, prefix, weaponreplace, string, NONE) \ + P(class, prefix, weaponstartoverride, float, NONE) \ + P(class, prefix, weaponstart, float, NONE) \ + P(class, prefix, weaponthrowable, float, NONE) \ + END() + W_PROPS(X, HeavyMachineGun, hmg) +#undef X + +ENDCLASS(HeavyMachineGun) +REGISTER_WEAPON(HMG, hmg, NEW(HeavyMachineGun)); diff --git a/qcsrc/common/mutators/mutator/overkill/module.inc b/qcsrc/common/mutators/mutator/overkill/module.inc deleted file mode 100644 index a7acff5a09..0000000000 --- a/qcsrc/common/mutators/mutator/overkill/module.inc +++ /dev/null @@ -1,19 +0,0 @@ -#include "hmg.qc" -#include "rpc.qc" - -#ifdef SVQC - #include "overkill.qc" -#endif -#ifdef CSQC - #ifdef IMPLEMENTATION - REGISTER_MUTATOR(ok, false) - { - MUTATOR_ONADD { - cvar_settemp("g_overkill", "1"); - WEP_SHOTGUN.mdl = "ok_shotgun"; - WEP_MACHINEGUN.mdl = "ok_mg"; - WEP_VORTEX.mdl = "ok_sniper"; - } - } - #endif -#endif diff --git a/qcsrc/common/mutators/mutator/overkill/overkill.qc b/qcsrc/common/mutators/mutator/overkill/overkill.qc index f49fdc11b4..e69de29bb2 100644 --- a/qcsrc/common/mutators/mutator/overkill/overkill.qc +++ b/qcsrc/common/mutators/mutator/overkill/overkill.qc @@ -1,410 +0,0 @@ -#ifdef IMPLEMENTATION -bool autocvar_g_overkill_powerups_replace; -float autocvar_g_overkill_superguns_respawn_time; -bool autocvar_g_overkill_100h_anyway; -bool autocvar_g_overkill_100a_anyway; -bool autocvar_g_overkill_ammo_charge; -float autocvar_g_overkill_ammo_charge_notice; -float autocvar_g_overkill_ammo_charge_limit; - -.vector ok_deathloc; -.float ok_spawnsys_timer; -.float ok_lastwep; -.float ok_item; - -.float ok_notice_time; -.float ammo_charge[Weapons_MAX]; -.float ok_use_ammocharge = _STAT(OK_AMMO_CHARGE); -.float ok_ammo_charge = _STAT(OK_AMMO_CHARGEPOOL); - -.float ok_pauseregen_finished; - -void(entity ent, float wep) ok_DecreaseCharge; - -void ok_Initialize(); - -REGISTER_MUTATOR(ok, cvar("g_overkill") && !cvar("g_instagib") && !g_nexball && cvar_string("g_mod_balance") == "Overkill") -{ - MUTATOR_ONADD - { - ok_Initialize(); - } - - MUTATOR_ONREMOVE - { - WEP_RPC.spawnflags |= WEP_FLAG_MUTATORBLOCKED; - WEP_HMG.spawnflags |= WEP_FLAG_MUTATORBLOCKED; - } -} - -MUTATOR_HOOKFUNCTION(ok, W_DecreaseAmmo) -{ - entity actor = M_ARGV(0, entity); - if (actor.ok_use_ammocharge) - { - ok_DecreaseCharge(actor, PS(actor).m_weapon.m_id); - return true; - } -} - -MUTATOR_HOOKFUNCTION(ok, W_Reload) -{ - entity actor = M_ARGV(0, entity); - return actor.ok_use_ammocharge; -} - -void W_Blaster_Attack(entity, .entity, float, float, float, float, float, float, float, float, float, float); -spawnfunc(weapon_hmg); -spawnfunc(weapon_rpc); - -void ok_DecreaseCharge(entity ent, int wep) -{ - if(!ent.ok_use_ammocharge) return; - - entity wepent = Weapons_from(wep); - - if (wepent == WEP_Null) return; // dummy - - ent.ammo_charge[wep] -= max(0, cvar(sprintf("g_overkill_ammo_decharge_%s", wepent.netname))); -} - -void ok_IncreaseCharge(entity ent, int wep) -{ - entity wepent = Weapons_from(wep); - - if (wepent == WEP_Null) return; // dummy - - if(ent.ok_use_ammocharge) - if(!PHYS_INPUT_BUTTON_ATCK(ent)) // not while attacking? - ent.ammo_charge[wep] = min(autocvar_g_overkill_ammo_charge_limit, ent.ammo_charge[wep] + cvar(sprintf("g_overkill_ammo_charge_rate_%s", wepent.netname)) * frametime / W_TICSPERFRAME); -} - -float ok_CheckWeaponCharge(entity ent, int wep) -{ - if(!ent.ok_use_ammocharge) return true; - - entity wepent = Weapons_from(wep); - - if(wepent == WEP_Null) return false; // dummy - - return (ent.ammo_charge[wep] >= cvar(sprintf("g_overkill_ammo_decharge_%s", wepent.netname))); -} - -MUTATOR_HOOKFUNCTION(ok, PlayerDamage_Calculate, CBC_ORDER_LAST) -{ - entity frag_attacker = M_ARGV(1, entity); - entity frag_target = M_ARGV(2, entity); - float frag_deathtype = M_ARGV(3, float); - - if(IS_PLAYER(frag_attacker) && IS_PLAYER(frag_target)) - if(DEATH_ISWEAPON(frag_deathtype, WEP_BLASTER)) - { - if(frag_attacker != frag_target) - if(frag_target.health > 0) - if(STAT(FROZEN, frag_target) == 0) - if(!IS_DEAD(frag_target)) - { - Send_Notification(NOTIF_ONE, frag_attacker, MSG_CENTER, CENTER_SECONDARY_NODAMAGE); - M_ARGV(6, vector) = '0 0 0'; - } - - M_ARGV(4, float) = 0; - } -} - -MUTATOR_HOOKFUNCTION(ok, PlayerDamage_SplitHealthArmor) -{ - entity frag_target = M_ARGV(2, entity); - float damage_take = M_ARGV(4, float); - - if(damage_take) - frag_target.ok_pauseregen_finished = max(frag_target.ok_pauseregen_finished, time + 2); -} - -void ok_DropItem(entity this, entity targ) -{ - entity e = new(droppedweapon); // hax - e.ok_item = true; - e.noalign = true; - e.pickup_anyway = true; - e.spawnfunc_checked = true; - spawnfunc_item_armor_small(e); - if (!wasfreed(e)) { // might have been blocked by a mutator - set_movetype(e, MOVETYPE_TOSS); - e.gravity = 1; - e.reset = SUB_Remove; - setorigin(e, this.origin + '0 0 32'); - e.velocity = '0 0 200' + normalize(targ.origin - this.origin) * 500; - SUB_SetFade(e, time + 5, 1); - } -} - -MUTATOR_HOOKFUNCTION(ok, PlayerDies) -{ - entity frag_attacker = M_ARGV(1, entity); - entity frag_target = M_ARGV(2, entity); - - entity targ = ((frag_attacker) ? frag_attacker : frag_target); - - ok_DropItem(frag_target, targ); - - frag_target.ok_lastwep = PS(frag_target).m_switchweapon.m_id; -} - -MUTATOR_HOOKFUNCTION(ok, MonsterDropItem) -{ - entity mon = M_ARGV(0, entity); - entity olditem = M_ARGV(1, entity); - entity frag_attacker = M_ARGV(2, entity); - - delete(olditem); - - M_ARGV(1, entity) = NULL; - - ok_DropItem(mon, frag_attacker); -} - -MUTATOR_HOOKFUNCTION(ok, PlayerRegen) -{ - entity player = M_ARGV(0, entity); - - // overkill's values are different, so use custom regen - if(!STAT(FROZEN, player)) - { - player.armorvalue = CalcRotRegen(player.armorvalue, autocvar_g_balance_armor_regenstable, autocvar_g_balance_armor_regen, autocvar_g_balance_armor_regenlinear, - 1 * frametime * (time > player.ok_pauseregen_finished), 0, 0, 1, 1 * frametime * (time > player.pauserotarmor_finished), autocvar_g_balance_armor_limit); - player.health = CalcRotRegen(player.health, autocvar_g_balance_health_regenstable, 0, 100, 1 * frametime * (time > player.ok_pauseregen_finished), 200, 0, - autocvar_g_balance_health_rotlinear, 1 * frametime * (time > player.pauserothealth_finished), autocvar_g_balance_health_limit); - - float minf, maxf, limitf; - - maxf = autocvar_g_balance_fuel_rotstable; - minf = autocvar_g_balance_fuel_regenstable; - limitf = autocvar_g_balance_fuel_limit; - - player.ammo_fuel = CalcRotRegen(player.ammo_fuel, minf, autocvar_g_balance_fuel_regen, autocvar_g_balance_fuel_regenlinear, - frametime * (time > player.pauseregen_finished) * ((player.items & ITEM_JetpackRegen.m_itemid) != 0), maxf, autocvar_g_balance_fuel_rot, autocvar_g_balance_fuel_rotlinear, frametime * (time > player.pauserotfuel_finished), limitf); - } - return true; // return true anyway, as frozen uses no regen -} - -MUTATOR_HOOKFUNCTION(ok, ForbidThrowCurrentWeapon) -{ - return true; -} - -MUTATOR_HOOKFUNCTION(ok, PlayerPreThink) -{ - if(intermission_running || gameover) - return; - - entity player = M_ARGV(0, entity); - - if(IS_DEAD(player) || !IS_PLAYER(player) || STAT(FROZEN, player)) - return; - - if(player.ok_lastwep) - { - Weapon newwep = Weapons_from(player.ok_lastwep); - if(player.ok_lastwep == WEP_HMG.m_id) - newwep = WEP_MACHINEGUN; - if(player.ok_lastwep == WEP_RPC.m_id) - newwep = WEP_VORTEX; - PS(player).m_switchweapon = newwep; - player.ok_lastwep = 0; - } - - ok_IncreaseCharge(player, PS(player).m_weapon.m_id); - - if(PHYS_INPUT_BUTTON_ATCK2(player)) - if(!forbidWeaponUse(player) || player.weapon_blocked) // allow if weapon is blocked - if(time >= player.jump_interval) - { - player.jump_interval = time + WEP_CVAR_PRI(blaster, refire) * W_WeaponRateFactor(player); - makevectors(player.v_angle); - - Weapon oldwep = PS(player).m_weapon; - PS(player).m_weapon = WEP_BLASTER; - W_Blaster_Attack( - player, - weaponentities[0], // TODO: unhardcode - WEP_BLASTER.m_id | HITTYPE_SECONDARY, - WEP_CVAR_SEC(vaporizer, shotangle), - WEP_CVAR_SEC(vaporizer, damage), - WEP_CVAR_SEC(vaporizer, edgedamage), - WEP_CVAR_SEC(vaporizer, radius), - WEP_CVAR_SEC(vaporizer, force), - WEP_CVAR_SEC(vaporizer, speed), - WEP_CVAR_SEC(vaporizer, spread), - WEP_CVAR_SEC(vaporizer, delay), - WEP_CVAR_SEC(vaporizer, lifetime) - ); - PS(player).m_weapon = oldwep; - } - - player.weapon_blocked = false; - - player.ok_ammo_charge = player.ammo_charge[PS(player).m_weapon.m_id]; - - if(player.ok_use_ammocharge) - if(!ok_CheckWeaponCharge(player, PS(player).m_weapon.m_id)) - { - if(autocvar_g_overkill_ammo_charge_notice && time > player.ok_notice_time && PHYS_INPUT_BUTTON_ATCK(player) && IS_REAL_CLIENT(player) && PS(player).m_weapon == PS(player).m_switchweapon) - { - //Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_OVERKILL_CHARGE); - player.ok_notice_time = time + 2; - play2(player, SND(DRYFIRE)); - } - Weapon wpn = PS(player).m_weapon; - .entity weaponentity = weaponentities[0]; // TODO: unhardcode - if(player.(weaponentity).state != WS_CLEAR) - w_ready(wpn, player, weaponentity, PHYS_INPUT_BUTTON_ATCK(player) | (PHYS_INPUT_BUTTON_ATCK2(player) << 1)); - - player.weapon_blocked = true; - } - - PHYS_INPUT_BUTTON_ATCK2(player) = false; -} - -MUTATOR_HOOKFUNCTION(ok, PlayerSpawn) -{ - entity player = M_ARGV(0, entity); - - if(autocvar_g_overkill_ammo_charge) - { - FOREACH(Weapons, it != WEP_Null, LAMBDA(player.ammo_charge[it.m_id] = autocvar_g_overkill_ammo_charge_limit)); - - player.ok_use_ammocharge = 1; - player.ok_notice_time = time; - } - else - player.ok_use_ammocharge = 0; - - // if player changed their weapon while dead, don't switch to their death weapon - if(player.impulse) - player.ok_lastwep = 0; - - player.ok_pauseregen_finished = time + 2; -} - -void self_spawnfunc_weapon_hmg(entity this) { spawnfunc_weapon_hmg(this); } -void self_spawnfunc_weapon_rpc(entity this) { spawnfunc_weapon_rpc(this); } - -MUTATOR_HOOKFUNCTION(ok, OnEntityPreSpawn) -{ - entity ent = M_ARGV(0, entity); - - if(autocvar_g_powerups) - if(autocvar_g_overkill_powerups_replace) - { - if(ent.classname == "item_strength") - { - entity wep = new(weapon_hmg); - setorigin(wep, ent.origin); - setmodel(wep, MDL_OK_HMG); - wep.ok_item = true; - wep.noalign = ent.noalign; - wep.cnt = ent.cnt; - wep.team = ent.team; - wep.respawntime = autocvar_g_overkill_superguns_respawn_time; - wep.pickup_anyway = true; - wep.spawnfunc_checked = true; - setthink(wep, self_spawnfunc_weapon_hmg); - wep.nextthink = time + 0.1; - return true; - } - - if(ent.classname == "item_invincible") - { - entity wep = new(weapon_rpc); - setorigin(wep, ent.origin); - setmodel(wep, MDL_OK_RPC); - wep.ok_item = true; - wep.noalign = ent.noalign; - wep.cnt = ent.cnt; - wep.team = ent.team; - wep.respawntime = autocvar_g_overkill_superguns_respawn_time; - wep.pickup_anyway = true; - wep.spawnfunc_checked = true; - setthink(wep, self_spawnfunc_weapon_rpc); - wep.nextthink = time + 0.1; - return true; - } - } -} - -MUTATOR_HOOKFUNCTION(ok, FilterItem) -{ - entity item = M_ARGV(0, entity); - - if(item.ok_item) - return; - - switch(item.items) - { - case ITEM_HealthMega.m_itemid: return !(autocvar_g_overkill_100h_anyway); - case ITEM_ArmorMega.m_itemid: return !(autocvar_g_overkill_100a_anyway); - } - - return true; -} - -MUTATOR_HOOKFUNCTION(ok, SpectateCopy) -{ - entity spectatee = M_ARGV(0, entity); - entity client = M_ARGV(1, entity); - - client.ammo_charge[PS(client).m_weapon.m_id] = spectatee.ammo_charge[PS(spectatee).m_weapon.m_id]; - client.ok_use_ammocharge = spectatee.ok_use_ammocharge; -} - -MUTATOR_HOOKFUNCTION(ok, SetStartItems) -{ - WepSet ok_start_items = (WEPSET(MACHINEGUN) | WEPSET(VORTEX) | WEPSET(SHOTGUN)); - - if(WEP_RPC.weaponstart > 0) { ok_start_items |= WEPSET(RPC); } - if(WEP_HMG.weaponstart > 0) { ok_start_items |= WEPSET(HMG); } - - start_items |= IT_UNLIMITED_WEAPON_AMMO; - start_weapons = warmup_start_weapons = ok_start_items; -} - -MUTATOR_HOOKFUNCTION(ok, BuildMutatorsString) -{ - M_ARGV(0, string) = strcat(M_ARGV(0, string), ":OK"); -} - -MUTATOR_HOOKFUNCTION(ok, BuildMutatorsPrettyString) -{ - M_ARGV(0, string) = strcat(M_ARGV(0, string), ", Overkill"); -} - -MUTATOR_HOOKFUNCTION(ok, SetModname) -{ - M_ARGV(0, string) = "Overkill"; - return true; -} - -void ok_SetCvars() -{ - // hack to force overkill playermodels - cvar_settemp("sv_defaultcharacter", "1"); - cvar_settemp("sv_defaultplayermodel", "models/ok_player/okrobot1.dpm models/ok_player/okrobot2.dpm models/ok_player/okrobot3.dpm models/ok_player/okrobot4.dpm models/ok_player/okmale1.dpm models/ok_player/okmale2.dpm models/ok_player/okmale3.dpm models/ok_player/okmale4.dpm"); - cvar_settemp("sv_defaultplayermodel_red", "models/ok_player/okrobot1.dpm models/ok_player/okrobot2.dpm models/ok_player/okrobot3.dpm models/ok_player/okrobot4.dpm"); - cvar_settemp("sv_defaultplayermodel_blue", "models/ok_player/okmale1.dpm models/ok_player/okmale2.dpm models/ok_player/okmale3.dpm models/ok_player/okmale4.dpm"); -} - -void ok_Initialize() -{ - ok_SetCvars(); - - precache_all_playermodels("models/ok_player/*.dpm"); - - WEP_RPC.spawnflags &= ~WEP_FLAG_MUTATORBLOCKED; - WEP_HMG.spawnflags &= ~WEP_FLAG_MUTATORBLOCKED; - - WEP_SHOTGUN.mdl = "ok_shotgun"; - WEP_MACHINEGUN.mdl = "ok_mg"; - WEP_VORTEX.mdl = "ok_sniper"; -} -#endif diff --git a/qcsrc/common/mutators/mutator/overkill/overkill.qh b/qcsrc/common/mutators/mutator/overkill/overkill.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/mutators/mutator/overkill/overkill.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mutators/mutator/overkill/rpc.qc b/qcsrc/common/mutators/mutator/overkill/rpc.qc index f9be2ad9f5..4540a26f2e 100644 --- a/qcsrc/common/mutators/mutator/overkill/rpc.qc +++ b/qcsrc/common/mutators/mutator/overkill/rpc.qc @@ -1,52 +1,5 @@ -#ifndef IMPLEMENTATION -CLASS(RocketPropelledChainsaw, Weapon) -/* ammotype */ ATTRIB(RocketPropelledChainsaw, ammo_field, .int, ammo_rockets); -/* impulse */ ATTRIB(RocketPropelledChainsaw, impulse, int, 7); -/* flags */ ATTRIB(RocketPropelledChainsaw, spawnflags, int, WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_HIDDEN | WEP_FLAG_NORMAL | WEP_FLAG_CANCLIMB | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH | WEP_FLAG_SUPERWEAPON); -/* rating */ ATTRIB(RocketPropelledChainsaw, bot_pickupbasevalue, float, BOT_PICKUP_RATING_HIGH); -/* color */ ATTRIB(RocketPropelledChainsaw, wpcolor, vector, '0.5 0.5 0'); -/* modelname */ ATTRIB(RocketPropelledChainsaw, mdl, string, "ok_rl"); -#ifndef MENUQC -/* model */ ATTRIB(RocketPropelledChainsaw, m_model, Model, MDL_RPC_ITEM); -#endif -/* crosshair */ ATTRIB(RocketPropelledChainsaw, w_crosshair, string, "gfx/crosshairrocketlauncher"); -/* crosshair */ ATTRIB(RocketPropelledChainsaw, w_crosshair_size, float, 0.6); -/* wepimg */ ATTRIB(RocketPropelledChainsaw, model2, string, "weaponrpc"); -/* refname */ ATTRIB(RocketPropelledChainsaw, netname, string, "rpc"); -/* wepname */ ATTRIB(RocketPropelledChainsaw, m_name, string, _("Rocket Propelled Chainsaw")); - -#define X(BEGIN, P, END, class, prefix) \ - BEGIN(class) \ - P(class, prefix, ammo, float, NONE) \ - P(class, prefix, animtime, float, NONE) \ - P(class, prefix, damage2, float, NONE) \ - P(class, prefix, damageforcescale, float, NONE) \ - P(class, prefix, damage, float, NONE) \ - P(class, prefix, edgedamage, float, NONE) \ - P(class, prefix, force, float, NONE) \ - P(class, prefix, health, float, NONE) \ - P(class, prefix, lifetime, float, NONE) \ - P(class, prefix, radius, float, NONE) \ - P(class, prefix, refire, float, NONE) \ - P(class, prefix, reload_ammo, float, NONE) \ - P(class, prefix, reload_time, float, NONE) \ - P(class, prefix, speedaccel, float, NONE) \ - P(class, prefix, speed, float, NONE) \ - P(class, prefix, switchdelay_drop, float, NONE) \ - P(class, prefix, switchdelay_raise, float, NONE) \ - P(class, prefix, weaponreplace, string, NONE) \ - P(class, prefix, weaponstartoverride, float, NONE) \ - P(class, prefix, weaponstart, float, NONE) \ - P(class, prefix, weaponthrowable, float, NONE) \ - END() - W_PROPS(X, RocketPropelledChainsaw, rpc) -#undef X - -ENDCLASS(RocketPropelledChainsaw) -REGISTER_WEAPON(RPC, rpc, NEW(RocketPropelledChainsaw)); +#include "rpc.qh" -#endif -#ifdef IMPLEMENTATION #ifdef SVQC spawnfunc(weapon_rpc) { weapon_defaultspawnfunc(this, WEP_RPC); } @@ -232,4 +185,3 @@ METHOD(RocketPropelledChainsaw, wr_impacteffect, void(entity thiswep, entity act } #endif -#endif diff --git a/qcsrc/common/mutators/mutator/overkill/rpc.qh b/qcsrc/common/mutators/mutator/overkill/rpc.qh new file mode 100644 index 0000000000..d68c9d9808 --- /dev/null +++ b/qcsrc/common/mutators/mutator/overkill/rpc.qh @@ -0,0 +1,47 @@ +#pragma once + +CLASS(RocketPropelledChainsaw, Weapon) +/* ammotype */ ATTRIB(RocketPropelledChainsaw, ammo_field, .int, ammo_rockets); +/* impulse */ ATTRIB(RocketPropelledChainsaw, impulse, int, 7); +/* flags */ ATTRIB(RocketPropelledChainsaw, spawnflags, int, WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_HIDDEN | WEP_FLAG_NORMAL | WEP_FLAG_CANCLIMB | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH | WEP_FLAG_SUPERWEAPON); +/* rating */ ATTRIB(RocketPropelledChainsaw, bot_pickupbasevalue, float, BOT_PICKUP_RATING_HIGH); +/* color */ ATTRIB(RocketPropelledChainsaw, wpcolor, vector, '0.5 0.5 0'); +/* modelname */ ATTRIB(RocketPropelledChainsaw, mdl, string, "ok_rl"); +#ifndef MENUQC +/* model */ ATTRIB(RocketPropelledChainsaw, m_model, Model, MDL_RPC_ITEM); +#endif +/* crosshair */ ATTRIB(RocketPropelledChainsaw, w_crosshair, string, "gfx/crosshairrocketlauncher"); +/* crosshair */ ATTRIB(RocketPropelledChainsaw, w_crosshair_size, float, 0.6); +/* wepimg */ ATTRIB(RocketPropelledChainsaw, model2, string, "weaponrpc"); +/* refname */ ATTRIB(RocketPropelledChainsaw, netname, string, "rpc"); +/* wepname */ ATTRIB(RocketPropelledChainsaw, m_name, string, _("Rocket Propelled Chainsaw")); + +#define X(BEGIN, P, END, class, prefix) \ + BEGIN(class) \ + P(class, prefix, ammo, float, NONE) \ + P(class, prefix, animtime, float, NONE) \ + P(class, prefix, damage2, float, NONE) \ + P(class, prefix, damageforcescale, float, NONE) \ + P(class, prefix, damage, float, NONE) \ + P(class, prefix, edgedamage, float, NONE) \ + P(class, prefix, force, float, NONE) \ + P(class, prefix, health, float, NONE) \ + P(class, prefix, lifetime, float, NONE) \ + P(class, prefix, radius, float, NONE) \ + P(class, prefix, refire, float, NONE) \ + P(class, prefix, reload_ammo, float, NONE) \ + P(class, prefix, reload_time, float, NONE) \ + P(class, prefix, speedaccel, float, NONE) \ + P(class, prefix, speed, float, NONE) \ + P(class, prefix, switchdelay_drop, float, NONE) \ + P(class, prefix, switchdelay_raise, float, NONE) \ + P(class, prefix, weaponreplace, string, NONE) \ + P(class, prefix, weaponstartoverride, float, NONE) \ + P(class, prefix, weaponstart, float, NONE) \ + P(class, prefix, weaponthrowable, float, NONE) \ + END() + W_PROPS(X, RocketPropelledChainsaw, rpc) +#undef X + +ENDCLASS(RocketPropelledChainsaw) +REGISTER_WEAPON(RPC, rpc, NEW(RocketPropelledChainsaw)); diff --git a/qcsrc/common/mutators/mutator/overkill/sv_overkill.qc b/qcsrc/common/mutators/mutator/overkill/sv_overkill.qc new file mode 100644 index 0000000000..6d5ac80866 --- /dev/null +++ b/qcsrc/common/mutators/mutator/overkill/sv_overkill.qc @@ -0,0 +1,410 @@ +#include "sv_overkill.qh" + +bool autocvar_g_overkill_powerups_replace; +float autocvar_g_overkill_superguns_respawn_time; +bool autocvar_g_overkill_100h_anyway; +bool autocvar_g_overkill_100a_anyway; +bool autocvar_g_overkill_ammo_charge; +float autocvar_g_overkill_ammo_charge_notice; +float autocvar_g_overkill_ammo_charge_limit; + +.vector ok_deathloc; +.float ok_spawnsys_timer; +.float ok_lastwep; +.float ok_item; + +.float ok_notice_time; +.float ammo_charge[Weapons_MAX]; +.float ok_use_ammocharge = _STAT(OK_AMMO_CHARGE); +.float ok_ammo_charge = _STAT(OK_AMMO_CHARGEPOOL); + +.float ok_pauseregen_finished; + +void(entity ent, float wep) ok_DecreaseCharge; + +void ok_Initialize(); + +REGISTER_MUTATOR(ok, cvar("g_overkill") && !cvar("g_instagib") && !g_nexball && cvar_string("g_mod_balance") == "Overkill") +{ + MUTATOR_ONADD + { + ok_Initialize(); + } + + MUTATOR_ONREMOVE + { + WEP_RPC.spawnflags |= WEP_FLAG_MUTATORBLOCKED; + WEP_HMG.spawnflags |= WEP_FLAG_MUTATORBLOCKED; + } +} + +MUTATOR_HOOKFUNCTION(ok, W_DecreaseAmmo) +{ + entity actor = M_ARGV(0, entity); + if (actor.ok_use_ammocharge) + { + ok_DecreaseCharge(actor, PS(actor).m_weapon.m_id); + return true; + } +} + +MUTATOR_HOOKFUNCTION(ok, W_Reload) +{ + entity actor = M_ARGV(0, entity); + return actor.ok_use_ammocharge; +} + +void W_Blaster_Attack(entity, .entity, float, float, float, float, float, float, float, float, float, float); +spawnfunc(weapon_hmg); +spawnfunc(weapon_rpc); + +void ok_DecreaseCharge(entity ent, int wep) +{ + if(!ent.ok_use_ammocharge) return; + + entity wepent = Weapons_from(wep); + + if (wepent == WEP_Null) return; // dummy + + ent.ammo_charge[wep] -= max(0, cvar(sprintf("g_overkill_ammo_decharge_%s", wepent.netname))); +} + +void ok_IncreaseCharge(entity ent, int wep) +{ + entity wepent = Weapons_from(wep); + + if (wepent == WEP_Null) return; // dummy + + if(ent.ok_use_ammocharge) + if(!PHYS_INPUT_BUTTON_ATCK(ent)) // not while attacking? + ent.ammo_charge[wep] = min(autocvar_g_overkill_ammo_charge_limit, ent.ammo_charge[wep] + cvar(sprintf("g_overkill_ammo_charge_rate_%s", wepent.netname)) * frametime / W_TICSPERFRAME); +} + +float ok_CheckWeaponCharge(entity ent, int wep) +{ + if(!ent.ok_use_ammocharge) return true; + + entity wepent = Weapons_from(wep); + + if(wepent == WEP_Null) return false; // dummy + + return (ent.ammo_charge[wep] >= cvar(sprintf("g_overkill_ammo_decharge_%s", wepent.netname))); +} + +MUTATOR_HOOKFUNCTION(ok, PlayerDamage_Calculate, CBC_ORDER_LAST) +{ + entity frag_attacker = M_ARGV(1, entity); + entity frag_target = M_ARGV(2, entity); + float frag_deathtype = M_ARGV(3, float); + + if(IS_PLAYER(frag_attacker) && IS_PLAYER(frag_target)) + if(DEATH_ISWEAPON(frag_deathtype, WEP_BLASTER)) + { + if(frag_attacker != frag_target) + if(frag_target.health > 0) + if(STAT(FROZEN, frag_target) == 0) + if(!IS_DEAD(frag_target)) + { + Send_Notification(NOTIF_ONE, frag_attacker, MSG_CENTER, CENTER_SECONDARY_NODAMAGE); + M_ARGV(6, vector) = '0 0 0'; + } + + M_ARGV(4, float) = 0; + } +} + +MUTATOR_HOOKFUNCTION(ok, PlayerDamage_SplitHealthArmor) +{ + entity frag_target = M_ARGV(2, entity); + float damage_take = M_ARGV(4, float); + + if(damage_take) + frag_target.ok_pauseregen_finished = max(frag_target.ok_pauseregen_finished, time + 2); +} + +void ok_DropItem(entity this, entity targ) +{ + entity e = new(droppedweapon); // hax + e.ok_item = true; + e.noalign = true; + e.pickup_anyway = true; + e.spawnfunc_checked = true; + spawnfunc_item_armor_small(e); + if (!wasfreed(e)) { // might have been blocked by a mutator + set_movetype(e, MOVETYPE_TOSS); + e.gravity = 1; + e.reset = SUB_Remove; + setorigin(e, this.origin + '0 0 32'); + e.velocity = '0 0 200' + normalize(targ.origin - this.origin) * 500; + SUB_SetFade(e, time + 5, 1); + } +} + +MUTATOR_HOOKFUNCTION(ok, PlayerDies) +{ + entity frag_attacker = M_ARGV(1, entity); + entity frag_target = M_ARGV(2, entity); + + entity targ = ((frag_attacker) ? frag_attacker : frag_target); + + ok_DropItem(frag_target, targ); + + frag_target.ok_lastwep = PS(frag_target).m_switchweapon.m_id; +} + +MUTATOR_HOOKFUNCTION(ok, MonsterDropItem) +{ + entity mon = M_ARGV(0, entity); + entity olditem = M_ARGV(1, entity); + entity frag_attacker = M_ARGV(2, entity); + + delete(olditem); + + M_ARGV(1, entity) = NULL; + + ok_DropItem(mon, frag_attacker); +} + +MUTATOR_HOOKFUNCTION(ok, PlayerRegen) +{ + entity player = M_ARGV(0, entity); + + // overkill's values are different, so use custom regen + if(!STAT(FROZEN, player)) + { + player.armorvalue = CalcRotRegen(player.armorvalue, autocvar_g_balance_armor_regenstable, autocvar_g_balance_armor_regen, autocvar_g_balance_armor_regenlinear, + 1 * frametime * (time > player.ok_pauseregen_finished), 0, 0, 1, 1 * frametime * (time > player.pauserotarmor_finished), autocvar_g_balance_armor_limit); + player.health = CalcRotRegen(player.health, autocvar_g_balance_health_regenstable, 0, 100, 1 * frametime * (time > player.ok_pauseregen_finished), 200, 0, + autocvar_g_balance_health_rotlinear, 1 * frametime * (time > player.pauserothealth_finished), autocvar_g_balance_health_limit); + + float minf, maxf, limitf; + + maxf = autocvar_g_balance_fuel_rotstable; + minf = autocvar_g_balance_fuel_regenstable; + limitf = autocvar_g_balance_fuel_limit; + + player.ammo_fuel = CalcRotRegen(player.ammo_fuel, minf, autocvar_g_balance_fuel_regen, autocvar_g_balance_fuel_regenlinear, + frametime * (time > player.pauseregen_finished) * ((player.items & ITEM_JetpackRegen.m_itemid) != 0), maxf, autocvar_g_balance_fuel_rot, autocvar_g_balance_fuel_rotlinear, frametime * (time > player.pauserotfuel_finished), limitf); + } + return true; // return true anyway, as frozen uses no regen +} + +MUTATOR_HOOKFUNCTION(ok, ForbidThrowCurrentWeapon) +{ + return true; +} + +MUTATOR_HOOKFUNCTION(ok, PlayerPreThink) +{ + if(intermission_running || gameover) + return; + + entity player = M_ARGV(0, entity); + + if(IS_DEAD(player) || !IS_PLAYER(player) || STAT(FROZEN, player)) + return; + + if(player.ok_lastwep) + { + Weapon newwep = Weapons_from(player.ok_lastwep); + if(player.ok_lastwep == WEP_HMG.m_id) + newwep = WEP_MACHINEGUN; + if(player.ok_lastwep == WEP_RPC.m_id) + newwep = WEP_VORTEX; + PS(player).m_switchweapon = newwep; + player.ok_lastwep = 0; + } + + ok_IncreaseCharge(player, PS(player).m_weapon.m_id); + + if(PHYS_INPUT_BUTTON_ATCK2(player)) + if(!forbidWeaponUse(player) || player.weapon_blocked) // allow if weapon is blocked + if(time >= player.jump_interval) + { + player.jump_interval = time + WEP_CVAR_PRI(blaster, refire) * W_WeaponRateFactor(player); + makevectors(player.v_angle); + + Weapon oldwep = PS(player).m_weapon; + PS(player).m_weapon = WEP_BLASTER; + W_Blaster_Attack( + player, + weaponentities[0], // TODO: unhardcode + WEP_BLASTER.m_id | HITTYPE_SECONDARY, + WEP_CVAR_SEC(vaporizer, shotangle), + WEP_CVAR_SEC(vaporizer, damage), + WEP_CVAR_SEC(vaporizer, edgedamage), + WEP_CVAR_SEC(vaporizer, radius), + WEP_CVAR_SEC(vaporizer, force), + WEP_CVAR_SEC(vaporizer, speed), + WEP_CVAR_SEC(vaporizer, spread), + WEP_CVAR_SEC(vaporizer, delay), + WEP_CVAR_SEC(vaporizer, lifetime) + ); + PS(player).m_weapon = oldwep; + } + + player.weapon_blocked = false; + + player.ok_ammo_charge = player.ammo_charge[PS(player).m_weapon.m_id]; + + if(player.ok_use_ammocharge) + if(!ok_CheckWeaponCharge(player, PS(player).m_weapon.m_id)) + { + if(autocvar_g_overkill_ammo_charge_notice && time > player.ok_notice_time && PHYS_INPUT_BUTTON_ATCK(player) && IS_REAL_CLIENT(player) && PS(player).m_weapon == PS(player).m_switchweapon) + { + //Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_OVERKILL_CHARGE); + player.ok_notice_time = time + 2; + play2(player, SND(DRYFIRE)); + } + Weapon wpn = PS(player).m_weapon; + .entity weaponentity = weaponentities[0]; // TODO: unhardcode + if(player.(weaponentity).state != WS_CLEAR) + w_ready(wpn, player, weaponentity, PHYS_INPUT_BUTTON_ATCK(player) | (PHYS_INPUT_BUTTON_ATCK2(player) << 1)); + + player.weapon_blocked = true; + } + + PHYS_INPUT_BUTTON_ATCK2(player) = false; +} + +MUTATOR_HOOKFUNCTION(ok, PlayerSpawn) +{ + entity player = M_ARGV(0, entity); + + if(autocvar_g_overkill_ammo_charge) + { + FOREACH(Weapons, it != WEP_Null, LAMBDA(player.ammo_charge[it.m_id] = autocvar_g_overkill_ammo_charge_limit)); + + player.ok_use_ammocharge = 1; + player.ok_notice_time = time; + } + else + player.ok_use_ammocharge = 0; + + // if player changed their weapon while dead, don't switch to their death weapon + if(player.impulse) + player.ok_lastwep = 0; + + player.ok_pauseregen_finished = time + 2; +} + +void self_spawnfunc_weapon_hmg(entity this) { spawnfunc_weapon_hmg(this); } +void self_spawnfunc_weapon_rpc(entity this) { spawnfunc_weapon_rpc(this); } + +MUTATOR_HOOKFUNCTION(ok, OnEntityPreSpawn) +{ + entity ent = M_ARGV(0, entity); + + if(autocvar_g_powerups) + if(autocvar_g_overkill_powerups_replace) + { + if(ent.classname == "item_strength") + { + entity wep = new(weapon_hmg); + setorigin(wep, ent.origin); + setmodel(wep, MDL_OK_HMG); + wep.ok_item = true; + wep.noalign = ent.noalign; + wep.cnt = ent.cnt; + wep.team = ent.team; + wep.respawntime = autocvar_g_overkill_superguns_respawn_time; + wep.pickup_anyway = true; + wep.spawnfunc_checked = true; + setthink(wep, self_spawnfunc_weapon_hmg); + wep.nextthink = time + 0.1; + return true; + } + + if(ent.classname == "item_invincible") + { + entity wep = new(weapon_rpc); + setorigin(wep, ent.origin); + setmodel(wep, MDL_OK_RPC); + wep.ok_item = true; + wep.noalign = ent.noalign; + wep.cnt = ent.cnt; + wep.team = ent.team; + wep.respawntime = autocvar_g_overkill_superguns_respawn_time; + wep.pickup_anyway = true; + wep.spawnfunc_checked = true; + setthink(wep, self_spawnfunc_weapon_rpc); + wep.nextthink = time + 0.1; + return true; + } + } +} + +MUTATOR_HOOKFUNCTION(ok, FilterItem) +{ + entity item = M_ARGV(0, entity); + + if(item.ok_item) + return; + + switch(item.items) + { + case ITEM_HealthMega.m_itemid: return !(autocvar_g_overkill_100h_anyway); + case ITEM_ArmorMega.m_itemid: return !(autocvar_g_overkill_100a_anyway); + } + + return true; +} + +MUTATOR_HOOKFUNCTION(ok, SpectateCopy) +{ + entity spectatee = M_ARGV(0, entity); + entity client = M_ARGV(1, entity); + + client.ammo_charge[PS(client).m_weapon.m_id] = spectatee.ammo_charge[PS(spectatee).m_weapon.m_id]; + client.ok_use_ammocharge = spectatee.ok_use_ammocharge; +} + +MUTATOR_HOOKFUNCTION(ok, SetStartItems) +{ + WepSet ok_start_items = (WEPSET(MACHINEGUN) | WEPSET(VORTEX) | WEPSET(SHOTGUN)); + + if(WEP_RPC.weaponstart > 0) { ok_start_items |= WEPSET(RPC); } + if(WEP_HMG.weaponstart > 0) { ok_start_items |= WEPSET(HMG); } + + start_items |= IT_UNLIMITED_WEAPON_AMMO; + start_weapons = warmup_start_weapons = ok_start_items; +} + +MUTATOR_HOOKFUNCTION(ok, BuildMutatorsString) +{ + M_ARGV(0, string) = strcat(M_ARGV(0, string), ":OK"); +} + +MUTATOR_HOOKFUNCTION(ok, BuildMutatorsPrettyString) +{ + M_ARGV(0, string) = strcat(M_ARGV(0, string), ", Overkill"); +} + +MUTATOR_HOOKFUNCTION(ok, SetModname) +{ + M_ARGV(0, string) = "Overkill"; + return true; +} + +void ok_SetCvars() +{ + // hack to force overkill playermodels + cvar_settemp("sv_defaultcharacter", "1"); + cvar_settemp("sv_defaultplayermodel", "models/ok_player/okrobot1.dpm models/ok_player/okrobot2.dpm models/ok_player/okrobot3.dpm models/ok_player/okrobot4.dpm models/ok_player/okmale1.dpm models/ok_player/okmale2.dpm models/ok_player/okmale3.dpm models/ok_player/okmale4.dpm"); + cvar_settemp("sv_defaultplayermodel_red", "models/ok_player/okrobot1.dpm models/ok_player/okrobot2.dpm models/ok_player/okrobot3.dpm models/ok_player/okrobot4.dpm"); + cvar_settemp("sv_defaultplayermodel_blue", "models/ok_player/okmale1.dpm models/ok_player/okmale2.dpm models/ok_player/okmale3.dpm models/ok_player/okmale4.dpm"); +} + +void ok_Initialize() +{ + ok_SetCvars(); + + precache_all_playermodels("models/ok_player/*.dpm"); + + WEP_RPC.spawnflags &= ~WEP_FLAG_MUTATORBLOCKED; + WEP_HMG.spawnflags &= ~WEP_FLAG_MUTATORBLOCKED; + + WEP_SHOTGUN.mdl = "ok_shotgun"; + WEP_MACHINEGUN.mdl = "ok_mg"; + WEP_VORTEX.mdl = "ok_sniper"; +} diff --git a/qcsrc/common/mutators/mutator/overkill/sv_overkill.qh b/qcsrc/common/mutators/mutator/overkill/sv_overkill.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/mutators/mutator/overkill/sv_overkill.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mutators/mutator/physical_items/_mod.inc b/qcsrc/common/mutators/mutator/physical_items/_mod.inc index 4d4ef59f8a..e99d4e257a 100644 --- a/qcsrc/common/mutators/mutator/physical_items/_mod.inc +++ b/qcsrc/common/mutators/mutator/physical_items/_mod.inc @@ -1,2 +1,4 @@ // generated file; do not modify -#include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/mutators/mutator/physical_items/_mod.qh b/qcsrc/common/mutators/mutator/physical_items/_mod.qh index a347cec04e..1aab8b0a84 100644 --- a/qcsrc/common/mutators/mutator/physical_items/_mod.qh +++ b/qcsrc/common/mutators/mutator/physical_items/_mod.qh @@ -1,2 +1,4 @@ // generated file; do not modify -#include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/mutators/mutator/physical_items/module.inc b/qcsrc/common/mutators/mutator/physical_items/module.inc deleted file mode 100644 index 7ed9b039be..0000000000 --- a/qcsrc/common/mutators/mutator/physical_items/module.inc +++ /dev/null @@ -1,3 +0,0 @@ -#ifdef SVQC -#include "physical_items.qc" -#endif diff --git a/qcsrc/common/mutators/mutator/physical_items/physical_items.qc b/qcsrc/common/mutators/mutator/physical_items/physical_items.qc deleted file mode 100644 index f015baeaa9..0000000000 --- a/qcsrc/common/mutators/mutator/physical_items/physical_items.qc +++ /dev/null @@ -1,141 +0,0 @@ -#ifdef IMPLEMENTATION -int autocvar_g_physical_items; -float autocvar_g_physical_items_damageforcescale; -float autocvar_g_physical_items_reset; - -REGISTER_MUTATOR(physical_items, cvar("g_physical_items")) -{ - // check if we have a physics engine - MUTATOR_ONADD - { - if (!(autocvar_physics_ode && checkextension("DP_PHYSICS_ODE"))) - { - LOG_TRACE("Warning: Physical items are enabled but no physics engine can be used. Reverting to old items."); - return -1; - } - } - - MUTATOR_ONROLLBACK_OR_REMOVE - { - // nothing to roll back - } - - MUTATOR_ONREMOVE - { - LOG_INFO("This cannot be removed at runtime\n"); - return -1; - } - - return 0; -} - -.vector spawn_origin, spawn_angles; - -void physical_item_think(entity this) -{ - this.nextthink = time; - - this.alpha = this.owner.alpha; // apply fading and ghosting - - if(!this.cnt) // map item, not dropped - { - // copy ghost item properties - this.colormap = this.owner.colormap; - this.colormod = this.owner.colormod; - this.glowmod = this.owner.glowmod; - - // if the item is not spawned, make sure the invisible / ghost item returns to its origin and stays there - if(autocvar_g_physical_items_reset) - { - if(this.owner.wait > time) // awaiting respawn - { - setorigin(this, this.spawn_origin); - this.angles = this.spawn_angles; - this.solid = SOLID_NOT; - this.alpha = -1; - set_movetype(this, MOVETYPE_NONE); - } - else - { - this.alpha = 1; - this.solid = SOLID_CORPSE; - set_movetype(this, MOVETYPE_PHYSICS); - } - } - } - - if(!this.owner.modelindex) - delete(this); // the real item is gone, remove this -} - -void physical_item_touch(entity this, entity toucher) -{ - if(!this.cnt) // not for dropped items - if (ITEM_TOUCH_NEEDKILL()) - { - setorigin(this, this.spawn_origin); - this.angles = this.spawn_angles; - } -} - -void physical_item_damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force) -{ - if(!this.cnt) // not for dropped items - if(ITEM_DAMAGE_NEEDKILL(deathtype)) - { - setorigin(this, this.spawn_origin); - this.angles = this.spawn_angles; - } -} - -MUTATOR_HOOKFUNCTION(physical_items, Item_Spawn) -{ - entity item = M_ARGV(0, entity); - - if(item.owner == NULL && autocvar_g_physical_items <= 1) - return; - if (item.spawnflags & 1) // floating item - return; - - // The actual item can't be physical and trigger at the same time, so make it invisible and use a second entity for physics. - // Ugly hack, but unless SOLID_TRIGGER is gotten to work with MOVETYPE_PHYSICS in the engine it can't be fixed. - entity wep; - wep = spawn(); - _setmodel(wep, item.model); - setsize(wep, item.mins, item.maxs); - setorigin(wep, item.origin); - wep.angles = item.angles; - wep.velocity = item.velocity; - - wep.owner = item; - wep.solid = SOLID_CORPSE; - set_movetype(wep, MOVETYPE_PHYSICS); - wep.takedamage = DAMAGE_AIM; - wep.effects |= EF_NOMODELFLAGS; // disable the spinning - wep.colormap = item.owner.colormap; - wep.glowmod = item.owner.glowmod; - wep.damageforcescale = autocvar_g_physical_items_damageforcescale; - wep.dphitcontentsmask = item.dphitcontentsmask; - wep.cnt = (item.owner != NULL); - - setthink(wep, physical_item_think); - wep.nextthink = time; - settouch(wep, physical_item_touch); - wep.event_damage = physical_item_damage; - - if(!wep.cnt) - { - // fix the spawn origin - setorigin(wep, wep.origin + '0 0 1'); - droptofloor(wep); - } - - wep.spawn_origin = wep.origin; - wep.spawn_angles = item.angles; - - item.effects |= EF_NODRAW; // hide the original weapon - set_movetype(item, MOVETYPE_FOLLOW); - item.aiment = wep; // attach the original weapon - setSendEntity(item, func_null); -} -#endif diff --git a/qcsrc/common/mutators/mutator/physical_items/sv_physical_items.qc b/qcsrc/common/mutators/mutator/physical_items/sv_physical_items.qc new file mode 100644 index 0000000000..62b30e2d14 --- /dev/null +++ b/qcsrc/common/mutators/mutator/physical_items/sv_physical_items.qc @@ -0,0 +1,141 @@ +#include "sv_physical_items.qh" + +int autocvar_g_physical_items; +float autocvar_g_physical_items_damageforcescale; +float autocvar_g_physical_items_reset; + +REGISTER_MUTATOR(physical_items, cvar("g_physical_items")) +{ + // check if we have a physics engine + MUTATOR_ONADD + { + if (!(autocvar_physics_ode && checkextension("DP_PHYSICS_ODE"))) + { + LOG_TRACE("Warning: Physical items are enabled but no physics engine can be used. Reverting to old items."); + return -1; + } + } + + MUTATOR_ONROLLBACK_OR_REMOVE + { + // nothing to roll back + } + + MUTATOR_ONREMOVE + { + LOG_INFO("This cannot be removed at runtime\n"); + return -1; + } + + return 0; +} + +.vector spawn_origin, spawn_angles; + +void physical_item_think(entity this) +{ + this.nextthink = time; + + this.alpha = this.owner.alpha; // apply fading and ghosting + + if(!this.cnt) // map item, not dropped + { + // copy ghost item properties + this.colormap = this.owner.colormap; + this.colormod = this.owner.colormod; + this.glowmod = this.owner.glowmod; + + // if the item is not spawned, make sure the invisible / ghost item returns to its origin and stays there + if(autocvar_g_physical_items_reset) + { + if(this.owner.wait > time) // awaiting respawn + { + setorigin(this, this.spawn_origin); + this.angles = this.spawn_angles; + this.solid = SOLID_NOT; + this.alpha = -1; + set_movetype(this, MOVETYPE_NONE); + } + else + { + this.alpha = 1; + this.solid = SOLID_CORPSE; + set_movetype(this, MOVETYPE_PHYSICS); + } + } + } + + if(!this.owner.modelindex) + delete(this); // the real item is gone, remove this +} + +void physical_item_touch(entity this, entity toucher) +{ + if(!this.cnt) // not for dropped items + if (ITEM_TOUCH_NEEDKILL()) + { + setorigin(this, this.spawn_origin); + this.angles = this.spawn_angles; + } +} + +void physical_item_damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force) +{ + if(!this.cnt) // not for dropped items + if(ITEM_DAMAGE_NEEDKILL(deathtype)) + { + setorigin(this, this.spawn_origin); + this.angles = this.spawn_angles; + } +} + +MUTATOR_HOOKFUNCTION(physical_items, Item_Spawn) +{ + entity item = M_ARGV(0, entity); + + if(item.owner == NULL && autocvar_g_physical_items <= 1) + return; + if (item.spawnflags & 1) // floating item + return; + + // The actual item can't be physical and trigger at the same time, so make it invisible and use a second entity for physics. + // Ugly hack, but unless SOLID_TRIGGER is gotten to work with MOVETYPE_PHYSICS in the engine it can't be fixed. + entity wep; + wep = spawn(); + _setmodel(wep, item.model); + setsize(wep, item.mins, item.maxs); + setorigin(wep, item.origin); + wep.angles = item.angles; + wep.velocity = item.velocity; + + wep.owner = item; + wep.solid = SOLID_CORPSE; + set_movetype(wep, MOVETYPE_PHYSICS); + wep.takedamage = DAMAGE_AIM; + wep.effects |= EF_NOMODELFLAGS; // disable the spinning + wep.colormap = item.owner.colormap; + wep.glowmod = item.owner.glowmod; + wep.damageforcescale = autocvar_g_physical_items_damageforcescale; + wep.dphitcontentsmask = item.dphitcontentsmask; + wep.cnt = (item.owner != NULL); + + setthink(wep, physical_item_think); + wep.nextthink = time; + settouch(wep, physical_item_touch); + wep.event_damage = physical_item_damage; + + if(!wep.cnt) + { + // fix the spawn origin + setorigin(wep, wep.origin + '0 0 1'); + droptofloor(wep); + } + + wep.spawn_origin = wep.origin; + wep.spawn_angles = item.angles; + + item.effects |= EF_NODRAW; // hide the original weapon + set_movetype(item, MOVETYPE_FOLLOW); + item.aiment = wep; // attach the original weapon + setSendEntity(item, func_null); +} diff --git a/qcsrc/common/mutators/mutator/physical_items/sv_physical_items.qh b/qcsrc/common/mutators/mutator/physical_items/sv_physical_items.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/mutators/mutator/physical_items/sv_physical_items.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mutators/mutator/pinata/_mod.inc b/qcsrc/common/mutators/mutator/pinata/_mod.inc index a0bd94d00e..5859c55107 100644 --- a/qcsrc/common/mutators/mutator/pinata/_mod.inc +++ b/qcsrc/common/mutators/mutator/pinata/_mod.inc @@ -1,2 +1,4 @@ // generated file; do not modify -#include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/mutators/mutator/pinata/_mod.qh b/qcsrc/common/mutators/mutator/pinata/_mod.qh index 1602640e09..fdb51ed25b 100644 --- a/qcsrc/common/mutators/mutator/pinata/_mod.qh +++ b/qcsrc/common/mutators/mutator/pinata/_mod.qh @@ -1,2 +1,4 @@ // generated file; do not modify -#include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/mutators/mutator/pinata/module.inc b/qcsrc/common/mutators/mutator/pinata/module.inc deleted file mode 100644 index 4e22966863..0000000000 --- a/qcsrc/common/mutators/mutator/pinata/module.inc +++ /dev/null @@ -1,3 +0,0 @@ -#ifdef SVQC -#include "pinata.qc" -#endif diff --git a/qcsrc/common/mutators/mutator/pinata/pinata.qc b/qcsrc/common/mutators/mutator/pinata/pinata.qc deleted file mode 100644 index acdf1718b0..0000000000 --- a/qcsrc/common/mutators/mutator/pinata/pinata.qc +++ /dev/null @@ -1,28 +0,0 @@ -#ifdef IMPLEMENTATION -REGISTER_MUTATOR(pinata, cvar("g_pinata") && !cvar("g_instagib") && !cvar("g_overkill")); - -MUTATOR_HOOKFUNCTION(pinata, PlayerDies) -{ - entity frag_target = M_ARGV(2, entity); - - FOREACH(Weapons, it != WEP_Null, LAMBDA( - if(frag_target.weapons & WepSet_FromWeapon(it)) - if(PS(frag_target).m_switchweapon != it) - if(W_IsWeaponThrowable(frag_target, it.m_id)) - W_ThrowNewWeapon(frag_target, it.m_id, false, CENTER_OR_VIEWOFS(frag_target), randomvec() * 175 + '0 0 325'); - )); - - return true; -} - -MUTATOR_HOOKFUNCTION(pinata, BuildMutatorsString) -{ - M_ARGV(0, string) = strcat(M_ARGV(0, string), ":Pinata"); -} - -MUTATOR_HOOKFUNCTION(pinata, BuildMutatorsPrettyString) -{ - M_ARGV(0, string) = strcat(M_ARGV(0, string), ", Piñata"); -} - -#endif diff --git a/qcsrc/common/mutators/mutator/pinata/sv_pinata.qc b/qcsrc/common/mutators/mutator/pinata/sv_pinata.qc new file mode 100644 index 0000000000..bc3887e860 --- /dev/null +++ b/qcsrc/common/mutators/mutator/pinata/sv_pinata.qc @@ -0,0 +1,27 @@ +#include "sv_pinata.qh" + +REGISTER_MUTATOR(pinata, cvar("g_pinata") && !cvar("g_instagib") && !cvar("g_overkill")); + +MUTATOR_HOOKFUNCTION(pinata, PlayerDies) +{ + entity frag_target = M_ARGV(2, entity); + + FOREACH(Weapons, it != WEP_Null, LAMBDA( + if(frag_target.weapons & WepSet_FromWeapon(it)) + if(PS(frag_target).m_switchweapon != it) + if(W_IsWeaponThrowable(frag_target, it.m_id)) + W_ThrowNewWeapon(frag_target, it.m_id, false, CENTER_OR_VIEWOFS(frag_target), randomvec() * 175 + '0 0 325'); + )); + + return true; +} + +MUTATOR_HOOKFUNCTION(pinata, BuildMutatorsString) +{ + M_ARGV(0, string) = strcat(M_ARGV(0, string), ":Pinata"); +} + +MUTATOR_HOOKFUNCTION(pinata, BuildMutatorsPrettyString) +{ + M_ARGV(0, string) = strcat(M_ARGV(0, string), ", Piñata"); +} diff --git a/qcsrc/common/mutators/mutator/pinata/sv_pinata.qh b/qcsrc/common/mutators/mutator/pinata/sv_pinata.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/mutators/mutator/pinata/sv_pinata.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mutators/mutator/random_gravity/_mod.inc b/qcsrc/common/mutators/mutator/random_gravity/_mod.inc index feeaec8d69..846bd8bf82 100644 --- a/qcsrc/common/mutators/mutator/random_gravity/_mod.inc +++ b/qcsrc/common/mutators/mutator/random_gravity/_mod.inc @@ -1,2 +1,4 @@ // generated file; do not modify -#include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/mutators/mutator/random_gravity/_mod.qh b/qcsrc/common/mutators/mutator/random_gravity/_mod.qh index 99a11ed638..2cdf724023 100644 --- a/qcsrc/common/mutators/mutator/random_gravity/_mod.qh +++ b/qcsrc/common/mutators/mutator/random_gravity/_mod.qh @@ -1,2 +1,4 @@ // generated file; do not modify -#include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/mutators/mutator/random_gravity/module.inc b/qcsrc/common/mutators/mutator/random_gravity/module.inc deleted file mode 100644 index 91baa43102..0000000000 --- a/qcsrc/common/mutators/mutator/random_gravity/module.inc +++ /dev/null @@ -1,3 +0,0 @@ -#ifdef SVQC -#include "random_gravity.qc" -#endif diff --git a/qcsrc/common/mutators/mutator/random_gravity/random_gravity.qc b/qcsrc/common/mutators/mutator/random_gravity/random_gravity.qc deleted file mode 100644 index 9706aeca78..0000000000 --- a/qcsrc/common/mutators/mutator/random_gravity/random_gravity.qc +++ /dev/null @@ -1,50 +0,0 @@ -#ifdef IMPLEMENTATION -// Random Gravity -// -// Mutator by Mario -// Inspired by Player 2 - -float autocvar_g_random_gravity_negative_chance; -float autocvar_g_random_gravity_min; -float autocvar_g_random_gravity_max; -float autocvar_g_random_gravity_positive; -float autocvar_g_random_gravity_negative; -float autocvar_g_random_gravity_delay; - -REGISTER_MUTATOR(random_gravity, cvar("g_random_gravity")) -{ - MUTATOR_ONADD - { - cvar_settemp("sv_gravity", cvar_string("sv_gravity")); // settemp current gravity so it's restored on match end - } -} - -float gravity_delay; - -MUTATOR_HOOKFUNCTION(random_gravity, SV_StartFrame) -{ - if(gameover || !cvar("g_random_gravity")) return false; - if(time < gravity_delay) return false; - if(time < game_starttime) return false; - if(round_handler_IsActive() && !round_handler_IsRoundStarted()) return false; - - if(random() >= autocvar_g_random_gravity_negative_chance) - cvar_set("sv_gravity", ftos(bound(autocvar_g_random_gravity_min, random() - random() * -autocvar_g_random_gravity_negative, autocvar_g_random_gravity_max))); - else - cvar_set("sv_gravity", ftos(bound(autocvar_g_random_gravity_min, random() * autocvar_g_random_gravity_positive, autocvar_g_random_gravity_max))); - - gravity_delay = time + autocvar_g_random_gravity_delay; - - LOG_TRACE("Gravity is now: ", ftos(autocvar_sv_gravity)); -} - -MUTATOR_HOOKFUNCTION(random_gravity, BuildMutatorsString) -{ - M_ARGV(0, string) = strcat(M_ARGV(0, string), ":RandomGravity"); -} - -MUTATOR_HOOKFUNCTION(random_gravity, BuildMutatorsPrettyString) -{ - M_ARGV(0, string) = strcat(M_ARGV(0, string), ", Random gravity"); -} -#endif diff --git a/qcsrc/common/mutators/mutator/random_gravity/sv_random_gravity.qc b/qcsrc/common/mutators/mutator/random_gravity/sv_random_gravity.qc new file mode 100644 index 0000000000..f6f28a7dcb --- /dev/null +++ b/qcsrc/common/mutators/mutator/random_gravity/sv_random_gravity.qc @@ -0,0 +1,50 @@ +#include "sv_random_gravity.qh" + +// Random Gravity + +// Mutator by Mario +// Inspired by Player 2 + +float autocvar_g_random_gravity_negative_chance; +float autocvar_g_random_gravity_min; +float autocvar_g_random_gravity_max; +float autocvar_g_random_gravity_positive; +float autocvar_g_random_gravity_negative; +float autocvar_g_random_gravity_delay; + +REGISTER_MUTATOR(random_gravity, cvar("g_random_gravity")) +{ + MUTATOR_ONADD + { + cvar_settemp("sv_gravity", cvar_string("sv_gravity")); // settemp current gravity so it's restored on match end + } +} + +float gravity_delay; + +MUTATOR_HOOKFUNCTION(random_gravity, SV_StartFrame) +{ + if(gameover || !cvar("g_random_gravity")) return false; + if(time < gravity_delay) return false; + if(time < game_starttime) return false; + if(round_handler_IsActive() && !round_handler_IsRoundStarted()) return false; + + if(random() >= autocvar_g_random_gravity_negative_chance) + cvar_set("sv_gravity", ftos(bound(autocvar_g_random_gravity_min, random() - random() * -autocvar_g_random_gravity_negative, autocvar_g_random_gravity_max))); + else + cvar_set("sv_gravity", ftos(bound(autocvar_g_random_gravity_min, random() * autocvar_g_random_gravity_positive, autocvar_g_random_gravity_max))); + + gravity_delay = time + autocvar_g_random_gravity_delay; + + LOG_TRACE("Gravity is now: ", ftos(autocvar_sv_gravity)); +} + +MUTATOR_HOOKFUNCTION(random_gravity, BuildMutatorsString) +{ + M_ARGV(0, string) = strcat(M_ARGV(0, string), ":RandomGravity"); +} + +MUTATOR_HOOKFUNCTION(random_gravity, BuildMutatorsPrettyString) +{ + M_ARGV(0, string) = strcat(M_ARGV(0, string), ", Random gravity"); +} diff --git a/qcsrc/common/mutators/mutator/random_gravity/sv_random_gravity.qh b/qcsrc/common/mutators/mutator/random_gravity/sv_random_gravity.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/mutators/mutator/random_gravity/sv_random_gravity.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mutators/mutator/rocketflying/_mod.inc b/qcsrc/common/mutators/mutator/rocketflying/_mod.inc index 0841ae680b..537a2b37b0 100644 --- a/qcsrc/common/mutators/mutator/rocketflying/_mod.inc +++ b/qcsrc/common/mutators/mutator/rocketflying/_mod.inc @@ -1,2 +1,4 @@ // generated file; do not modify -#include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/mutators/mutator/rocketflying/_mod.qh b/qcsrc/common/mutators/mutator/rocketflying/_mod.qh index 75ca141bf0..b24545f8c4 100644 --- a/qcsrc/common/mutators/mutator/rocketflying/_mod.qh +++ b/qcsrc/common/mutators/mutator/rocketflying/_mod.qh @@ -1,2 +1,4 @@ // generated file; do not modify -#include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/mutators/mutator/rocketflying/module.inc b/qcsrc/common/mutators/mutator/rocketflying/module.inc deleted file mode 100644 index 7036bc49d6..0000000000 --- a/qcsrc/common/mutators/mutator/rocketflying/module.inc +++ /dev/null @@ -1,3 +0,0 @@ -#ifdef SVQC -#include "rocketflying.qc" -#endif diff --git a/qcsrc/common/mutators/mutator/rocketflying/rocketflying.qc b/qcsrc/common/mutators/mutator/rocketflying/rocketflying.qc deleted file mode 100644 index da7e1c3ec0..0000000000 --- a/qcsrc/common/mutators/mutator/rocketflying/rocketflying.qc +++ /dev/null @@ -1,24 +0,0 @@ -#ifdef IMPLEMENTATION -REGISTER_MUTATOR(rocketflying, cvar("g_rocket_flying")); - -MUTATOR_HOOKFUNCTION(rocketflying, EditProjectile) -{ - entity proj = M_ARGV(1, entity); - - if(proj.classname == "rocket" || proj.classname == "mine") - { - // kill detonate delay of rockets - proj.spawnshieldtime = time; - } -} - -MUTATOR_HOOKFUNCTION(rocketflying, BuildMutatorsString) -{ - M_ARGV(0, string) = strcat(M_ARGV(0, string), ":RocketFlying"); -} - -MUTATOR_HOOKFUNCTION(rocketflying, BuildMutatorsPrettyString) -{ - M_ARGV(0, string) = strcat(M_ARGV(0, string), ", Rocket Flying"); -} -#endif diff --git a/qcsrc/common/mutators/mutator/rocketflying/sv_rocketflying.qc b/qcsrc/common/mutators/mutator/rocketflying/sv_rocketflying.qc new file mode 100644 index 0000000000..9f0d8fbf0d --- /dev/null +++ b/qcsrc/common/mutators/mutator/rocketflying/sv_rocketflying.qc @@ -0,0 +1,24 @@ +#include "sv_rocketflying.qh" + +REGISTER_MUTATOR(rocketflying, cvar("g_rocket_flying")); + +MUTATOR_HOOKFUNCTION(rocketflying, EditProjectile) +{ + entity proj = M_ARGV(1, entity); + + if(proj.classname == "rocket" || proj.classname == "mine") + { + // kill detonate delay of rockets + proj.spawnshieldtime = time; + } +} + +MUTATOR_HOOKFUNCTION(rocketflying, BuildMutatorsString) +{ + M_ARGV(0, string) = strcat(M_ARGV(0, string), ":RocketFlying"); +} + +MUTATOR_HOOKFUNCTION(rocketflying, BuildMutatorsPrettyString) +{ + M_ARGV(0, string) = strcat(M_ARGV(0, string), ", Rocket Flying"); +} diff --git a/qcsrc/common/mutators/mutator/rocketflying/sv_rocketflying.qh b/qcsrc/common/mutators/mutator/rocketflying/sv_rocketflying.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/mutators/mutator/rocketflying/sv_rocketflying.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mutators/mutator/rocketminsta/_mod.inc b/qcsrc/common/mutators/mutator/rocketminsta/_mod.inc index bc579ec512..bb75554bad 100644 --- a/qcsrc/common/mutators/mutator/rocketminsta/_mod.inc +++ b/qcsrc/common/mutators/mutator/rocketminsta/_mod.inc @@ -1,2 +1,4 @@ // generated file; do not modify -#include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/mutators/mutator/rocketminsta/_mod.qh b/qcsrc/common/mutators/mutator/rocketminsta/_mod.qh index 29a367d3cf..832c5da2cb 100644 --- a/qcsrc/common/mutators/mutator/rocketminsta/_mod.qh +++ b/qcsrc/common/mutators/mutator/rocketminsta/_mod.qh @@ -1,2 +1,4 @@ // generated file; do not modify -#include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/mutators/mutator/rocketminsta/module.inc b/qcsrc/common/mutators/mutator/rocketminsta/module.inc deleted file mode 100644 index b7d02a9f6b..0000000000 --- a/qcsrc/common/mutators/mutator/rocketminsta/module.inc +++ /dev/null @@ -1,3 +0,0 @@ -#ifdef SVQC -#include "rocketminsta.qc" -#endif diff --git a/qcsrc/common/mutators/mutator/rocketminsta/rocketminsta.qc b/qcsrc/common/mutators/mutator/rocketminsta/rocketminsta.qc deleted file mode 100644 index b0a740391f..0000000000 --- a/qcsrc/common/mutators/mutator/rocketminsta/rocketminsta.qc +++ /dev/null @@ -1,40 +0,0 @@ -#ifdef IMPLEMENTATION -#include -#include - -REGISTER_MUTATOR(rm, cvar("g_instagib")); - -MUTATOR_HOOKFUNCTION(rm, PlayerDamage_Calculate) -{ - // we do it this way, so rm can be toggled during the match - if(!autocvar_g_rm) { return; } - - entity frag_attacker = M_ARGV(1, entity); - entity frag_target = M_ARGV(2, entity); - float frag_deathtype = M_ARGV(3, float); - float frag_damage = M_ARGV(4, float); - - if(DEATH_ISWEAPON(frag_deathtype, WEP_DEVASTATOR)) - if(frag_attacker == frag_target || frag_target.classname == "nade") - frag_damage = 0; - - if(autocvar_g_rm_laser) - if(DEATH_ISWEAPON(frag_deathtype, WEP_ELECTRO)) - if(frag_attacker == frag_target || (round_handler_IsActive() && !round_handler_IsRoundStarted())) - frag_damage = 0; - - M_ARGV(4, float) = frag_damage; -} - -MUTATOR_HOOKFUNCTION(rm, PlayerDies) -{ - // we do it this way, so rm can be toggled during the match - if(!autocvar_g_rm) { return; } - - float frag_deathtype = M_ARGV(3, float); - - if(DEATH_ISWEAPON(frag_deathtype, WEP_DEVASTATOR) || DEATH_ISWEAPON(frag_deathtype, WEP_ELECTRO)) - M_ARGV(4, float) = 1000; // always gib if it was a vaporizer death -} - -#endif diff --git a/qcsrc/common/mutators/mutator/rocketminsta/sv_rocketminsta.qc b/qcsrc/common/mutators/mutator/rocketminsta/sv_rocketminsta.qc new file mode 100644 index 0000000000..04d8099e02 --- /dev/null +++ b/qcsrc/common/mutators/mutator/rocketminsta/sv_rocketminsta.qc @@ -0,0 +1,39 @@ +#include "sv_rocketminsta.qh" + +#include +#include + +REGISTER_MUTATOR(rm, cvar("g_instagib")); + +MUTATOR_HOOKFUNCTION(rm, PlayerDamage_Calculate) +{ + // we do it this way, so rm can be toggled during the match + if(!autocvar_g_rm) { return; } + + entity frag_attacker = M_ARGV(1, entity); + entity frag_target = M_ARGV(2, entity); + float frag_deathtype = M_ARGV(3, float); + float frag_damage = M_ARGV(4, float); + + if(DEATH_ISWEAPON(frag_deathtype, WEP_DEVASTATOR)) + if(frag_attacker == frag_target || frag_target.classname == "nade") + frag_damage = 0; + + if(autocvar_g_rm_laser) + if(DEATH_ISWEAPON(frag_deathtype, WEP_ELECTRO)) + if(frag_attacker == frag_target || (round_handler_IsActive() && !round_handler_IsRoundStarted())) + frag_damage = 0; + + M_ARGV(4, float) = frag_damage; +} + +MUTATOR_HOOKFUNCTION(rm, PlayerDies) +{ + // we do it this way, so rm can be toggled during the match + if(!autocvar_g_rm) { return; } + + float frag_deathtype = M_ARGV(3, float); + + if(DEATH_ISWEAPON(frag_deathtype, WEP_DEVASTATOR) || DEATH_ISWEAPON(frag_deathtype, WEP_ELECTRO)) + M_ARGV(4, float) = 1000; // always gib if it was a vaporizer death +} diff --git a/qcsrc/common/mutators/mutator/rocketminsta/sv_rocketminsta.qh b/qcsrc/common/mutators/mutator/rocketminsta/sv_rocketminsta.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/mutators/mutator/rocketminsta/sv_rocketminsta.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mutators/mutator/running_guns/_mod.inc b/qcsrc/common/mutators/mutator/running_guns/_mod.inc index f88b36a534..d1db34cf75 100644 --- a/qcsrc/common/mutators/mutator/running_guns/_mod.inc +++ b/qcsrc/common/mutators/mutator/running_guns/_mod.inc @@ -1,2 +1,4 @@ // generated file; do not modify -#include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/mutators/mutator/running_guns/_mod.qh b/qcsrc/common/mutators/mutator/running_guns/_mod.qh index 559be4c987..cc0c58eff4 100644 --- a/qcsrc/common/mutators/mutator/running_guns/_mod.qh +++ b/qcsrc/common/mutators/mutator/running_guns/_mod.qh @@ -1,2 +1,4 @@ // generated file; do not modify -#include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/mutators/mutator/running_guns/module.inc b/qcsrc/common/mutators/mutator/running_guns/module.inc deleted file mode 100644 index 036b70ff62..0000000000 --- a/qcsrc/common/mutators/mutator/running_guns/module.inc +++ /dev/null @@ -1,3 +0,0 @@ -#ifdef SVQC -#include "running_guns.qc" -#endif diff --git a/qcsrc/common/mutators/mutator/running_guns/running_guns.qc b/qcsrc/common/mutators/mutator/running_guns/running_guns.qc deleted file mode 100644 index cad4d5f2fe..0000000000 --- a/qcsrc/common/mutators/mutator/running_guns/running_guns.qc +++ /dev/null @@ -1,14 +0,0 @@ -#ifdef IMPLEMENTATION - -bool autocvar_g_running_guns; - -REGISTER_MUTATOR(running_guns, autocvar_g_running_guns); - -MUTATOR_HOOKFUNCTION(running_guns, SetDefaultAlpha) -{ - default_player_alpha = -1; - default_weapon_alpha = +1; - return true; -} - -#endif diff --git a/qcsrc/common/mutators/mutator/running_guns/sv_running_guns.qc b/qcsrc/common/mutators/mutator/running_guns/sv_running_guns.qc new file mode 100644 index 0000000000..797108e012 --- /dev/null +++ b/qcsrc/common/mutators/mutator/running_guns/sv_running_guns.qc @@ -0,0 +1,11 @@ +#include "sv_running_guns.qh" + +bool autocvar_g_running_guns; +REGISTER_MUTATOR(running_guns, autocvar_g_running_guns); + +MUTATOR_HOOKFUNCTION(running_guns, SetDefaultAlpha) +{ + default_player_alpha = -1; + default_weapon_alpha = +1; + return true; +} diff --git a/qcsrc/common/mutators/mutator/running_guns/sv_running_guns.qh b/qcsrc/common/mutators/mutator/running_guns/sv_running_guns.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/mutators/mutator/running_guns/sv_running_guns.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mutators/mutator/sandbox/_mod.inc b/qcsrc/common/mutators/mutator/sandbox/_mod.inc index 8e54c1f95e..569c25319b 100644 --- a/qcsrc/common/mutators/mutator/sandbox/_mod.inc +++ b/qcsrc/common/mutators/mutator/sandbox/_mod.inc @@ -1,2 +1,4 @@ // generated file; do not modify -#include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/mutators/mutator/sandbox/_mod.qh b/qcsrc/common/mutators/mutator/sandbox/_mod.qh index 81e250c7fa..86b112934d 100644 --- a/qcsrc/common/mutators/mutator/sandbox/_mod.qh +++ b/qcsrc/common/mutators/mutator/sandbox/_mod.qh @@ -1,2 +1,4 @@ // generated file; do not modify -#include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/mutators/mutator/sandbox/module.inc b/qcsrc/common/mutators/mutator/sandbox/module.inc deleted file mode 100644 index 0715d5b403..0000000000 --- a/qcsrc/common/mutators/mutator/sandbox/module.inc +++ /dev/null @@ -1,4 +0,0 @@ -#ifdef SVQC -#include "sandbox.qc" - -#endif diff --git a/qcsrc/common/mutators/mutator/sandbox/sandbox.qc b/qcsrc/common/mutators/mutator/sandbox/sandbox.qc deleted file mode 100644 index 577e29c6b0..0000000000 --- a/qcsrc/common/mutators/mutator/sandbox/sandbox.qc +++ /dev/null @@ -1,825 +0,0 @@ -#ifdef IMPLEMENTATION -int autocvar_g_sandbox_info; -bool autocvar_g_sandbox_readonly; -string autocvar_g_sandbox_storage_name; -float autocvar_g_sandbox_storage_autosave; -bool autocvar_g_sandbox_storage_autoload; -float autocvar_g_sandbox_editor_flood; -int autocvar_g_sandbox_editor_maxobjects; -int autocvar_g_sandbox_editor_free; -float autocvar_g_sandbox_editor_distance_spawn; -float autocvar_g_sandbox_editor_distance_edit; -float autocvar_g_sandbox_object_scale_min; -float autocvar_g_sandbox_object_scale_max; -float autocvar_g_sandbox_object_material_velocity_min; -float autocvar_g_sandbox_object_material_velocity_factor; - -float autosave_time; -void sandbox_Database_Load(); - -REGISTER_MUTATOR(sandbox, cvar("g_sandbox")) -{ - MUTATOR_ONADD - { - autosave_time = time + autocvar_g_sandbox_storage_autosave; // don't save the first server frame - if(autocvar_g_sandbox_storage_autoload) - sandbox_Database_Load(); - } -} - -const float MAX_STORAGE_ATTACHMENTS = 16; -float object_count; -.float object_flood; -.entity object_attach; -.string material; - -.float touch_timer; -void sandbox_ObjectFunction_Touch(entity this, entity toucher) -{ - // apply material impact effects - - if(!this.material) - return; - if(this.touch_timer > time) - return; // don't execute each frame - this.touch_timer = time + 0.1; - - // make particle count and sound volume depend on impact speed - float intensity; - intensity = vlen(this.velocity) + vlen(toucher.velocity); - if(intensity) // avoid divisions by 0 - intensity /= 2; // average the two velocities - if (!(intensity >= autocvar_g_sandbox_object_material_velocity_min)) - return; // impact not strong enough to do anything - // now offset intensity and apply it to the effects - intensity -= autocvar_g_sandbox_object_material_velocity_min; // start from minimum velocity, not actual velocity - intensity = bound(0, intensity * autocvar_g_sandbox_object_material_velocity_factor, 1); - - _sound(this, CH_TRIGGER, strcat("object/impact_", this.material, "_", ftos(ceil(random() * 5)) , ".wav"), VOL_BASE * intensity, ATTEN_NORM); - Send_Effect_(strcat("impact_", this.material), this.origin, '0 0 0', ceil(intensity * 10)); // allow a count from 1 to 10 -} - -void sandbox_ObjectFunction_Think(entity this) -{ - // decide if and how this object can be grabbed - if(autocvar_g_sandbox_readonly) - this.grab = 0; // no grabbing - else if(autocvar_g_sandbox_editor_free < 2 && this.crypto_idfp) - this.grab = 1; // owner only - else - this.grab = 3; // anyone - - // Object owner is stored via player UID, but we also need the owner as an entity (if the player is available on the server). - // Therefore, scan for all players, and update the owner as long as the player is present. We must always do this, - // since if the owning player disconnects, the object's owner should also be reset. - - // bots can't have objects - FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), LAMBDA( - if(this.crypto_idfp == it.crypto_idfp) - { - this.realowner = it; - break; - } - this.realowner = NULL; - )); - - this.nextthink = time; - - CSQCMODEL_AUTOUPDATE(this); -} - -.float old_solid, old_movetype; -entity sandbox_ObjectEdit_Get(entity this, float permissions) -{ - // Returns the traced entity if the player can edit it, and NULL if not. - // If permissions if false, the object is returned regardless of editing rights. - // Attached objects are SOLID_NOT and do not get traced. - - crosshair_trace_plusvisibletriggers(this); - if(vdist(this.origin - trace_ent.origin, >, autocvar_g_sandbox_editor_distance_edit)) - return NULL; // out of trace range - if(trace_ent.classname != "object") - return NULL; // entity is not an object - if(!permissions) - return trace_ent; // don't check permissions, anyone can edit this object - if(trace_ent.crypto_idfp == "") - return trace_ent; // the player who spawned this object did not have an UID, so anyone can edit it - if (!(trace_ent.realowner != this && autocvar_g_sandbox_editor_free < 2)) - return trace_ent; // object does not belong to the player, and players can only edit their own objects on this server - return NULL; -} - -void sandbox_ObjectEdit_Scale(entity e, float f) -{ - e.scale = f; - if(e.scale) - { - e.scale = bound(autocvar_g_sandbox_object_scale_min, e.scale, autocvar_g_sandbox_object_scale_max); - _setmodel(e, e.model); // reset mins and maxs based on mesh - setsize(e, e.mins * e.scale, e.maxs * e.scale); // adapt bounding box size to model size - } -} - -void sandbox_ObjectAttach_Remove(entity e); -void sandbox_ObjectAttach_Set(entity e, entity parent, string s) -{ - // attaches e to parent on string s - - // we can't attach to an attachment, for obvious reasons - sandbox_ObjectAttach_Remove(e); - - e.old_solid = e.solid; // persist solidity - e.old_movetype = e.move_movetype; // persist physics - set_movetype(e, MOVETYPE_FOLLOW); - e.solid = SOLID_NOT; - e.takedamage = DAMAGE_NO; - - setattachment(e, parent, s); - e.owner = parent; -} - -void sandbox_ObjectAttach_Remove(entity e) -{ - // detaches any object attached to e - - FOREACH_ENTITY_ENT(owner, e, - { - if(it.classname != "object") continue; - - vector org; - org = gettaginfo(it, 0); - setattachment(it, NULL, ""); - it.owner = NULL; - - // objects change origin and angles when detached, so apply previous position - setorigin(it, org); - it.angles = e.angles; // don't allow detached objects to spin or roll - - it.solid = it.old_solid; // restore persisted solidity - set_movetype(it, it.old_movetype); // restore persisted physics - it.takedamage = DAMAGE_AIM; - }); -} - -entity sandbox_ObjectSpawn(entity this, float database) -{ - // spawn a new object with default properties - - entity e = new(object); - e.takedamage = DAMAGE_AIM; - e.damageforcescale = 1; - e.solid = SOLID_BBOX; // SOLID_BSP would be best, but can lag the server badly - set_movetype(e, MOVETYPE_TOSS); - e.frame = 0; - e.skin = 0; - e.material = string_null; - settouch(e, sandbox_ObjectFunction_Touch); - setthink(e, sandbox_ObjectFunction_Think); - e.nextthink = time; - //e.effects |= EF_SELECTABLE; // don't do this all the time, maybe just when editing objects? - - if(!database) - { - // set the object's owner via player UID - // if the player does not have an UID, the owner cannot be stored and his objects may be edited by anyone - if(this.crypto_idfp != "") - e.crypto_idfp = strzone(this.crypto_idfp); - else - print_to(this, "^1SANDBOX - WARNING: ^7You spawned an object, but lack a player UID. ^1Your objects are not secured and can be edited by any player!"); - - // set public object information - e.netname = strzone(this.netname); // name of the owner - e.message = strzone(strftime(true, "%d-%m-%Y %H:%M:%S")); // creation time - e.message2 = strzone(strftime(true, "%d-%m-%Y %H:%M:%S")); // last editing time - - // set origin and direction based on player position and view angle - makevectors(this.v_angle); - WarpZone_TraceLine(this.origin + this.view_ofs, this.origin + this.view_ofs + v_forward * autocvar_g_sandbox_editor_distance_spawn, MOVE_NORMAL, this); - setorigin(e, trace_endpos); - e.angles_y = this.v_angle.y; - } - - CSQCMODEL_AUTOINIT(e); - - object_count += 1; - return e; -} - -void sandbox_ObjectRemove(entity e) -{ - sandbox_ObjectAttach_Remove(e); // detach child objects - - // if the object being removed has been selected for attachment by a player, unset it - FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it) && it.object_attach == e, LAMBDA(it.object_attach = NULL)); - - if(e.material) { strunzone(e.material); e.material = string_null; } - if(e.crypto_idfp) { strunzone(e.crypto_idfp); e.crypto_idfp = string_null; } - if(e.netname) { strunzone(e.netname); e.netname = string_null; } - if(e.message) { strunzone(e.message); e.message = string_null; } - if(e.message2) { strunzone(e.message2); e.message2 = string_null; } - delete(e); - e = NULL; - - object_count -= 1; -} - -string port_string[MAX_STORAGE_ATTACHMENTS]; // fteqcc crashes if this isn't defined as a global - -string sandbox_ObjectPort_Save(entity e, float database) -{ - // save object properties, and return them as a string - float i = 0; - string s; - entity head; - - for(head = NULL; (head = find(head, classname, "object")); ) - { - // the main object needs to be first in the array [0] with attached objects following - float slot, physics, solidity; - if(head == e) // this is the main object, place it first - { - slot = 0; - solidity = head.solid; // applied solidity is normal solidity for children - physics = head.move_movetype; // applied physics are normal physics for parents - } - else if(head.owner == e) // child object, list them in order - { - i += 1; // children start from 1 - slot = i; - solidity = head.old_solid; // persisted solidity is normal solidity for children - physics = head.old_movetype; // persisted physics are normal physics for children - gettaginfo(head.owner, head.tag_index); // get the name of the tag our object is attached to, used further below - } - else - continue; - - // ---------------- OBJECT PROPERTY STORAGE: SAVE ---------------- - if(slot) - { - // properties stored only for child objects - if(gettaginfo_name) port_string[slot] = strcat(port_string[slot], "\"", gettaginfo_name, "\" "); else port_string[slot] = strcat(port_string[slot], "\"\" "); // none - } - else - { - // properties stored only for parent objects - if(database) - { - port_string[slot] = strcat(port_string[slot], sprintf("\"%.9v\"", head.origin), " "); - port_string[slot] = strcat(port_string[slot], sprintf("\"%.9v\"", head.angles), " "); - } - } - // properties stored for all objects - port_string[slot] = strcat(port_string[slot], "\"", head.model, "\" "); - port_string[slot] = strcat(port_string[slot], ftos(head.skin), " "); - port_string[slot] = strcat(port_string[slot], ftos(head.alpha), " "); - port_string[slot] = strcat(port_string[slot], sprintf("\"%.9v\"", head.colormod), " "); - port_string[slot] = strcat(port_string[slot], sprintf("\"%.9v\"", head.glowmod), " "); - port_string[slot] = strcat(port_string[slot], ftos(head.frame), " "); - port_string[slot] = strcat(port_string[slot], ftos(head.scale), " "); - port_string[slot] = strcat(port_string[slot], ftos(solidity), " "); - port_string[slot] = strcat(port_string[slot], ftos(physics), " "); - port_string[slot] = strcat(port_string[slot], ftos(head.damageforcescale), " "); - if(head.material) port_string[slot] = strcat(port_string[slot], "\"", head.material, "\" "); else port_string[slot] = strcat(port_string[slot], "\"\" "); // none - if(database) - { - // properties stored only for the database - if(head.crypto_idfp) port_string[slot] = strcat(port_string[slot], "\"", head.crypto_idfp, "\" "); else port_string[slot] = strcat(port_string[slot], "\"\" "); // none - port_string[slot] = strcat(port_string[slot], "\"", e.netname, "\" "); - port_string[slot] = strcat(port_string[slot], "\"", e.message, "\" "); - port_string[slot] = strcat(port_string[slot], "\"", e.message2, "\" "); - } - } - - // now apply the array to a simple string, with the ; symbol separating objects - s = ""; - for(i = 0; i <= MAX_STORAGE_ATTACHMENTS; ++i) - { - if(port_string[i]) - s = strcat(s, port_string[i], "; "); - port_string[i] = string_null; // fully clear the string - } - - return s; -} - -entity sandbox_ObjectPort_Load(entity this, string s, float database) -{ - // load object properties, and spawn a new object with them - float n, i; - entity e = NULL, parent = NULL; - - // separate objects between the ; symbols - n = tokenizebyseparator(s, "; "); - for(i = 0; i < n; ++i) - port_string[i] = argv(i); - - // now separate and apply the properties of each object - for(i = 0; i < n; ++i) - { - float argv_num; - string tagname = string_null; - argv_num = 0; - tokenize_console(port_string[i]); - e = sandbox_ObjectSpawn(this, database); - - // ---------------- OBJECT PROPERTY STORAGE: LOAD ---------------- - if(i) - { - // properties stored only for child objects - if(argv(argv_num) != "") tagname = argv(argv_num); else tagname = string_null; ++argv_num; - } - else - { - // properties stored only for parent objects - if(database) - { - setorigin(e, stov(argv(argv_num))); ++argv_num; - e.angles = stov(argv(argv_num)); ++argv_num; - } - parent = e; // mark parent objects as such - } - // properties stored for all objects - _setmodel(e, argv(argv_num)); ++argv_num; - e.skin = stof(argv(argv_num)); ++argv_num; - e.alpha = stof(argv(argv_num)); ++argv_num; - e.colormod = stov(argv(argv_num)); ++argv_num; - e.glowmod = stov(argv(argv_num)); ++argv_num; - e.frame = stof(argv(argv_num)); ++argv_num; - sandbox_ObjectEdit_Scale(e, stof(argv(argv_num))); ++argv_num; - e.solid = e.old_solid = stof(argv(argv_num)); ++argv_num; - e.old_movetype = stof(argv(argv_num)); ++argv_num; - set_movetype(e, e.old_movetype); - e.damageforcescale = stof(argv(argv_num)); ++argv_num; - if(e.material) strunzone(e.material); if(argv(argv_num) != "") e.material = strzone(argv(argv_num)); else e.material = string_null; ++argv_num; - if(database) - { - // properties stored only for the database - if(e.crypto_idfp) strunzone(e.crypto_idfp); if(argv(argv_num) != "") e.crypto_idfp = strzone(argv(argv_num)); else e.crypto_idfp = string_null; ++argv_num; - if(e.netname) strunzone(e.netname); e.netname = strzone(argv(argv_num)); ++argv_num; - if(e.message) strunzone(e.message); e.message = strzone(argv(argv_num)); ++argv_num; - if(e.message2) strunzone(e.message2); e.message2 = strzone(argv(argv_num)); ++argv_num; - } - - // attach last - if(i) - sandbox_ObjectAttach_Set(e, parent, tagname); - } - - for(i = 0; i <= MAX_STORAGE_ATTACHMENTS; ++i) - port_string[i] = string_null; // fully clear the string - - return e; -} - -void sandbox_Database_Save() -{ - // saves all objects to the database file - entity head; - string file_name; - float file_get; - - file_name = strcat("sandbox/storage_", autocvar_g_sandbox_storage_name, "_", GetMapname(), ".txt"); - file_get = fopen(file_name, FILE_WRITE); - fputs(file_get, strcat("// sandbox storage \"", autocvar_g_sandbox_storage_name, "\" for map \"", GetMapname(), "\" last updated ", strftime(true, "%d-%m-%Y %H:%M:%S"))); - fputs(file_get, strcat(" containing ", ftos(object_count), " objects\n")); - - for(head = NULL; (head = find(head, classname, "object")); ) - { - // attached objects are persisted separately, ignore them here - if(head.owner != NULL) - continue; - - // use a line of text for each object, listing all properties - fputs(file_get, strcat(sandbox_ObjectPort_Save(head, true), "\n")); - } - fclose(file_get); -} - -void sandbox_Database_Load() -{ - // loads all objects from the database file - string file_read, file_name; - float file_get, i; - - file_name = strcat("sandbox/storage_", autocvar_g_sandbox_storage_name, "_", GetMapname(), ".txt"); - file_get = fopen(file_name, FILE_READ); - if(file_get < 0) - { - if(autocvar_g_sandbox_info > 0) - LOG_INFO(strcat("^3SANDBOX - SERVER: ^7could not find storage file ^3", file_name, "^7, no objects 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 = sandbox_ObjectPort_Load(NULL, file_read, true); - - if(e.material) - { - // since objects are being loaded for the first time, precache material sounds for each - for (i = 1; i <= 5; i++) // 5 sounds in total - precache_sound(strcat("object/impact_", e.material, "_", ftos(i), ".wav")); - } - } - if(autocvar_g_sandbox_info > 0) - LOG_INFO(strcat("^3SANDBOX - SERVER: ^7successfully loaded storage file ^3", file_name, "\n")); - } - fclose(file_get); -} - -MUTATOR_HOOKFUNCTION(sandbox, SV_ParseClientCommand) -{ - if(MUTATOR_RETURNVALUE) // command was already handled? - return; - - entity player = M_ARGV(0, entity); - string cmd_name = M_ARGV(1, string); - int cmd_argc = M_ARGV(2, int); - - if(cmd_name == "g_sandbox") - { - if(autocvar_g_sandbox_readonly) - { - print_to(player, "^2SANDBOX - INFO: ^7Sandbox mode is active, but in read-only mode. Sandbox commands cannot be used"); - return true; - } - if(cmd_argc < 2) - { - print_to(player, "^2SANDBOX - INFO: ^7Sandbox mode is active. For usage information, type 'sandbox help'"); - return true; - } - - switch(argv(1)) - { - entity e; - int j; - string s; - - // ---------------- COMMAND: HELP ---------------- - case "help": - print_to(player, "You can use the following sandbox commands:"); - print_to(player, "^7\"^2object_spawn ^3models/foo/bar.md3^7\" spawns a new object in front of the player, and gives it the specified model"); - print_to(player, "^7\"^2object_remove^7\" removes the object the player is looking at. Players can only remove their own objects"); - print_to(player, "^7\"^2object_duplicate ^3value^7\" duplicates the object, if the player has copying rights over the original"); - print_to(player, "^3copy value ^7- copies the properties of the object to the specified client cvar"); - print_to(player, "^3paste value ^7- spawns an object with the given properties. Properties or cvars must be specified as follows; eg1: \"0 1 2 ...\", eg2: \"$cl_cvar\""); - print_to(player, "^7\"^2object_attach ^3property value^7\" attaches one object to another. Players can only attach their own objects"); - print_to(player, "^3get ^7- selects the object you are facing as the object to be attached"); - print_to(player, "^3set value ^7- attaches the previously selected object to the object you are facing, on the specified bone"); - print_to(player, "^3remove ^7- detaches all objects from the object you are facing"); - print_to(player, "^7\"^2object_edit ^3property value^7\" edits the given property of the object. Players can only edit their own objects"); - print_to(player, "^3skin value ^7- changes the skin of the object"); - print_to(player, "^3alpha value ^7- sets object transparency"); - print_to(player, "^3colormod \"value_x value_y value_z\" ^7- main object color"); - print_to(player, "^3glowmod \"value_x value_y value_z\" ^7- glow object color"); - print_to(player, "^3frame value ^7- object animation frame, for self-animated models"); - print_to(player, "^3scale value ^7- changes object scale. 0.5 is half size and 2 is double size"); - print_to(player, "^3solidity value ^7- object collisions, 0 = non-solid, 1 = solid"); - print_to(player, "^3physics value ^7- object physics, 0 = static, 1 = movable, 2 = physical"); - print_to(player, "^3force value ^7- amount of force applied to objects that are shot"); - print_to(player, "^3material value ^7- sets the material of the object. Default materials are: metal, stone, wood, flesh"); - print_to(player, "^7\"^2object_claim^7\" sets the player as the owner of the object, if he has the right to edit it"); - print_to(player, "^7\"^2object_info ^3value^7\" shows public information about the object"); - print_to(player, "^3object ^7- prints general information about the object, such as owner and creation / editing date"); - print_to(player, "^3mesh ^7- prints information about the object's mesh, including skeletal bones"); - print_to(player, "^3attachments ^7- prints information about the object's attachments"); - print_to(player, "^7The ^1drag object ^7key can be used to grab and carry objects. Players can only grab their own objects"); - return true; - - // ---------------- COMMAND: OBJECT, SPAWN ---------------- - case "object_spawn": - if(time < player.object_flood) - { - print_to(player, strcat("^1SANDBOX - WARNING: ^7Flood protection active. Please wait ^3", ftos(player.object_flood - time), " ^7seconds beofore spawning another object")); - return true; - } - player.object_flood = time + autocvar_g_sandbox_editor_flood; - if(object_count >= autocvar_g_sandbox_editor_maxobjects) - { - print_to(player, strcat("^1SANDBOX - WARNING: ^7Cannot spawn any more objects. Up to ^3", ftos(autocvar_g_sandbox_editor_maxobjects), " ^7objects may exist at a time")); - return true; - } - if(cmd_argc < 3) - { - print_to(player, "^1SANDBOX - WARNING: ^7Attempted to spawn an object without specifying a model. Please specify the path to your model file after the 'object_spawn' command"); - return true; - } - if (!(fexists(argv(2)))) - { - print_to(player, "^1SANDBOX - WARNING: ^7Attempted to spawn an object with a non-existent model. Make sure the path to your model file is correct"); - return true; - } - - e = sandbox_ObjectSpawn(player, false); - _setmodel(e, argv(2)); - - if(autocvar_g_sandbox_info > 0) - LOG_INFO(strcat("^3SANDBOX - SERVER: ^7", player.netname, " spawned an object at origin ^3", vtos(e.origin), "\n")); - return true; - - // ---------------- COMMAND: OBJECT, REMOVE ---------------- - case "object_remove": - e = sandbox_ObjectEdit_Get(player, true); - if(e != NULL) - { - if(autocvar_g_sandbox_info > 0) - LOG_INFO(strcat("^3SANDBOX - SERVER: ^7", player.netname, " removed an object at origin ^3", vtos(e.origin), "\n")); - sandbox_ObjectRemove(e); - return true; - } - - print_to(player, "^1SANDBOX - WARNING: ^7Object could not be removed. Make sure you are facing an object that you have edit rights over"); - return true; - - // ---------------- COMMAND: OBJECT, DUPLICATE ---------------- - case "object_duplicate": - switch(argv(2)) - { - case "copy": - // copies customizable properties of the selected object to the clipboard cvar - e = sandbox_ObjectEdit_Get(player, autocvar_g_sandbox_editor_free); // can we copy objects we can't edit? - if(e != NULL) - { - s = sandbox_ObjectPort_Save(e, false); - s = strreplace("\"", "\\\"", s); - stuffcmd(player, strcat("set ", argv(3), " \"", s, "\"")); - - print_to(player, "^2SANDBOX - INFO: ^7Object copied to clipboard"); - return true; - } - print_to(player, "^1SANDBOX - WARNING: ^7Object could not be copied. Make sure you are facing an object that you have copy rights over"); - return true; - - case "paste": - // spawns a new object using the properties in the player's clipboard cvar - if(time < player.object_flood) - { - print_to(player, strcat("^1SANDBOX - WARNING: ^7Flood protection active. Please wait ^3", ftos(player.object_flood - time), " ^7seconds beofore spawning another object")); - return true; - } - player.object_flood = time + autocvar_g_sandbox_editor_flood; - if(argv(3) == "") // no object in clipboard - { - print_to(player, "^1SANDBOX - WARNING: ^7No object in clipboard. You must copy an object before you can paste it"); - return true; - } - if(object_count >= autocvar_g_sandbox_editor_maxobjects) - { - print_to(player, strcat("^1SANDBOX - WARNING: ^7Cannot spawn any more objects. Up to ^3", ftos(autocvar_g_sandbox_editor_maxobjects), " ^7objects may exist at a time")); - return true; - } - e = sandbox_ObjectPort_Load(player, argv(3), false); - - print_to(player, "^2SANDBOX - INFO: ^7Object pasted successfully"); - if(autocvar_g_sandbox_info > 0) - LOG_INFO(strcat("^3SANDBOX - SERVER: ^7", player.netname, " pasted an object at origin ^3", vtos(e.origin), "\n")); - return true; - } - return true; - - // ---------------- COMMAND: OBJECT, ATTACH ---------------- - case "object_attach": - switch(argv(2)) - { - case "get": - // select e as the object as meant to be attached - e = sandbox_ObjectEdit_Get(player, true); - if(e != NULL) - { - player.object_attach = e; - print_to(player, "^2SANDBOX - INFO: ^7Object selected for attachment"); - return true; - } - print_to(player, "^1SANDBOX - WARNING: ^7Object could not be selected for attachment. Make sure you are facing an object that you have edit rights over"); - return true; - case "set": - if(player.object_attach == NULL) - { - print_to(player, "^1SANDBOX - WARNING: ^7No object selected for attachment. Please select an object to be attached first."); - return true; - } - - // attaches the previously selected object to e - e = sandbox_ObjectEdit_Get(player, true); - if(e != NULL) - { - sandbox_ObjectAttach_Set(player.object_attach, e, argv(3)); - player.object_attach = NULL; // object was attached, no longer keep it scheduled for attachment - print_to(player, "^2SANDBOX - INFO: ^7Object attached successfully"); - if(autocvar_g_sandbox_info > 1) - LOG_INFO(strcat("^3SANDBOX - SERVER: ^7", player.netname, " attached objects at origin ^3", vtos(e.origin), "\n")); - return true; - } - print_to(player, "^1SANDBOX - WARNING: ^7Object could not be attached to the parent. Make sure you are facing an object that you have edit rights over"); - return true; - case "remove": - // removes e if it was attached - e = sandbox_ObjectEdit_Get(player, true); - if(e != NULL) - { - sandbox_ObjectAttach_Remove(e); - print_to(player, "^2SANDBOX - INFO: ^7Child objects detached successfully"); - if(autocvar_g_sandbox_info > 1) - LOG_INFO(strcat("^3SANDBOX - SERVER: ^7", player.netname, " detached objects at origin ^3", vtos(e.origin), "\n")); - return true; - } - print_to(player, "^1SANDBOX - WARNING: ^7Child objects could not be detached. Make sure you are facing an object that you have edit rights over"); - return true; - } - return true; - - // ---------------- COMMAND: OBJECT, EDIT ---------------- - case "object_edit": - if(argv(2) == "") - { - print_to(player, "^1SANDBOX - WARNING: ^7Too few parameters. You must specify a property to edit"); - return true; - } - - e = sandbox_ObjectEdit_Get(player, true); - if(e != NULL) - { - switch(argv(2)) - { - case "skin": - e.skin = stof(argv(3)); - break; - case "alpha": - e.alpha = stof(argv(3)); - break; - case "color_main": - e.colormod = stov(argv(3)); - break; - case "color_glow": - e.glowmod = stov(argv(3)); - break; - case "frame": - e.frame = stof(argv(3)); - break; - case "scale": - sandbox_ObjectEdit_Scale(e, stof(argv(3))); - break; - case "solidity": - switch(argv(3)) - { - case "0": // non-solid - e.solid = SOLID_TRIGGER; - break; - case "1": // solid - e.solid = SOLID_BBOX; - break; - default: - break; - } - case "physics": - switch(argv(3)) - { - case "0": // static - set_movetype(e, MOVETYPE_NONE); - break; - case "1": // movable - set_movetype(e, MOVETYPE_TOSS); - break; - case "2": // physical - set_movetype(e, MOVETYPE_PHYSICS); - break; - default: - break; - } - break; - case "force": - e.damageforcescale = stof(argv(3)); - break; - case "material": - if(e.material) strunzone(e.material); - if(argv(3)) - { - for (j = 1; j <= 5; j++) // precache material sounds, 5 in total - precache_sound(strcat("object/impact_", argv(3), "_", ftos(j), ".wav")); - e.material = strzone(argv(3)); - } - else - e.material = string_null; // no material - break; - default: - print_to(player, "^1SANDBOX - WARNING: ^7Invalid object property. For usage information, type 'sandbox help'"); - return true; - } - - // update last editing time - if(e.message2) strunzone(e.message2); - e.message2 = strzone(strftime(true, "%d-%m-%Y %H:%M:%S")); - - if(autocvar_g_sandbox_info > 1) - LOG_INFO(strcat("^3SANDBOX - SERVER: ^7", player.netname, " edited property ^3", argv(2), " ^7of an object at origin ^3", vtos(e.origin), "\n")); - return true; - } - - print_to(player, "^1SANDBOX - WARNING: ^7Object could not be edited. Make sure you are facing an object that you have edit rights over"); - return true; - - // ---------------- COMMAND: OBJECT, CLAIM ---------------- - case "object_claim": - // if the player can edit an object but is not its owner, this can be used to claim that object - if(player.crypto_idfp == "") - { - print_to(player, "^1SANDBOX - WARNING: ^7You do not have a player UID, and cannot claim objects"); - return true; - } - e = sandbox_ObjectEdit_Get(player, true); - if(e != NULL) - { - // update the owner's name - // Do this before checking if you're already the owner and skipping if such, so we - // also update the player's nickname if he changed it (but has the same player UID) - if(e.netname != player.netname) - { - if(e.netname) strunzone(e.netname); - e.netname = strzone(player.netname); - print_to(player, "^2SANDBOX - INFO: ^7Object owner name updated"); - } - - if(e.crypto_idfp == player.crypto_idfp) - { - print_to(player, "^2SANDBOX - INFO: ^7Object is already yours, nothing to claim"); - return true; - } - - if(e.crypto_idfp) strunzone(e.crypto_idfp); - e.crypto_idfp = strzone(player.crypto_idfp); - - print_to(player, "^2SANDBOX - INFO: ^7Object claimed successfully"); - } - print_to(player, "^1SANDBOX - WARNING: ^7Object could not be claimed. Make sure you are facing an object that you have edit rights over"); - return true; - - // ---------------- COMMAND: OBJECT, INFO ---------------- - case "object_info": - // prints public information about the object to the player - e = sandbox_ObjectEdit_Get(player, false); - if(e != NULL) - { - switch(argv(2)) - { - case "object": - print_to(player, strcat("^2SANDBOX - INFO: ^7Object is owned by \"^7", e.netname, "^7\", created \"^3", e.message, "^7\", last edited \"^3", e.message2, "^7\"")); - return true; - case "mesh": - s = ""; - FOR_EACH_TAG(e) - s = strcat(s, "^7\"^5", gettaginfo_name, "^7\", "); - print_to(player, strcat("^2SANDBOX - INFO: ^7Object mesh is \"^3", e.model, "^7\" at animation frame ^3", ftos(e.frame), " ^7containing the following tags: ", s)); - return true; - case "attachments": - // this should show the same info as 'mesh' but for attachments - s = ""; - j = 0; - FOREACH_ENTITY_ENT(owner, e, - { - if(it.classname != "object") continue; - - ++j; // start from 1 - gettaginfo(e, it.tag_index); - s = strcat(s, "^1attachment ", ftos(j), "^7 has mesh \"^3", it.model, "^7\" at animation frame ^3", ftos(it.frame)); - s = strcat(s, "^7 and is attached to bone \"^5", gettaginfo_name, "^7\", "); - }); - if(j) // object contains attachments - print_to(player, strcat("^2SANDBOX - INFO: ^7Object contains the following ^1", ftos(j), "^7 attachment(s): ", s)); - else - print_to(player, "^2SANDBOX - INFO: ^7Object contains no attachments"); - return true; - } - } - print_to(player, "^1SANDBOX - WARNING: ^7No information could be found. Make sure you are facing an object"); - return true; - - // ---------------- COMMAND: DEFAULT ---------------- - default: - print_to(player, "Invalid command. For usage information, type 'sandbox help'"); - return true; - } - } -} - -MUTATOR_HOOKFUNCTION(sandbox, SV_StartFrame) -{ - if(!autocvar_g_sandbox_storage_autosave) - return; - if(time < autosave_time) - return; - autosave_time = time + autocvar_g_sandbox_storage_autosave; - - sandbox_Database_Save(); - - return true; -} -#endif diff --git a/qcsrc/common/mutators/mutator/sandbox/sv_sandbox.qc b/qcsrc/common/mutators/mutator/sandbox/sv_sandbox.qc new file mode 100644 index 0000000000..d0739c2e44 --- /dev/null +++ b/qcsrc/common/mutators/mutator/sandbox/sv_sandbox.qc @@ -0,0 +1,825 @@ +#include "sv_sandbox.qh" + +int autocvar_g_sandbox_info; +bool autocvar_g_sandbox_readonly; +string autocvar_g_sandbox_storage_name; +float autocvar_g_sandbox_storage_autosave; +bool autocvar_g_sandbox_storage_autoload; +float autocvar_g_sandbox_editor_flood; +int autocvar_g_sandbox_editor_maxobjects; +int autocvar_g_sandbox_editor_free; +float autocvar_g_sandbox_editor_distance_spawn; +float autocvar_g_sandbox_editor_distance_edit; +float autocvar_g_sandbox_object_scale_min; +float autocvar_g_sandbox_object_scale_max; +float autocvar_g_sandbox_object_material_velocity_min; +float autocvar_g_sandbox_object_material_velocity_factor; + +float autosave_time; +void sandbox_Database_Load(); + +REGISTER_MUTATOR(sandbox, cvar("g_sandbox")) +{ + MUTATOR_ONADD + { + autosave_time = time + autocvar_g_sandbox_storage_autosave; // don't save the first server frame + if(autocvar_g_sandbox_storage_autoload) + sandbox_Database_Load(); + } +} + +const float MAX_STORAGE_ATTACHMENTS = 16; +float object_count; +.float object_flood; +.entity object_attach; +.string material; + +.float touch_timer; +void sandbox_ObjectFunction_Touch(entity this, entity toucher) +{ + // apply material impact effects + + if(!this.material) + return; + if(this.touch_timer > time) + return; // don't execute each frame + this.touch_timer = time + 0.1; + + // make particle count and sound volume depend on impact speed + float intensity; + intensity = vlen(this.velocity) + vlen(toucher.velocity); + if(intensity) // avoid divisions by 0 + intensity /= 2; // average the two velocities + if (!(intensity >= autocvar_g_sandbox_object_material_velocity_min)) + return; // impact not strong enough to do anything + // now offset intensity and apply it to the effects + intensity -= autocvar_g_sandbox_object_material_velocity_min; // start from minimum velocity, not actual velocity + intensity = bound(0, intensity * autocvar_g_sandbox_object_material_velocity_factor, 1); + + _sound(this, CH_TRIGGER, strcat("object/impact_", this.material, "_", ftos(ceil(random() * 5)) , ".wav"), VOL_BASE * intensity, ATTEN_NORM); + Send_Effect_(strcat("impact_", this.material), this.origin, '0 0 0', ceil(intensity * 10)); // allow a count from 1 to 10 +} + +void sandbox_ObjectFunction_Think(entity this) +{ + // decide if and how this object can be grabbed + if(autocvar_g_sandbox_readonly) + this.grab = 0; // no grabbing + else if(autocvar_g_sandbox_editor_free < 2 && this.crypto_idfp) + this.grab = 1; // owner only + else + this.grab = 3; // anyone + + // Object owner is stored via player UID, but we also need the owner as an entity (if the player is available on the server). + // Therefore, scan for all players, and update the owner as long as the player is present. We must always do this, + // since if the owning player disconnects, the object's owner should also be reset. + + // bots can't have objects + FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), LAMBDA( + if(this.crypto_idfp == it.crypto_idfp) + { + this.realowner = it; + break; + } + this.realowner = NULL; + )); + + this.nextthink = time; + + CSQCMODEL_AUTOUPDATE(this); +} + +.float old_solid, old_movetype; +entity sandbox_ObjectEdit_Get(entity this, float permissions) +{ + // Returns the traced entity if the player can edit it, and NULL if not. + // If permissions if false, the object is returned regardless of editing rights. + // Attached objects are SOLID_NOT and do not get traced. + + crosshair_trace_plusvisibletriggers(this); + if(vdist(this.origin - trace_ent.origin, >, autocvar_g_sandbox_editor_distance_edit)) + return NULL; // out of trace range + if(trace_ent.classname != "object") + return NULL; // entity is not an object + if(!permissions) + return trace_ent; // don't check permissions, anyone can edit this object + if(trace_ent.crypto_idfp == "") + return trace_ent; // the player who spawned this object did not have an UID, so anyone can edit it + if (!(trace_ent.realowner != this && autocvar_g_sandbox_editor_free < 2)) + return trace_ent; // object does not belong to the player, and players can only edit their own objects on this server + return NULL; +} + +void sandbox_ObjectEdit_Scale(entity e, float f) +{ + e.scale = f; + if(e.scale) + { + e.scale = bound(autocvar_g_sandbox_object_scale_min, e.scale, autocvar_g_sandbox_object_scale_max); + _setmodel(e, e.model); // reset mins and maxs based on mesh + setsize(e, e.mins * e.scale, e.maxs * e.scale); // adapt bounding box size to model size + } +} + +void sandbox_ObjectAttach_Remove(entity e); +void sandbox_ObjectAttach_Set(entity e, entity parent, string s) +{ + // attaches e to parent on string s + + // we can't attach to an attachment, for obvious reasons + sandbox_ObjectAttach_Remove(e); + + e.old_solid = e.solid; // persist solidity + e.old_movetype = e.move_movetype; // persist physics + set_movetype(e, MOVETYPE_FOLLOW); + e.solid = SOLID_NOT; + e.takedamage = DAMAGE_NO; + + setattachment(e, parent, s); + e.owner = parent; +} + +void sandbox_ObjectAttach_Remove(entity e) +{ + // detaches any object attached to e + + FOREACH_ENTITY_ENT(owner, e, + { + if(it.classname != "object") continue; + + vector org; + org = gettaginfo(it, 0); + setattachment(it, NULL, ""); + it.owner = NULL; + + // objects change origin and angles when detached, so apply previous position + setorigin(it, org); + it.angles = e.angles; // don't allow detached objects to spin or roll + + it.solid = it.old_solid; // restore persisted solidity + set_movetype(it, it.old_movetype); // restore persisted physics + it.takedamage = DAMAGE_AIM; + }); +} + +entity sandbox_ObjectSpawn(entity this, float database) +{ + // spawn a new object with default properties + + entity e = new(object); + e.takedamage = DAMAGE_AIM; + e.damageforcescale = 1; + e.solid = SOLID_BBOX; // SOLID_BSP would be best, but can lag the server badly + set_movetype(e, MOVETYPE_TOSS); + e.frame = 0; + e.skin = 0; + e.material = string_null; + settouch(e, sandbox_ObjectFunction_Touch); + setthink(e, sandbox_ObjectFunction_Think); + e.nextthink = time; + //e.effects |= EF_SELECTABLE; // don't do this all the time, maybe just when editing objects? + + if(!database) + { + // set the object's owner via player UID + // if the player does not have an UID, the owner cannot be stored and his objects may be edited by anyone + if(this.crypto_idfp != "") + e.crypto_idfp = strzone(this.crypto_idfp); + else + print_to(this, "^1SANDBOX - WARNING: ^7You spawned an object, but lack a player UID. ^1Your objects are not secured and can be edited by any player!"); + + // set public object information + e.netname = strzone(this.netname); // name of the owner + e.message = strzone(strftime(true, "%d-%m-%Y %H:%M:%S")); // creation time + e.message2 = strzone(strftime(true, "%d-%m-%Y %H:%M:%S")); // last editing time + + // set origin and direction based on player position and view angle + makevectors(this.v_angle); + WarpZone_TraceLine(this.origin + this.view_ofs, this.origin + this.view_ofs + v_forward * autocvar_g_sandbox_editor_distance_spawn, MOVE_NORMAL, this); + setorigin(e, trace_endpos); + e.angles_y = this.v_angle.y; + } + + CSQCMODEL_AUTOINIT(e); + + object_count += 1; + return e; +} + +void sandbox_ObjectRemove(entity e) +{ + sandbox_ObjectAttach_Remove(e); // detach child objects + + // if the object being removed has been selected for attachment by a player, unset it + FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it) && it.object_attach == e, LAMBDA(it.object_attach = NULL)); + + if(e.material) { strunzone(e.material); e.material = string_null; } + if(e.crypto_idfp) { strunzone(e.crypto_idfp); e.crypto_idfp = string_null; } + if(e.netname) { strunzone(e.netname); e.netname = string_null; } + if(e.message) { strunzone(e.message); e.message = string_null; } + if(e.message2) { strunzone(e.message2); e.message2 = string_null; } + delete(e); + e = NULL; + + object_count -= 1; +} + +string port_string[MAX_STORAGE_ATTACHMENTS]; // fteqcc crashes if this isn't defined as a global + +string sandbox_ObjectPort_Save(entity e, float database) +{ + // save object properties, and return them as a string + float i = 0; + string s; + entity head; + + for(head = NULL; (head = find(head, classname, "object")); ) + { + // the main object needs to be first in the array [0] with attached objects following + float slot, physics, solidity; + if(head == e) // this is the main object, place it first + { + slot = 0; + solidity = head.solid; // applied solidity is normal solidity for children + physics = head.move_movetype; // applied physics are normal physics for parents + } + else if(head.owner == e) // child object, list them in order + { + i += 1; // children start from 1 + slot = i; + solidity = head.old_solid; // persisted solidity is normal solidity for children + physics = head.old_movetype; // persisted physics are normal physics for children + gettaginfo(head.owner, head.tag_index); // get the name of the tag our object is attached to, used further below + } + else + continue; + + // ---------------- OBJECT PROPERTY STORAGE: SAVE ---------------- + if(slot) + { + // properties stored only for child objects + if(gettaginfo_name) port_string[slot] = strcat(port_string[slot], "\"", gettaginfo_name, "\" "); else port_string[slot] = strcat(port_string[slot], "\"\" "); // none + } + else + { + // properties stored only for parent objects + if(database) + { + port_string[slot] = strcat(port_string[slot], sprintf("\"%.9v\"", head.origin), " "); + port_string[slot] = strcat(port_string[slot], sprintf("\"%.9v\"", head.angles), " "); + } + } + // properties stored for all objects + port_string[slot] = strcat(port_string[slot], "\"", head.model, "\" "); + port_string[slot] = strcat(port_string[slot], ftos(head.skin), " "); + port_string[slot] = strcat(port_string[slot], ftos(head.alpha), " "); + port_string[slot] = strcat(port_string[slot], sprintf("\"%.9v\"", head.colormod), " "); + port_string[slot] = strcat(port_string[slot], sprintf("\"%.9v\"", head.glowmod), " "); + port_string[slot] = strcat(port_string[slot], ftos(head.frame), " "); + port_string[slot] = strcat(port_string[slot], ftos(head.scale), " "); + port_string[slot] = strcat(port_string[slot], ftos(solidity), " "); + port_string[slot] = strcat(port_string[slot], ftos(physics), " "); + port_string[slot] = strcat(port_string[slot], ftos(head.damageforcescale), " "); + if(head.material) port_string[slot] = strcat(port_string[slot], "\"", head.material, "\" "); else port_string[slot] = strcat(port_string[slot], "\"\" "); // none + if(database) + { + // properties stored only for the database + if(head.crypto_idfp) port_string[slot] = strcat(port_string[slot], "\"", head.crypto_idfp, "\" "); else port_string[slot] = strcat(port_string[slot], "\"\" "); // none + port_string[slot] = strcat(port_string[slot], "\"", e.netname, "\" "); + port_string[slot] = strcat(port_string[slot], "\"", e.message, "\" "); + port_string[slot] = strcat(port_string[slot], "\"", e.message2, "\" "); + } + } + + // now apply the array to a simple string, with the ; symbol separating objects + s = ""; + for(i = 0; i <= MAX_STORAGE_ATTACHMENTS; ++i) + { + if(port_string[i]) + s = strcat(s, port_string[i], "; "); + port_string[i] = string_null; // fully clear the string + } + + return s; +} + +entity sandbox_ObjectPort_Load(entity this, string s, float database) +{ + // load object properties, and spawn a new object with them + float n, i; + entity e = NULL, parent = NULL; + + // separate objects between the ; symbols + n = tokenizebyseparator(s, "; "); + for(i = 0; i < n; ++i) + port_string[i] = argv(i); + + // now separate and apply the properties of each object + for(i = 0; i < n; ++i) + { + float argv_num; + string tagname = string_null; + argv_num = 0; + tokenize_console(port_string[i]); + e = sandbox_ObjectSpawn(this, database); + + // ---------------- OBJECT PROPERTY STORAGE: LOAD ---------------- + if(i) + { + // properties stored only for child objects + if(argv(argv_num) != "") tagname = argv(argv_num); else tagname = string_null; ++argv_num; + } + else + { + // properties stored only for parent objects + if(database) + { + setorigin(e, stov(argv(argv_num))); ++argv_num; + e.angles = stov(argv(argv_num)); ++argv_num; + } + parent = e; // mark parent objects as such + } + // properties stored for all objects + _setmodel(e, argv(argv_num)); ++argv_num; + e.skin = stof(argv(argv_num)); ++argv_num; + e.alpha = stof(argv(argv_num)); ++argv_num; + e.colormod = stov(argv(argv_num)); ++argv_num; + e.glowmod = stov(argv(argv_num)); ++argv_num; + e.frame = stof(argv(argv_num)); ++argv_num; + sandbox_ObjectEdit_Scale(e, stof(argv(argv_num))); ++argv_num; + e.solid = e.old_solid = stof(argv(argv_num)); ++argv_num; + e.old_movetype = stof(argv(argv_num)); ++argv_num; + set_movetype(e, e.old_movetype); + e.damageforcescale = stof(argv(argv_num)); ++argv_num; + if(e.material) strunzone(e.material); if(argv(argv_num) != "") e.material = strzone(argv(argv_num)); else e.material = string_null; ++argv_num; + if(database) + { + // properties stored only for the database + if(e.crypto_idfp) strunzone(e.crypto_idfp); if(argv(argv_num) != "") e.crypto_idfp = strzone(argv(argv_num)); else e.crypto_idfp = string_null; ++argv_num; + if(e.netname) strunzone(e.netname); e.netname = strzone(argv(argv_num)); ++argv_num; + if(e.message) strunzone(e.message); e.message = strzone(argv(argv_num)); ++argv_num; + if(e.message2) strunzone(e.message2); e.message2 = strzone(argv(argv_num)); ++argv_num; + } + + // attach last + if(i) + sandbox_ObjectAttach_Set(e, parent, tagname); + } + + for(i = 0; i <= MAX_STORAGE_ATTACHMENTS; ++i) + port_string[i] = string_null; // fully clear the string + + return e; +} + +void sandbox_Database_Save() +{ + // saves all objects to the database file + entity head; + string file_name; + float file_get; + + file_name = strcat("sandbox/storage_", autocvar_g_sandbox_storage_name, "_", GetMapname(), ".txt"); + file_get = fopen(file_name, FILE_WRITE); + fputs(file_get, strcat("// sandbox storage \"", autocvar_g_sandbox_storage_name, "\" for map \"", GetMapname(), "\" last updated ", strftime(true, "%d-%m-%Y %H:%M:%S"))); + fputs(file_get, strcat(" containing ", ftos(object_count), " objects\n")); + + for(head = NULL; (head = find(head, classname, "object")); ) + { + // attached objects are persisted separately, ignore them here + if(head.owner != NULL) + continue; + + // use a line of text for each object, listing all properties + fputs(file_get, strcat(sandbox_ObjectPort_Save(head, true), "\n")); + } + fclose(file_get); +} + +void sandbox_Database_Load() +{ + // loads all objects from the database file + string file_read, file_name; + float file_get, i; + + file_name = strcat("sandbox/storage_", autocvar_g_sandbox_storage_name, "_", GetMapname(), ".txt"); + file_get = fopen(file_name, FILE_READ); + if(file_get < 0) + { + if(autocvar_g_sandbox_info > 0) + LOG_INFO(strcat("^3SANDBOX - SERVER: ^7could not find storage file ^3", file_name, "^7, no objects 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 = sandbox_ObjectPort_Load(NULL, file_read, true); + + if(e.material) + { + // since objects are being loaded for the first time, precache material sounds for each + for (i = 1; i <= 5; i++) // 5 sounds in total + precache_sound(strcat("object/impact_", e.material, "_", ftos(i), ".wav")); + } + } + if(autocvar_g_sandbox_info > 0) + LOG_INFO(strcat("^3SANDBOX - SERVER: ^7successfully loaded storage file ^3", file_name, "\n")); + } + fclose(file_get); +} + +MUTATOR_HOOKFUNCTION(sandbox, SV_ParseClientCommand) +{ + if(MUTATOR_RETURNVALUE) // command was already handled? + return; + + entity player = M_ARGV(0, entity); + string cmd_name = M_ARGV(1, string); + int cmd_argc = M_ARGV(2, int); + + if(cmd_name == "g_sandbox") + { + if(autocvar_g_sandbox_readonly) + { + print_to(player, "^2SANDBOX - INFO: ^7Sandbox mode is active, but in read-only mode. Sandbox commands cannot be used"); + return true; + } + if(cmd_argc < 2) + { + print_to(player, "^2SANDBOX - INFO: ^7Sandbox mode is active. For usage information, type 'sandbox help'"); + return true; + } + + switch(argv(1)) + { + entity e; + int j; + string s; + + // ---------------- COMMAND: HELP ---------------- + case "help": + print_to(player, "You can use the following sandbox commands:"); + print_to(player, "^7\"^2object_spawn ^3models/foo/bar.md3^7\" spawns a new object in front of the player, and gives it the specified model"); + print_to(player, "^7\"^2object_remove^7\" removes the object the player is looking at. Players can only remove their own objects"); + print_to(player, "^7\"^2object_duplicate ^3value^7\" duplicates the object, if the player has copying rights over the original"); + print_to(player, "^3copy value ^7- copies the properties of the object to the specified client cvar"); + print_to(player, "^3paste value ^7- spawns an object with the given properties. Properties or cvars must be specified as follows; eg1: \"0 1 2 ...\", eg2: \"$cl_cvar\""); + print_to(player, "^7\"^2object_attach ^3property value^7\" attaches one object to another. Players can only attach their own objects"); + print_to(player, "^3get ^7- selects the object you are facing as the object to be attached"); + print_to(player, "^3set value ^7- attaches the previously selected object to the object you are facing, on the specified bone"); + print_to(player, "^3remove ^7- detaches all objects from the object you are facing"); + print_to(player, "^7\"^2object_edit ^3property value^7\" edits the given property of the object. Players can only edit their own objects"); + print_to(player, "^3skin value ^7- changes the skin of the object"); + print_to(player, "^3alpha value ^7- sets object transparency"); + print_to(player, "^3colormod \"value_x value_y value_z\" ^7- main object color"); + print_to(player, "^3glowmod \"value_x value_y value_z\" ^7- glow object color"); + print_to(player, "^3frame value ^7- object animation frame, for self-animated models"); + print_to(player, "^3scale value ^7- changes object scale. 0.5 is half size and 2 is double size"); + print_to(player, "^3solidity value ^7- object collisions, 0 = non-solid, 1 = solid"); + print_to(player, "^3physics value ^7- object physics, 0 = static, 1 = movable, 2 = physical"); + print_to(player, "^3force value ^7- amount of force applied to objects that are shot"); + print_to(player, "^3material value ^7- sets the material of the object. Default materials are: metal, stone, wood, flesh"); + print_to(player, "^7\"^2object_claim^7\" sets the player as the owner of the object, if he has the right to edit it"); + print_to(player, "^7\"^2object_info ^3value^7\" shows public information about the object"); + print_to(player, "^3object ^7- prints general information about the object, such as owner and creation / editing date"); + print_to(player, "^3mesh ^7- prints information about the object's mesh, including skeletal bones"); + print_to(player, "^3attachments ^7- prints information about the object's attachments"); + print_to(player, "^7The ^1drag object ^7key can be used to grab and carry objects. Players can only grab their own objects"); + return true; + + // ---------------- COMMAND: OBJECT, SPAWN ---------------- + case "object_spawn": + if(time < player.object_flood) + { + print_to(player, strcat("^1SANDBOX - WARNING: ^7Flood protection active. Please wait ^3", ftos(player.object_flood - time), " ^7seconds beofore spawning another object")); + return true; + } + player.object_flood = time + autocvar_g_sandbox_editor_flood; + if(object_count >= autocvar_g_sandbox_editor_maxobjects) + { + print_to(player, strcat("^1SANDBOX - WARNING: ^7Cannot spawn any more objects. Up to ^3", ftos(autocvar_g_sandbox_editor_maxobjects), " ^7objects may exist at a time")); + return true; + } + if(cmd_argc < 3) + { + print_to(player, "^1SANDBOX - WARNING: ^7Attempted to spawn an object without specifying a model. Please specify the path to your model file after the 'object_spawn' command"); + return true; + } + if (!(fexists(argv(2)))) + { + print_to(player, "^1SANDBOX - WARNING: ^7Attempted to spawn an object with a non-existent model. Make sure the path to your model file is correct"); + return true; + } + + e = sandbox_ObjectSpawn(player, false); + _setmodel(e, argv(2)); + + if(autocvar_g_sandbox_info > 0) + LOG_INFO(strcat("^3SANDBOX - SERVER: ^7", player.netname, " spawned an object at origin ^3", vtos(e.origin), "\n")); + return true; + + // ---------------- COMMAND: OBJECT, REMOVE ---------------- + case "object_remove": + e = sandbox_ObjectEdit_Get(player, true); + if(e != NULL) + { + if(autocvar_g_sandbox_info > 0) + LOG_INFO(strcat("^3SANDBOX - SERVER: ^7", player.netname, " removed an object at origin ^3", vtos(e.origin), "\n")); + sandbox_ObjectRemove(e); + return true; + } + + print_to(player, "^1SANDBOX - WARNING: ^7Object could not be removed. Make sure you are facing an object that you have edit rights over"); + return true; + + // ---------------- COMMAND: OBJECT, DUPLICATE ---------------- + case "object_duplicate": + switch(argv(2)) + { + case "copy": + // copies customizable properties of the selected object to the clipboard cvar + e = sandbox_ObjectEdit_Get(player, autocvar_g_sandbox_editor_free); // can we copy objects we can't edit? + if(e != NULL) + { + s = sandbox_ObjectPort_Save(e, false); + s = strreplace("\"", "\\\"", s); + stuffcmd(player, strcat("set ", argv(3), " \"", s, "\"")); + + print_to(player, "^2SANDBOX - INFO: ^7Object copied to clipboard"); + return true; + } + print_to(player, "^1SANDBOX - WARNING: ^7Object could not be copied. Make sure you are facing an object that you have copy rights over"); + return true; + + case "paste": + // spawns a new object using the properties in the player's clipboard cvar + if(time < player.object_flood) + { + print_to(player, strcat("^1SANDBOX - WARNING: ^7Flood protection active. Please wait ^3", ftos(player.object_flood - time), " ^7seconds beofore spawning another object")); + return true; + } + player.object_flood = time + autocvar_g_sandbox_editor_flood; + if(argv(3) == "") // no object in clipboard + { + print_to(player, "^1SANDBOX - WARNING: ^7No object in clipboard. You must copy an object before you can paste it"); + return true; + } + if(object_count >= autocvar_g_sandbox_editor_maxobjects) + { + print_to(player, strcat("^1SANDBOX - WARNING: ^7Cannot spawn any more objects. Up to ^3", ftos(autocvar_g_sandbox_editor_maxobjects), " ^7objects may exist at a time")); + return true; + } + e = sandbox_ObjectPort_Load(player, argv(3), false); + + print_to(player, "^2SANDBOX - INFO: ^7Object pasted successfully"); + if(autocvar_g_sandbox_info > 0) + LOG_INFO(strcat("^3SANDBOX - SERVER: ^7", player.netname, " pasted an object at origin ^3", vtos(e.origin), "\n")); + return true; + } + return true; + + // ---------------- COMMAND: OBJECT, ATTACH ---------------- + case "object_attach": + switch(argv(2)) + { + case "get": + // select e as the object as meant to be attached + e = sandbox_ObjectEdit_Get(player, true); + if(e != NULL) + { + player.object_attach = e; + print_to(player, "^2SANDBOX - INFO: ^7Object selected for attachment"); + return true; + } + print_to(player, "^1SANDBOX - WARNING: ^7Object could not be selected for attachment. Make sure you are facing an object that you have edit rights over"); + return true; + case "set": + if(player.object_attach == NULL) + { + print_to(player, "^1SANDBOX - WARNING: ^7No object selected for attachment. Please select an object to be attached first."); + return true; + } + + // attaches the previously selected object to e + e = sandbox_ObjectEdit_Get(player, true); + if(e != NULL) + { + sandbox_ObjectAttach_Set(player.object_attach, e, argv(3)); + player.object_attach = NULL; // object was attached, no longer keep it scheduled for attachment + print_to(player, "^2SANDBOX - INFO: ^7Object attached successfully"); + if(autocvar_g_sandbox_info > 1) + LOG_INFO(strcat("^3SANDBOX - SERVER: ^7", player.netname, " attached objects at origin ^3", vtos(e.origin), "\n")); + return true; + } + print_to(player, "^1SANDBOX - WARNING: ^7Object could not be attached to the parent. Make sure you are facing an object that you have edit rights over"); + return true; + case "remove": + // removes e if it was attached + e = sandbox_ObjectEdit_Get(player, true); + if(e != NULL) + { + sandbox_ObjectAttach_Remove(e); + print_to(player, "^2SANDBOX - INFO: ^7Child objects detached successfully"); + if(autocvar_g_sandbox_info > 1) + LOG_INFO(strcat("^3SANDBOX - SERVER: ^7", player.netname, " detached objects at origin ^3", vtos(e.origin), "\n")); + return true; + } + print_to(player, "^1SANDBOX - WARNING: ^7Child objects could not be detached. Make sure you are facing an object that you have edit rights over"); + return true; + } + return true; + + // ---------------- COMMAND: OBJECT, EDIT ---------------- + case "object_edit": + if(argv(2) == "") + { + print_to(player, "^1SANDBOX - WARNING: ^7Too few parameters. You must specify a property to edit"); + return true; + } + + e = sandbox_ObjectEdit_Get(player, true); + if(e != NULL) + { + switch(argv(2)) + { + case "skin": + e.skin = stof(argv(3)); + break; + case "alpha": + e.alpha = stof(argv(3)); + break; + case "color_main": + e.colormod = stov(argv(3)); + break; + case "color_glow": + e.glowmod = stov(argv(3)); + break; + case "frame": + e.frame = stof(argv(3)); + break; + case "scale": + sandbox_ObjectEdit_Scale(e, stof(argv(3))); + break; + case "solidity": + switch(argv(3)) + { + case "0": // non-solid + e.solid = SOLID_TRIGGER; + break; + case "1": // solid + e.solid = SOLID_BBOX; + break; + default: + break; + } + case "physics": + switch(argv(3)) + { + case "0": // static + set_movetype(e, MOVETYPE_NONE); + break; + case "1": // movable + set_movetype(e, MOVETYPE_TOSS); + break; + case "2": // physical + set_movetype(e, MOVETYPE_PHYSICS); + break; + default: + break; + } + break; + case "force": + e.damageforcescale = stof(argv(3)); + break; + case "material": + if(e.material) strunzone(e.material); + if(argv(3)) + { + for (j = 1; j <= 5; j++) // precache material sounds, 5 in total + precache_sound(strcat("object/impact_", argv(3), "_", ftos(j), ".wav")); + e.material = strzone(argv(3)); + } + else + e.material = string_null; // no material + break; + default: + print_to(player, "^1SANDBOX - WARNING: ^7Invalid object property. For usage information, type 'sandbox help'"); + return true; + } + + // update last editing time + if(e.message2) strunzone(e.message2); + e.message2 = strzone(strftime(true, "%d-%m-%Y %H:%M:%S")); + + if(autocvar_g_sandbox_info > 1) + LOG_INFO(strcat("^3SANDBOX - SERVER: ^7", player.netname, " edited property ^3", argv(2), " ^7of an object at origin ^3", vtos(e.origin), "\n")); + return true; + } + + print_to(player, "^1SANDBOX - WARNING: ^7Object could not be edited. Make sure you are facing an object that you have edit rights over"); + return true; + + // ---------------- COMMAND: OBJECT, CLAIM ---------------- + case "object_claim": + // if the player can edit an object but is not its owner, this can be used to claim that object + if(player.crypto_idfp == "") + { + print_to(player, "^1SANDBOX - WARNING: ^7You do not have a player UID, and cannot claim objects"); + return true; + } + e = sandbox_ObjectEdit_Get(player, true); + if(e != NULL) + { + // update the owner's name + // Do this before checking if you're already the owner and skipping if such, so we + // also update the player's nickname if he changed it (but has the same player UID) + if(e.netname != player.netname) + { + if(e.netname) strunzone(e.netname); + e.netname = strzone(player.netname); + print_to(player, "^2SANDBOX - INFO: ^7Object owner name updated"); + } + + if(e.crypto_idfp == player.crypto_idfp) + { + print_to(player, "^2SANDBOX - INFO: ^7Object is already yours, nothing to claim"); + return true; + } + + if(e.crypto_idfp) strunzone(e.crypto_idfp); + e.crypto_idfp = strzone(player.crypto_idfp); + + print_to(player, "^2SANDBOX - INFO: ^7Object claimed successfully"); + } + print_to(player, "^1SANDBOX - WARNING: ^7Object could not be claimed. Make sure you are facing an object that you have edit rights over"); + return true; + + // ---------------- COMMAND: OBJECT, INFO ---------------- + case "object_info": + // prints public information about the object to the player + e = sandbox_ObjectEdit_Get(player, false); + if(e != NULL) + { + switch(argv(2)) + { + case "object": + print_to(player, strcat("^2SANDBOX - INFO: ^7Object is owned by \"^7", e.netname, "^7\", created \"^3", e.message, "^7\", last edited \"^3", e.message2, "^7\"")); + return true; + case "mesh": + s = ""; + FOR_EACH_TAG(e) + s = strcat(s, "^7\"^5", gettaginfo_name, "^7\", "); + print_to(player, strcat("^2SANDBOX - INFO: ^7Object mesh is \"^3", e.model, "^7\" at animation frame ^3", ftos(e.frame), " ^7containing the following tags: ", s)); + return true; + case "attachments": + // this should show the same info as 'mesh' but for attachments + s = ""; + j = 0; + FOREACH_ENTITY_ENT(owner, e, + { + if(it.classname != "object") continue; + + ++j; // start from 1 + gettaginfo(e, it.tag_index); + s = strcat(s, "^1attachment ", ftos(j), "^7 has mesh \"^3", it.model, "^7\" at animation frame ^3", ftos(it.frame)); + s = strcat(s, "^7 and is attached to bone \"^5", gettaginfo_name, "^7\", "); + }); + if(j) // object contains attachments + print_to(player, strcat("^2SANDBOX - INFO: ^7Object contains the following ^1", ftos(j), "^7 attachment(s): ", s)); + else + print_to(player, "^2SANDBOX - INFO: ^7Object contains no attachments"); + return true; + } + } + print_to(player, "^1SANDBOX - WARNING: ^7No information could be found. Make sure you are facing an object"); + return true; + + // ---------------- COMMAND: DEFAULT ---------------- + default: + print_to(player, "Invalid command. For usage information, type 'sandbox help'"); + return true; + } + } +} + +MUTATOR_HOOKFUNCTION(sandbox, SV_StartFrame) +{ + if(!autocvar_g_sandbox_storage_autosave) + return; + if(time < autosave_time) + return; + autosave_time = time + autocvar_g_sandbox_storage_autosave; + + sandbox_Database_Save(); + + return true; +} diff --git a/qcsrc/common/mutators/mutator/sandbox/sv_sandbox.qh b/qcsrc/common/mutators/mutator/sandbox/sv_sandbox.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/mutators/mutator/sandbox/sv_sandbox.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mutators/mutator/spawn_near_teammate/_mod.inc b/qcsrc/common/mutators/mutator/spawn_near_teammate/_mod.inc index b7d3af7f4d..fe3a6ebc5d 100644 --- a/qcsrc/common/mutators/mutator/spawn_near_teammate/_mod.inc +++ b/qcsrc/common/mutators/mutator/spawn_near_teammate/_mod.inc @@ -1,2 +1,4 @@ // generated file; do not modify -#include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/mutators/mutator/spawn_near_teammate/_mod.qh b/qcsrc/common/mutators/mutator/spawn_near_teammate/_mod.qh index 5f53e95c88..b34f8f8f16 100644 --- a/qcsrc/common/mutators/mutator/spawn_near_teammate/_mod.qh +++ b/qcsrc/common/mutators/mutator/spawn_near_teammate/_mod.qh @@ -1,2 +1,4 @@ // generated file; do not modify -#include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/mutators/mutator/spawn_near_teammate/module.inc b/qcsrc/common/mutators/mutator/spawn_near_teammate/module.inc deleted file mode 100644 index f88a768a21..0000000000 --- a/qcsrc/common/mutators/mutator/spawn_near_teammate/module.inc +++ /dev/null @@ -1,3 +0,0 @@ -#ifdef SVQC -#include "spawn_near_teammate.qc" -#endif diff --git a/qcsrc/common/mutators/mutator/spawn_near_teammate/spawn_near_teammate.qc b/qcsrc/common/mutators/mutator/spawn_near_teammate/spawn_near_teammate.qc deleted file mode 100644 index b16a6c9acf..0000000000 --- a/qcsrc/common/mutators/mutator/spawn_near_teammate/spawn_near_teammate.qc +++ /dev/null @@ -1,189 +0,0 @@ -#ifdef IMPLEMENTATION - -float autocvar_g_spawn_near_teammate_distance; -int autocvar_g_spawn_near_teammate_ignore_spawnpoint; -float autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay; -float autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay_death; -int autocvar_g_spawn_near_teammate_ignore_spawnpoint_check_health; -bool autocvar_g_spawn_near_teammate_ignore_spawnpoint_closetodeath; - -REGISTER_MUTATOR(spawn_near_teammate, cvar("g_spawn_near_teammate")); - -.entity msnt_lookat; - -.float msnt_timer; -.vector msnt_deathloc; - -.float cvar_cl_spawn_near_teammate; - -MUTATOR_HOOKFUNCTION(spawn_near_teammate, Spawn_Score) -{ - entity player = M_ARGV(0, entity); - entity spawn_spot = M_ARGV(1, entity); - vector spawn_score = M_ARGV(2, vector); - - if(autocvar_g_spawn_near_teammate_ignore_spawnpoint == 1 || (autocvar_g_spawn_near_teammate_ignore_spawnpoint == 2 && player.cvar_cl_spawn_near_teammate)) - return; - - spawn_spot.msnt_lookat = NULL; - - if(!teamplay) - return; - - RandomSelection_Init(); - FOREACH_CLIENT(IS_PLAYER(it) && it != player && SAME_TEAM(it, player) && !IS_DEAD(it), LAMBDA( - if(vdist(spawn_spot.origin - it.origin, >, autocvar_g_spawn_near_teammate_distance)) - continue; - if(vdist(spawn_spot.origin - it.origin, <, 48)) - continue; - if(!checkpvs(spawn_spot.origin, it)) - continue; - RandomSelection_Add(it, 0, string_null, 1, 1); - )); - - if(RandomSelection_chosen_ent) - { - spawn_spot.msnt_lookat = RandomSelection_chosen_ent; - spawn_score.x += SPAWN_PRIO_NEAR_TEAMMATE_FOUND; - } - else if(player.team == spawn_spot.team) - spawn_score.x += SPAWN_PRIO_NEAR_TEAMMATE_SAMETEAM; // prefer same team, if we can't find a spawn near teammate - - M_ARGV(2, vector) = spawn_score; -} - -MUTATOR_HOOKFUNCTION(spawn_near_teammate, PlayerSpawn) -{ - if(!teamplay) { return; } - entity player = M_ARGV(0, entity); - entity spawn_spot = M_ARGV(1, entity); - - int num_red = 0, num_blue = 0, num_yellow = 0, num_pink = 0; - FOREACH_CLIENT(IS_PLAYER(it), - { - switch(it.team) - { - case NUM_TEAM_1: ++num_red; break; - case NUM_TEAM_2: ++num_blue; break; - case NUM_TEAM_3: ++num_yellow; break; - case NUM_TEAM_4: ++num_pink; break; - } - }); - - if(num_red == 1 || num_blue == 1 || num_yellow == 1 || num_pink == 1) - return; // at least 1 team has only 1 player, let's not give the bigger team too much of an advantage! - - // Note: when entering this, fixangle is already set. - if(autocvar_g_spawn_near_teammate_ignore_spawnpoint == 1 || (autocvar_g_spawn_near_teammate_ignore_spawnpoint == 2 && player.cvar_cl_spawn_near_teammate)) - { - if(autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay_death) - player.msnt_timer = time + autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay_death; - - entity best_mate = NULL; - vector best_spot = '0 0 0'; - float pc = 0, best_dist = 0, dist = 0; - FOREACH_CLIENT(IS_PLAYER(it), LAMBDA( - if((autocvar_g_spawn_near_teammate_ignore_spawnpoint_check_health >= 0 && it.health >= autocvar_g_balance_health_regenstable) || autocvar_g_spawn_near_teammate_ignore_spawnpoint_check_health == 0) - if(!IS_DEAD(it)) - if(it.msnt_timer < time) - if(SAME_TEAM(player, it)) - if(time > it.spawnshieldtime) // spawn shielding - if(STAT(FROZEN, it) == 0) - if(it != player) - { - tracebox(it.origin, STAT(PL_MIN, NULL), STAT(PL_MAX, NULL), it.origin - '0 0 100', MOVE_NOMONSTERS, it); - if(trace_fraction != 1.0) - if(!(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY)) - { - pc = pointcontents(trace_endpos + '0 0 1'); - if(pc == CONTENT_EMPTY) - { - if(vdist(it.velocity, >, 5)) - fixedmakevectors(vectoangles(it.velocity)); - else - fixedmakevectors(it.angles); - - for(pc = 0; pc < 4; ++pc) // test 4 diffrent spots close to mate - { - switch(pc) - { - case 0: - tracebox(it.origin , STAT(PL_MIN, NULL), STAT(PL_MAX, NULL), it.origin + v_right * 128, MOVE_NOMONSTERS, it); - break; - case 1: - tracebox(it.origin , STAT(PL_MIN, NULL), STAT(PL_MAX, NULL), it.origin - v_right * 128 , MOVE_NOMONSTERS, it); - break; - case 2: - tracebox(it.origin , STAT(PL_MIN, NULL), STAT(PL_MAX, NULL), it.origin + v_right * 128 - v_forward * 64, MOVE_NOMONSTERS, it); - break; - case 3: - tracebox(it.origin , STAT(PL_MIN, NULL), STAT(PL_MAX, NULL), it.origin - v_right * 128 - v_forward * 64, MOVE_NOMONSTERS, it); - break; - //case 4: - //tracebox(it.origin , STAT(PL_MIN, NULL), STAT(PL_MAX, NULL), it.origin - v_forward * 128, MOVE_NOMONSTERS, it); - //break; - } - - if(trace_fraction == 1.0) - { - traceline(trace_endpos + '0 0 4', trace_endpos - '0 0 100', MOVE_NOMONSTERS, it); - if(trace_fraction != 1.0) - { - if(autocvar_g_spawn_near_teammate_ignore_spawnpoint_closetodeath) - { - dist = vlen(trace_endpos - player.msnt_deathloc); - if(dist < best_dist || best_dist == 0) - { - best_dist = dist; - best_spot = trace_endpos; - best_mate = it; - } - } - else - { - setorigin(player, trace_endpos); - player.angles = it.angles; - player.angles_z = 0; // never spawn tilted even if the spot says to - it.msnt_timer = time + autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay; - return; - } - } - } - } - } - } - } - )); - - if(autocvar_g_spawn_near_teammate_ignore_spawnpoint_closetodeath) - if(best_dist) - { - setorigin(player, best_spot); - player.angles = best_mate.angles; - player.angles_z = 0; // never spawn tilted even if the spot says to - best_mate.msnt_timer = time + autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay; - } - } - else if(spawn_spot.msnt_lookat) - { - player.angles = vectoangles(spawn_spot.msnt_lookat.origin - player.origin); - player.angles_x = -player.angles.x; - player.angles_z = 0; // never spawn tilted even if the spot says to - /* - sprint(player, "You should be looking at ", spawn_spot.msnt_lookat.netname, "^7.\n"); - sprint(player, "distance: ", vtos(spawn_spot.msnt_lookat.origin - player.origin), "\n"); - sprint(player, "angles: ", vtos(player.angles), "\n"); - */ - } -} - -MUTATOR_HOOKFUNCTION(spawn_near_teammate, PlayerDies) -{ - entity frag_target = M_ARGV(0, entity); - - frag_target.msnt_deathloc = frag_target.origin; -} - -REPLICATE(cvar_cl_spawn_near_teammate, bool, "cl_spawn_near_teammate"); - -#endif diff --git a/qcsrc/common/mutators/mutator/spawn_near_teammate/sv_spawn_near_teammate.qc b/qcsrc/common/mutators/mutator/spawn_near_teammate/sv_spawn_near_teammate.qc new file mode 100644 index 0000000000..4c57d01895 --- /dev/null +++ b/qcsrc/common/mutators/mutator/spawn_near_teammate/sv_spawn_near_teammate.qc @@ -0,0 +1,187 @@ +#include "sv_spawn_near_teammate.qh" + +float autocvar_g_spawn_near_teammate_distance; +int autocvar_g_spawn_near_teammate_ignore_spawnpoint; +float autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay; +float autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay_death; +int autocvar_g_spawn_near_teammate_ignore_spawnpoint_check_health; +bool autocvar_g_spawn_near_teammate_ignore_spawnpoint_closetodeath; + +REGISTER_MUTATOR(spawn_near_teammate, cvar("g_spawn_near_teammate")); + +.entity msnt_lookat; + +.float msnt_timer; +.vector msnt_deathloc; + +.float cvar_cl_spawn_near_teammate; + +MUTATOR_HOOKFUNCTION(spawn_near_teammate, Spawn_Score) +{ + entity player = M_ARGV(0, entity); + entity spawn_spot = M_ARGV(1, entity); + vector spawn_score = M_ARGV(2, vector); + + if(autocvar_g_spawn_near_teammate_ignore_spawnpoint == 1 || (autocvar_g_spawn_near_teammate_ignore_spawnpoint == 2 && player.cvar_cl_spawn_near_teammate)) + return; + + spawn_spot.msnt_lookat = NULL; + + if(!teamplay) + return; + + RandomSelection_Init(); + FOREACH_CLIENT(IS_PLAYER(it) && it != player && SAME_TEAM(it, player) && !IS_DEAD(it), LAMBDA( + if(vdist(spawn_spot.origin - it.origin, >, autocvar_g_spawn_near_teammate_distance)) + continue; + if(vdist(spawn_spot.origin - it.origin, <, 48)) + continue; + if(!checkpvs(spawn_spot.origin, it)) + continue; + RandomSelection_Add(it, 0, string_null, 1, 1); + )); + + if(RandomSelection_chosen_ent) + { + spawn_spot.msnt_lookat = RandomSelection_chosen_ent; + spawn_score.x += SPAWN_PRIO_NEAR_TEAMMATE_FOUND; + } + else if(player.team == spawn_spot.team) + spawn_score.x += SPAWN_PRIO_NEAR_TEAMMATE_SAMETEAM; // prefer same team, if we can't find a spawn near teammate + + M_ARGV(2, vector) = spawn_score; +} + +MUTATOR_HOOKFUNCTION(spawn_near_teammate, PlayerSpawn) +{ + if(!teamplay) { return; } + entity player = M_ARGV(0, entity); + entity spawn_spot = M_ARGV(1, entity); + + int num_red = 0, num_blue = 0, num_yellow = 0, num_pink = 0; + FOREACH_CLIENT(IS_PLAYER(it), + { + switch(it.team) + { + case NUM_TEAM_1: ++num_red; break; + case NUM_TEAM_2: ++num_blue; break; + case NUM_TEAM_3: ++num_yellow; break; + case NUM_TEAM_4: ++num_pink; break; + } + }); + + if(num_red == 1 || num_blue == 1 || num_yellow == 1 || num_pink == 1) + return; // at least 1 team has only 1 player, let's not give the bigger team too much of an advantage! + + // Note: when entering this, fixangle is already set. + if(autocvar_g_spawn_near_teammate_ignore_spawnpoint == 1 || (autocvar_g_spawn_near_teammate_ignore_spawnpoint == 2 && player.cvar_cl_spawn_near_teammate)) + { + if(autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay_death) + player.msnt_timer = time + autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay_death; + + entity best_mate = NULL; + vector best_spot = '0 0 0'; + float pc = 0, best_dist = 0, dist = 0; + FOREACH_CLIENT(IS_PLAYER(it), LAMBDA( + if((autocvar_g_spawn_near_teammate_ignore_spawnpoint_check_health >= 0 && it.health >= autocvar_g_balance_health_regenstable) || autocvar_g_spawn_near_teammate_ignore_spawnpoint_check_health == 0) + if(!IS_DEAD(it)) + if(it.msnt_timer < time) + if(SAME_TEAM(player, it)) + if(time > it.spawnshieldtime) // spawn shielding + if(STAT(FROZEN, it) == 0) + if(it != player) + { + tracebox(it.origin, STAT(PL_MIN, NULL), STAT(PL_MAX, NULL), it.origin - '0 0 100', MOVE_NOMONSTERS, it); + if(trace_fraction != 1.0) + if(!(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY)) + { + pc = pointcontents(trace_endpos + '0 0 1'); + if(pc == CONTENT_EMPTY) + { + if(vdist(it.velocity, >, 5)) + fixedmakevectors(vectoangles(it.velocity)); + else + fixedmakevectors(it.angles); + + for(pc = 0; pc < 4; ++pc) // test 4 diffrent spots close to mate + { + switch(pc) + { + case 0: + tracebox(it.origin , STAT(PL_MIN, NULL), STAT(PL_MAX, NULL), it.origin + v_right * 128, MOVE_NOMONSTERS, it); + break; + case 1: + tracebox(it.origin , STAT(PL_MIN, NULL), STAT(PL_MAX, NULL), it.origin - v_right * 128 , MOVE_NOMONSTERS, it); + break; + case 2: + tracebox(it.origin , STAT(PL_MIN, NULL), STAT(PL_MAX, NULL), it.origin + v_right * 128 - v_forward * 64, MOVE_NOMONSTERS, it); + break; + case 3: + tracebox(it.origin , STAT(PL_MIN, NULL), STAT(PL_MAX, NULL), it.origin - v_right * 128 - v_forward * 64, MOVE_NOMONSTERS, it); + break; + //case 4: + //tracebox(it.origin , STAT(PL_MIN, NULL), STAT(PL_MAX, NULL), it.origin - v_forward * 128, MOVE_NOMONSTERS, it); + //break; + } + + if(trace_fraction == 1.0) + { + traceline(trace_endpos + '0 0 4', trace_endpos - '0 0 100', MOVE_NOMONSTERS, it); + if(trace_fraction != 1.0) + { + if(autocvar_g_spawn_near_teammate_ignore_spawnpoint_closetodeath) + { + dist = vlen(trace_endpos - player.msnt_deathloc); + if(dist < best_dist || best_dist == 0) + { + best_dist = dist; + best_spot = trace_endpos; + best_mate = it; + } + } + else + { + setorigin(player, trace_endpos); + player.angles = it.angles; + player.angles_z = 0; // never spawn tilted even if the spot says to + it.msnt_timer = time + autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay; + return; + } + } + } + } + } + } + } + )); + + if(autocvar_g_spawn_near_teammate_ignore_spawnpoint_closetodeath) + if(best_dist) + { + setorigin(player, best_spot); + player.angles = best_mate.angles; + player.angles_z = 0; // never spawn tilted even if the spot says to + best_mate.msnt_timer = time + autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay; + } + } + else if(spawn_spot.msnt_lookat) + { + player.angles = vectoangles(spawn_spot.msnt_lookat.origin - player.origin); + player.angles_x = -player.angles.x; + player.angles_z = 0; // never spawn tilted even if the spot says to + /* + sprint(player, "You should be looking at ", spawn_spot.msnt_lookat.netname, "^7.\n"); + sprint(player, "distance: ", vtos(spawn_spot.msnt_lookat.origin - player.origin), "\n"); + sprint(player, "angles: ", vtos(player.angles), "\n"); + */ + } +} + +MUTATOR_HOOKFUNCTION(spawn_near_teammate, PlayerDies) +{ + entity frag_target = M_ARGV(0, entity); + + frag_target.msnt_deathloc = frag_target.origin; +} + +REPLICATE(cvar_cl_spawn_near_teammate, bool, "cl_spawn_near_teammate"); diff --git a/qcsrc/common/mutators/mutator/spawn_near_teammate/sv_spawn_near_teammate.qh b/qcsrc/common/mutators/mutator/spawn_near_teammate/sv_spawn_near_teammate.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/mutators/mutator/spawn_near_teammate/sv_spawn_near_teammate.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mutators/mutator/superspec/_mod.inc b/qcsrc/common/mutators/mutator/superspec/_mod.inc index d5005242f2..262c7fcdbc 100644 --- a/qcsrc/common/mutators/mutator/superspec/_mod.inc +++ b/qcsrc/common/mutators/mutator/superspec/_mod.inc @@ -1,2 +1,4 @@ // generated file; do not modify -#include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/mutators/mutator/superspec/_mod.qh b/qcsrc/common/mutators/mutator/superspec/_mod.qh index b544ffc611..110087b674 100644 --- a/qcsrc/common/mutators/mutator/superspec/_mod.qh +++ b/qcsrc/common/mutators/mutator/superspec/_mod.qh @@ -1,2 +1,4 @@ // generated file; do not modify -#include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/mutators/mutator/superspec/module.inc b/qcsrc/common/mutators/mutator/superspec/module.inc deleted file mode 100644 index 8e0a998c2d..0000000000 --- a/qcsrc/common/mutators/mutator/superspec/module.inc +++ /dev/null @@ -1,3 +0,0 @@ -#ifdef SVQC -#include "superspec.qc" -#endif diff --git a/qcsrc/common/mutators/mutator/superspec/superspec.qc b/qcsrc/common/mutators/mutator/superspec/superspec.qc deleted file mode 100644 index 947ff01bb7..0000000000 --- a/qcsrc/common/mutators/mutator/superspec/superspec.qc +++ /dev/null @@ -1,459 +0,0 @@ -#ifdef IMPLEMENTATION -REGISTER_MUTATOR(superspec, cvar("g_superspectate")); - -#define _SSMAGIX "SUPERSPEC_OPTIONSFILE_V1" -#define _ISLOCAL(ent) ((edict_num(1) == (ent)) ? true : false) - -const float ASF_STRENGTH = BIT(0); -const float ASF_SHIELD = BIT(1); -const float ASF_MEGA_AR = BIT(2); -const float ASF_MEGA_HP = BIT(3); -const float ASF_FLAG_GRAB = BIT(4); -const float ASF_OBSERVER_ONLY = BIT(5); -const float ASF_SHOWWHAT = BIT(6); -const float ASF_SSIM = BIT(7); -const float ASF_FOLLOWKILLER = BIT(8); -const float ASF_ALL = 0xFFFFFF; -.float autospec_flags; - -const float SSF_SILENT = 1; -const float SSF_VERBOSE = 2; -const float SSF_ITEMMSG = 4; -.float superspec_flags; - -.string superspec_itemfilter; //"classname1 classname2 ..." - -bool superspec_Spectate(entity this, entity targ) -{ - if(Spectate(this, targ) == 1) - TRANSMUTE(Spectator, this); - - return true; -} - -void superspec_save_client_conf(entity this) -{ - string fn = "superspec-local.options"; - float fh; - - if (!_ISLOCAL(this)) - { - if(this.crypto_idfp == "") - return; - - fn = sprintf("superspec-%s.options", uri_escape(this.crypto_idfp)); - } - - fh = fopen(fn, FILE_WRITE); - if(fh < 0) - { - LOG_TRACE("^1ERROR: ^7 superspec can not open ", fn, " for writing."); - } - else - { - fputs(fh, _SSMAGIX); - fputs(fh, "\n"); - fputs(fh, ftos(this.autospec_flags)); - fputs(fh, "\n"); - fputs(fh, ftos(this.superspec_flags)); - fputs(fh, "\n"); - fputs(fh, this.superspec_itemfilter); - fputs(fh, "\n"); - fclose(fh); - } -} - -void superspec_msg(string _center_title, string _con_title, entity _to, string _msg, float _spamlevel) -{ - sprint(_to, strcat(_con_title, _msg)); - - if(_to.superspec_flags & SSF_SILENT) - return; - - if(_spamlevel > 1) - if (!(_to.superspec_flags & SSF_VERBOSE)) - return; - - centerprint(_to, strcat(_center_title, _msg)); -} - -float superspec_filteritem(entity _for, entity _item) -{ - float i; - - if(_for.superspec_itemfilter == "") - return true; - - if(_for.superspec_itemfilter == "") - return true; - - float l = tokenize_console(_for.superspec_itemfilter); - for(i = 0; i < l; ++i) - { - if(argv(i) == _item.classname) - return true; - } - - return false; -} - -MUTATOR_HOOKFUNCTION(superspec, ItemTouch) -{ - entity item = M_ARGV(0, entity); - entity toucher = M_ARGV(1, entity); - - FOREACH_CLIENT(true, LAMBDA( - if(!IS_SPEC(it) && !IS_OBSERVER(it)) - continue; - if(it.superspec_flags & SSF_ITEMMSG) - if(superspec_filteritem(it, item)) - { - if(it.superspec_flags & SSF_VERBOSE) - superspec_msg("", "", it, sprintf("Player %s^7 just picked up ^3%s\n", toucher.netname, item.netname), 1); - else - superspec_msg("", "", it, sprintf("Player %s^7 just picked up ^3%s\n^8(%s^8)\n", toucher.netname, item.netname, item.classname), 1); - if((it.autospec_flags & ASF_SSIM) && it.enemy != toucher) - { - superspec_Spectate(it, toucher); - return MUT_ITEMTOUCH_CONTINUE; - } - } - - if((it.autospec_flags & ASF_SHIELD && item.invincible_finished) || - (it.autospec_flags & ASF_STRENGTH && item.strength_finished) || - (it.autospec_flags & ASF_MEGA_AR && item.itemdef == ITEM_ArmorMega) || - (it.autospec_flags & ASF_MEGA_HP && item.itemdef == ITEM_HealthMega) || - (it.autospec_flags & ASF_FLAG_GRAB && item.classname == "item_flag_team")) - { - - if((it.enemy != toucher) || IS_OBSERVER(it)) - { - if(it.autospec_flags & ASF_OBSERVER_ONLY && !IS_OBSERVER(it)) - { - if(it.superspec_flags & SSF_VERBOSE) - superspec_msg("", "", it, sprintf("^8Ignored that ^7%s^8 grabbed %s^8 since the observer_only option is ON\n", toucher.netname, item.netname), 2); - } - else - { - if(it.autospec_flags & ASF_SHOWWHAT) - superspec_msg("", "", it, sprintf("^7Following %s^7 due to picking up %s\n", toucher.netname, item.netname), 2); - - superspec_Spectate(it, toucher); - } - } - } - )); - - return MUT_ITEMTOUCH_CONTINUE; -} - -MUTATOR_HOOKFUNCTION(superspec, SV_ParseClientCommand) -{ -#define OPTIONINFO(flag,var,test,text,long,short) \ - var = strcat(var, ((flag & test) ? "^2[ON] ^7" : "^1[OFF] ^7")); \ - var = strcat(var, text," ^7(^3 ", long, "^7 | ^3", short, " ^7)\n") - - if(MUTATOR_RETURNVALUE) // command was already handled? - return; - - entity player = M_ARGV(0, entity); - string cmd_name = M_ARGV(1, string); - int cmd_argc = M_ARGV(2, int); - - if(IS_PLAYER(player)) - return; - - if(cmd_name == "superspec_itemfilter") - { - if(argv(1) == "help") - { - string _aspeco; - _aspeco = "^7 superspec_itemfilter ^3\"item_classname1 item_classname2\"^7 only show thise items when ^2superspec ^3item_message^7 is on\n"; - _aspeco = strcat(_aspeco, "^3 clear^7 Remove the filter (show all pickups)\n"); - _aspeco = strcat(_aspeco, "^3 show ^7 Display current filter\n"); - superspec_msg("^3superspec_itemfilter help:\n\n\n", "\n^3superspec_itemfilter help:\n", player, _aspeco, 1); - } - else if(argv(1) == "clear") - { - if(player.superspec_itemfilter != "") - strunzone(player.superspec_itemfilter); - - player.superspec_itemfilter = ""; - } - else if(argv(1) == "show" || argv(1) == "") - { - if(player.superspec_itemfilter == "") - { - superspec_msg("^3superspec_itemfilter^7 is ^1not^7 set", "\n^3superspec_itemfilter^7 is ^1not^7 set\n", player, "", 1); - return true; - } - float i; - float l = tokenize_console(player.superspec_itemfilter); - string _msg = ""; - for(i = 0; i < l; ++i) - _msg = strcat(_msg, "^3#", ftos(i), " ^7", argv(i), "\n"); - //_msg = sprintf("^3#%d^7 %s\n%s", i, _msg, argv(i)); - - _msg = strcat(_msg,"\n"); - - superspec_msg("^3superspec_itemfilter is:\n\n\n", "\n^3superspec_itemfilter is:\n", player, _msg, 1); - } - else - { - if(player.superspec_itemfilter != "") - strunzone(player.superspec_itemfilter); - - player.superspec_itemfilter = strzone(argv(1)); - } - - return true; - } - - if(cmd_name == "superspec") - { - string _aspeco; - - if(cmd_argc > 1) - { - float i, _bits = 0, _start = 1; - if(argv(1) == "help") - { - _aspeco = "use cmd superspec [option] [on|off] to set options\n\n"; - _aspeco = strcat(_aspeco, "^3 silent ^7(short^5 si^7) supresses ALL messages from superspectate.\n"); - _aspeco = strcat(_aspeco, "^3 verbose ^7(short^5 ve^7) makes superspectate print some additional information.\n"); - _aspeco = strcat(_aspeco, "^3 item_message ^7(short^5 im^7) makes superspectate print items that were picked up.\n"); - _aspeco = strcat(_aspeco, "^7 Use cmd superspec_itemfilter \"item_class1 item_class2\" to set up a filter of what to show with ^3item_message.\n"); - superspec_msg("^2Available Super Spectate ^3options:\n\n\n", "\n^2Available Super Spectate ^3options:\n", player, _aspeco, 1); - return true; - } - - if(argv(1) == "clear") - { - player.superspec_flags = 0; - _start = 2; - } - - for(i = _start; i < cmd_argc; ++i) - { - if(argv(i) == "on" || argv(i) == "1") - { - player.superspec_flags |= _bits; - _bits = 0; - } - else if(argv(i) == "off" || argv(i) == "0") - { - if(_start == 1) - player.superspec_flags &= ~_bits; - - _bits = 0; - } - else - { - if((argv(i) == "silent") || (argv(i) == "si")) _bits |= SSF_SILENT ; - if((argv(i) == "verbose") || (argv(i) == "ve")) _bits |= SSF_VERBOSE; - if((argv(i) == "item_message") || (argv(i) == "im")) _bits |= SSF_ITEMMSG; - } - } - } - - _aspeco = ""; - OPTIONINFO(player.superspec_flags, _aspeco, SSF_SILENT, "Silent", "silent", "si"); - OPTIONINFO(player.superspec_flags, _aspeco, SSF_VERBOSE, "Verbose", "verbose", "ve"); - OPTIONINFO(player.superspec_flags, _aspeco, SSF_ITEMMSG, "Item pickup messages", "item_message", "im"); - - superspec_msg("^3Current Super Spectate options are:\n\n\n\n\n", "\n^3Current Super Spectate options are:\n", player, _aspeco, 1); - - return true; - } - -///////////////////// - - if(cmd_name == "autospec") - { - string _aspeco; - if(cmd_argc > 1) - { - if(argv(1) == "help") - { - _aspeco = "use cmd autospec [option] [on|off] to set options\n\n"; - _aspeco = strcat(_aspeco, "^3 strength ^7(short^5 st^7) for automatic spectate on strength powerup\n"); - _aspeco = strcat(_aspeco, "^3 shield ^7(short^5 sh^7) for automatic spectate on shield powerup\n"); - _aspeco = strcat(_aspeco, "^3 mega_health ^7(short^5 mh^7) for automatic spectate on mega health\n"); - _aspeco = strcat(_aspeco, "^3 mega_armor ^7(short^5 ma^7) for automatic spectate on mega armor\n"); - _aspeco = strcat(_aspeco, "^3 flag_grab ^7(short^5 fg^7) for automatic spectate on CTF flag grab\n"); - _aspeco = strcat(_aspeco, "^3 observer_only ^7(short^5 oo^7) for automatic spectate only if in observer mode\n"); - _aspeco = strcat(_aspeco, "^3 show_what ^7(short^5 sw^7) to display what event triggered autospectate\n"); - _aspeco = strcat(_aspeco, "^3 item_msg ^7(short^5 im^7) to autospec when item_message in superspectate is triggered\n"); - _aspeco = strcat(_aspeco, "^3 followkiller ^7(short ^5fk^7) to autospec the killer/off\n"); - _aspeco = strcat(_aspeco, "^3 all ^7(short ^5aa^7) to turn everything on/off\n"); - superspec_msg("^2Available Auto Spectate ^3options:\n\n\n", "\n^2Available Auto Spectate ^3options:\n", player, _aspeco, 1); - return true; - } - - float i, _bits = 0, _start = 1; - if(argv(1) == "clear") - { - player.autospec_flags = 0; - _start = 2; - } - - for(i = _start; i < cmd_argc; ++i) - { - if(argv(i) == "on" || argv(i) == "1") - { - player.autospec_flags |= _bits; - _bits = 0; - } - else if(argv(i) == "off" || argv(i) == "0") - { - if(_start == 1) - player.autospec_flags &= ~_bits; - - _bits = 0; - } - else - { - if((argv(i) == "strength") || (argv(i) == "st")) _bits |= ASF_STRENGTH; - if((argv(i) == "shield") || (argv(i) == "sh")) _bits |= ASF_SHIELD; - if((argv(i) == "mega_health") || (argv(i) == "mh")) _bits |= ASF_MEGA_HP; - if((argv(i) == "mega_armor") || (argv(i) == "ma")) _bits |= ASF_MEGA_AR; - if((argv(i) == "flag_grab") || (argv(i) == "fg")) _bits |= ASF_FLAG_GRAB; - if((argv(i) == "observer_only") || (argv(i) == "oo")) _bits |= ASF_OBSERVER_ONLY; - if((argv(i) == "show_what") || (argv(i) == "sw")) _bits |= ASF_SHOWWHAT; - if((argv(i) == "item_msg") || (argv(i) == "im")) _bits |= ASF_SSIM; - if((argv(i) == "followkiller") || (argv(i) == "fk")) _bits |= ASF_FOLLOWKILLER; - if((argv(i) == "all") || (argv(i) == "aa")) _bits |= ASF_ALL; - } - } - } - - _aspeco = ""; - OPTIONINFO(player.autospec_flags, _aspeco, ASF_STRENGTH, "Strength", "strength", "st"); - OPTIONINFO(player.autospec_flags, _aspeco, ASF_SHIELD, "Shield", "shield", "sh"); - OPTIONINFO(player.autospec_flags, _aspeco, ASF_MEGA_HP, "Mega Health", "mega_health", "mh"); - OPTIONINFO(player.autospec_flags, _aspeco, ASF_MEGA_AR, "Mega Armor", "mega_armor", "ma"); - OPTIONINFO(player.autospec_flags, _aspeco, ASF_FLAG_GRAB, "Flag grab", "flag_grab","fg"); - OPTIONINFO(player.autospec_flags, _aspeco, ASF_OBSERVER_ONLY, "Only switch if observer", "observer_only", "oo"); - OPTIONINFO(player.autospec_flags, _aspeco, ASF_SHOWWHAT, "Show what item triggered spectate", "show_what", "sw"); - OPTIONINFO(player.autospec_flags, _aspeco, ASF_SSIM, "Switch on superspec item message", "item_msg", "im"); - OPTIONINFO(player.autospec_flags, _aspeco, ASF_FOLLOWKILLER, "Followkiller", "followkiller", "fk"); - - superspec_msg("^3Current auto spectate options are:\n\n\n\n\n", "\n^3Current auto spectate options are:\n", player, _aspeco, 1); - return true; - } - - if(cmd_name == "followpowerup") - { - FOREACH_CLIENT(IS_PLAYER(it) && (it.strength_finished > time || it.invincible_finished > time), LAMBDA(return superspec_Spectate(player, it))); - - superspec_msg("", "", player, "No active powerup\n", 1); - return true; - } - - if(cmd_name == "followstrength") - { - FOREACH_CLIENT(IS_PLAYER(it) && it.strength_finished > time, LAMBDA(return superspec_Spectate(player, it))); - - superspec_msg("", "", player, "No active Strength\n", 1); - return true; - } - - if(cmd_name == "followshield") - { - FOREACH_CLIENT(IS_PLAYER(it) && it.invincible_finished > time, LAMBDA(return superspec_Spectate(player, it))); - - superspec_msg("", "", player, "No active Shield\n", 1); - return true; - } -#undef OPTIONINFO -} - -MUTATOR_HOOKFUNCTION(superspec, BuildMutatorsString) -{ - M_ARGV(0, string) = strcat(M_ARGV(0, string), ":SS"); -} - -MUTATOR_HOOKFUNCTION(superspec, BuildMutatorsPrettyString) -{ - M_ARGV(0, string) = strcat(M_ARGV(0, string), ", Super Spectators"); -} - -void superspec_hello(entity this) -{ - if(this.enemy.crypto_idfp == "") - Send_Notification(NOTIF_ONE_ONLY, this.enemy, MSG_INFO, INFO_SUPERSPEC_MISSING_UID); - - delete(this); -} - -MUTATOR_HOOKFUNCTION(superspec, ClientConnect) -{ - entity player = M_ARGV(0, entity); - - if(!IS_REAL_CLIENT(player)) - return; - - string fn = "superspec-local.options"; - float fh; - - player.superspec_flags = SSF_VERBOSE; - player.superspec_itemfilter = ""; - - entity _hello = spawn(); - _hello.enemy = player; - setthink(_hello, superspec_hello); - _hello.nextthink = time + 5; - - if (!_ISLOCAL(player)) - { - if(player.crypto_idfp == "") - return; - - fn = sprintf("superspec-%s.options", uri_escape(player.crypto_idfp)); - } - - fh = fopen(fn, FILE_READ); - if(fh < 0) - { - LOG_TRACE("^1ERROR: ^7 superspec can not open ", fn, " for reading."); - } - else - { - string _magic = fgets(fh); - if(_magic != _SSMAGIX) - { - LOG_TRACE("^1ERROR^7 While reading superspec options file: unknown magic"); - } - else - { - player.autospec_flags = stof(fgets(fh)); - player.superspec_flags = stof(fgets(fh)); - player.superspec_itemfilter = strzone(fgets(fh)); - } - fclose(fh); - } -} - -MUTATOR_HOOKFUNCTION(superspec, PlayerDies) -{ - entity frag_attacker = M_ARGV(1, entity); - entity frag_target = M_ARGV(2, entity); - - FOREACH_CLIENT(IS_SPEC(it), LAMBDA( - if(it.autospec_flags & ASF_FOLLOWKILLER && IS_PLAYER(frag_attacker) && it.enemy == frag_target) - { - if(it.autospec_flags & ASF_SHOWWHAT) - superspec_msg("", "", it, sprintf("^7Following %s^7 due to followkiller\n", frag_attacker.netname), 2); - - superspec_Spectate(it, frag_attacker); - } - )); -} - -MUTATOR_HOOKFUNCTION(superspec, ClientDisconnect) -{ - entity player = M_ARGV(0, entity); - - superspec_save_client_conf(player); -} -#endif diff --git a/qcsrc/common/mutators/mutator/superspec/sv_superspec.qc b/qcsrc/common/mutators/mutator/superspec/sv_superspec.qc new file mode 100644 index 0000000000..c423042a3c --- /dev/null +++ b/qcsrc/common/mutators/mutator/superspec/sv_superspec.qc @@ -0,0 +1,459 @@ +#include "sv_superspec.qh" + +REGISTER_MUTATOR(superspec, cvar("g_superspectate")); + +#define _SSMAGIX "SUPERSPEC_OPTIONSFILE_V1" +#define _ISLOCAL(ent) ((edict_num(1) == (ent)) ? true : false) + +const float ASF_STRENGTH = BIT(0); +const float ASF_SHIELD = BIT(1); +const float ASF_MEGA_AR = BIT(2); +const float ASF_MEGA_HP = BIT(3); +const float ASF_FLAG_GRAB = BIT(4); +const float ASF_OBSERVER_ONLY = BIT(5); +const float ASF_SHOWWHAT = BIT(6); +const float ASF_SSIM = BIT(7); +const float ASF_FOLLOWKILLER = BIT(8); +const float ASF_ALL = 0xFFFFFF; +.float autospec_flags; + +const float SSF_SILENT = 1; +const float SSF_VERBOSE = 2; +const float SSF_ITEMMSG = 4; +.float superspec_flags; + +.string superspec_itemfilter; //"classname1 classname2 ..." + +bool superspec_Spectate(entity this, entity targ) +{ + if(Spectate(this, targ) == 1) + TRANSMUTE(Spectator, this); + + return true; +} + +void superspec_save_client_conf(entity this) +{ + string fn = "superspec-local.options"; + float fh; + + if (!_ISLOCAL(this)) + { + if(this.crypto_idfp == "") + return; + + fn = sprintf("superspec-%s.options", uri_escape(this.crypto_idfp)); + } + + fh = fopen(fn, FILE_WRITE); + if(fh < 0) + { + LOG_TRACE("^1ERROR: ^7 superspec can not open ", fn, " for writing."); + } + else + { + fputs(fh, _SSMAGIX); + fputs(fh, "\n"); + fputs(fh, ftos(this.autospec_flags)); + fputs(fh, "\n"); + fputs(fh, ftos(this.superspec_flags)); + fputs(fh, "\n"); + fputs(fh, this.superspec_itemfilter); + fputs(fh, "\n"); + fclose(fh); + } +} + +void superspec_msg(string _center_title, string _con_title, entity _to, string _msg, float _spamlevel) +{ + sprint(_to, strcat(_con_title, _msg)); + + if(_to.superspec_flags & SSF_SILENT) + return; + + if(_spamlevel > 1) + if (!(_to.superspec_flags & SSF_VERBOSE)) + return; + + centerprint(_to, strcat(_center_title, _msg)); +} + +float superspec_filteritem(entity _for, entity _item) +{ + float i; + + if(_for.superspec_itemfilter == "") + return true; + + if(_for.superspec_itemfilter == "") + return true; + + float l = tokenize_console(_for.superspec_itemfilter); + for(i = 0; i < l; ++i) + { + if(argv(i) == _item.classname) + return true; + } + + return false; +} + +MUTATOR_HOOKFUNCTION(superspec, ItemTouch) +{ + entity item = M_ARGV(0, entity); + entity toucher = M_ARGV(1, entity); + + FOREACH_CLIENT(true, LAMBDA( + if(!IS_SPEC(it) && !IS_OBSERVER(it)) + continue; + if(it.superspec_flags & SSF_ITEMMSG) + if(superspec_filteritem(it, item)) + { + if(it.superspec_flags & SSF_VERBOSE) + superspec_msg("", "", it, sprintf("Player %s^7 just picked up ^3%s\n", toucher.netname, item.netname), 1); + else + superspec_msg("", "", it, sprintf("Player %s^7 just picked up ^3%s\n^8(%s^8)\n", toucher.netname, item.netname, item.classname), 1); + if((it.autospec_flags & ASF_SSIM) && it.enemy != toucher) + { + superspec_Spectate(it, toucher); + return MUT_ITEMTOUCH_CONTINUE; + } + } + + if((it.autospec_flags & ASF_SHIELD && item.invincible_finished) || + (it.autospec_flags & ASF_STRENGTH && item.strength_finished) || + (it.autospec_flags & ASF_MEGA_AR && item.itemdef == ITEM_ArmorMega) || + (it.autospec_flags & ASF_MEGA_HP && item.itemdef == ITEM_HealthMega) || + (it.autospec_flags & ASF_FLAG_GRAB && item.classname == "item_flag_team")) + { + + if((it.enemy != toucher) || IS_OBSERVER(it)) + { + if(it.autospec_flags & ASF_OBSERVER_ONLY && !IS_OBSERVER(it)) + { + if(it.superspec_flags & SSF_VERBOSE) + superspec_msg("", "", it, sprintf("^8Ignored that ^7%s^8 grabbed %s^8 since the observer_only option is ON\n", toucher.netname, item.netname), 2); + } + else + { + if(it.autospec_flags & ASF_SHOWWHAT) + superspec_msg("", "", it, sprintf("^7Following %s^7 due to picking up %s\n", toucher.netname, item.netname), 2); + + superspec_Spectate(it, toucher); + } + } + } + )); + + return MUT_ITEMTOUCH_CONTINUE; +} + +MUTATOR_HOOKFUNCTION(superspec, SV_ParseClientCommand) +{ +#define OPTIONINFO(flag,var,test,text,long,short) \ + var = strcat(var, ((flag & test) ? "^2[ON] ^7" : "^1[OFF] ^7")); \ + var = strcat(var, text," ^7(^3 ", long, "^7 | ^3", short, " ^7)\n") + + if(MUTATOR_RETURNVALUE) // command was already handled? + return; + + entity player = M_ARGV(0, entity); + string cmd_name = M_ARGV(1, string); + int cmd_argc = M_ARGV(2, int); + + if(IS_PLAYER(player)) + return; + + if(cmd_name == "superspec_itemfilter") + { + if(argv(1) == "help") + { + string _aspeco; + _aspeco = "^7 superspec_itemfilter ^3\"item_classname1 item_classname2\"^7 only show thise items when ^2superspec ^3item_message^7 is on\n"; + _aspeco = strcat(_aspeco, "^3 clear^7 Remove the filter (show all pickups)\n"); + _aspeco = strcat(_aspeco, "^3 show ^7 Display current filter\n"); + superspec_msg("^3superspec_itemfilter help:\n\n\n", "\n^3superspec_itemfilter help:\n", player, _aspeco, 1); + } + else if(argv(1) == "clear") + { + if(player.superspec_itemfilter != "") + strunzone(player.superspec_itemfilter); + + player.superspec_itemfilter = ""; + } + else if(argv(1) == "show" || argv(1) == "") + { + if(player.superspec_itemfilter == "") + { + superspec_msg("^3superspec_itemfilter^7 is ^1not^7 set", "\n^3superspec_itemfilter^7 is ^1not^7 set\n", player, "", 1); + return true; + } + float i; + float l = tokenize_console(player.superspec_itemfilter); + string _msg = ""; + for(i = 0; i < l; ++i) + _msg = strcat(_msg, "^3#", ftos(i), " ^7", argv(i), "\n"); + //_msg = sprintf("^3#%d^7 %s\n%s", i, _msg, argv(i)); + + _msg = strcat(_msg,"\n"); + + superspec_msg("^3superspec_itemfilter is:\n\n\n", "\n^3superspec_itemfilter is:\n", player, _msg, 1); + } + else + { + if(player.superspec_itemfilter != "") + strunzone(player.superspec_itemfilter); + + player.superspec_itemfilter = strzone(argv(1)); + } + + return true; + } + + if(cmd_name == "superspec") + { + string _aspeco; + + if(cmd_argc > 1) + { + float i, _bits = 0, _start = 1; + if(argv(1) == "help") + { + _aspeco = "use cmd superspec [option] [on|off] to set options\n\n"; + _aspeco = strcat(_aspeco, "^3 silent ^7(short^5 si^7) supresses ALL messages from superspectate.\n"); + _aspeco = strcat(_aspeco, "^3 verbose ^7(short^5 ve^7) makes superspectate print some additional information.\n"); + _aspeco = strcat(_aspeco, "^3 item_message ^7(short^5 im^7) makes superspectate print items that were picked up.\n"); + _aspeco = strcat(_aspeco, "^7 Use cmd superspec_itemfilter \"item_class1 item_class2\" to set up a filter of what to show with ^3item_message.\n"); + superspec_msg("^2Available Super Spectate ^3options:\n\n\n", "\n^2Available Super Spectate ^3options:\n", player, _aspeco, 1); + return true; + } + + if(argv(1) == "clear") + { + player.superspec_flags = 0; + _start = 2; + } + + for(i = _start; i < cmd_argc; ++i) + { + if(argv(i) == "on" || argv(i) == "1") + { + player.superspec_flags |= _bits; + _bits = 0; + } + else if(argv(i) == "off" || argv(i) == "0") + { + if(_start == 1) + player.superspec_flags &= ~_bits; + + _bits = 0; + } + else + { + if((argv(i) == "silent") || (argv(i) == "si")) _bits |= SSF_SILENT ; + if((argv(i) == "verbose") || (argv(i) == "ve")) _bits |= SSF_VERBOSE; + if((argv(i) == "item_message") || (argv(i) == "im")) _bits |= SSF_ITEMMSG; + } + } + } + + _aspeco = ""; + OPTIONINFO(player.superspec_flags, _aspeco, SSF_SILENT, "Silent", "silent", "si"); + OPTIONINFO(player.superspec_flags, _aspeco, SSF_VERBOSE, "Verbose", "verbose", "ve"); + OPTIONINFO(player.superspec_flags, _aspeco, SSF_ITEMMSG, "Item pickup messages", "item_message", "im"); + + superspec_msg("^3Current Super Spectate options are:\n\n\n\n\n", "\n^3Current Super Spectate options are:\n", player, _aspeco, 1); + + return true; + } + +///////////////////// + + if(cmd_name == "autospec") + { + string _aspeco; + if(cmd_argc > 1) + { + if(argv(1) == "help") + { + _aspeco = "use cmd autospec [option] [on|off] to set options\n\n"; + _aspeco = strcat(_aspeco, "^3 strength ^7(short^5 st^7) for automatic spectate on strength powerup\n"); + _aspeco = strcat(_aspeco, "^3 shield ^7(short^5 sh^7) for automatic spectate on shield powerup\n"); + _aspeco = strcat(_aspeco, "^3 mega_health ^7(short^5 mh^7) for automatic spectate on mega health\n"); + _aspeco = strcat(_aspeco, "^3 mega_armor ^7(short^5 ma^7) for automatic spectate on mega armor\n"); + _aspeco = strcat(_aspeco, "^3 flag_grab ^7(short^5 fg^7) for automatic spectate on CTF flag grab\n"); + _aspeco = strcat(_aspeco, "^3 observer_only ^7(short^5 oo^7) for automatic spectate only if in observer mode\n"); + _aspeco = strcat(_aspeco, "^3 show_what ^7(short^5 sw^7) to display what event triggered autospectate\n"); + _aspeco = strcat(_aspeco, "^3 item_msg ^7(short^5 im^7) to autospec when item_message in superspectate is triggered\n"); + _aspeco = strcat(_aspeco, "^3 followkiller ^7(short ^5fk^7) to autospec the killer/off\n"); + _aspeco = strcat(_aspeco, "^3 all ^7(short ^5aa^7) to turn everything on/off\n"); + superspec_msg("^2Available Auto Spectate ^3options:\n\n\n", "\n^2Available Auto Spectate ^3options:\n", player, _aspeco, 1); + return true; + } + + float i, _bits = 0, _start = 1; + if(argv(1) == "clear") + { + player.autospec_flags = 0; + _start = 2; + } + + for(i = _start; i < cmd_argc; ++i) + { + if(argv(i) == "on" || argv(i) == "1") + { + player.autospec_flags |= _bits; + _bits = 0; + } + else if(argv(i) == "off" || argv(i) == "0") + { + if(_start == 1) + player.autospec_flags &= ~_bits; + + _bits = 0; + } + else + { + if((argv(i) == "strength") || (argv(i) == "st")) _bits |= ASF_STRENGTH; + if((argv(i) == "shield") || (argv(i) == "sh")) _bits |= ASF_SHIELD; + if((argv(i) == "mega_health") || (argv(i) == "mh")) _bits |= ASF_MEGA_HP; + if((argv(i) == "mega_armor") || (argv(i) == "ma")) _bits |= ASF_MEGA_AR; + if((argv(i) == "flag_grab") || (argv(i) == "fg")) _bits |= ASF_FLAG_GRAB; + if((argv(i) == "observer_only") || (argv(i) == "oo")) _bits |= ASF_OBSERVER_ONLY; + if((argv(i) == "show_what") || (argv(i) == "sw")) _bits |= ASF_SHOWWHAT; + if((argv(i) == "item_msg") || (argv(i) == "im")) _bits |= ASF_SSIM; + if((argv(i) == "followkiller") || (argv(i) == "fk")) _bits |= ASF_FOLLOWKILLER; + if((argv(i) == "all") || (argv(i) == "aa")) _bits |= ASF_ALL; + } + } + } + + _aspeco = ""; + OPTIONINFO(player.autospec_flags, _aspeco, ASF_STRENGTH, "Strength", "strength", "st"); + OPTIONINFO(player.autospec_flags, _aspeco, ASF_SHIELD, "Shield", "shield", "sh"); + OPTIONINFO(player.autospec_flags, _aspeco, ASF_MEGA_HP, "Mega Health", "mega_health", "mh"); + OPTIONINFO(player.autospec_flags, _aspeco, ASF_MEGA_AR, "Mega Armor", "mega_armor", "ma"); + OPTIONINFO(player.autospec_flags, _aspeco, ASF_FLAG_GRAB, "Flag grab", "flag_grab","fg"); + OPTIONINFO(player.autospec_flags, _aspeco, ASF_OBSERVER_ONLY, "Only switch if observer", "observer_only", "oo"); + OPTIONINFO(player.autospec_flags, _aspeco, ASF_SHOWWHAT, "Show what item triggered spectate", "show_what", "sw"); + OPTIONINFO(player.autospec_flags, _aspeco, ASF_SSIM, "Switch on superspec item message", "item_msg", "im"); + OPTIONINFO(player.autospec_flags, _aspeco, ASF_FOLLOWKILLER, "Followkiller", "followkiller", "fk"); + + superspec_msg("^3Current auto spectate options are:\n\n\n\n\n", "\n^3Current auto spectate options are:\n", player, _aspeco, 1); + return true; + } + + if(cmd_name == "followpowerup") + { + FOREACH_CLIENT(IS_PLAYER(it) && (it.strength_finished > time || it.invincible_finished > time), LAMBDA(return superspec_Spectate(player, it))); + + superspec_msg("", "", player, "No active powerup\n", 1); + return true; + } + + if(cmd_name == "followstrength") + { + FOREACH_CLIENT(IS_PLAYER(it) && it.strength_finished > time, LAMBDA(return superspec_Spectate(player, it))); + + superspec_msg("", "", player, "No active Strength\n", 1); + return true; + } + + if(cmd_name == "followshield") + { + FOREACH_CLIENT(IS_PLAYER(it) && it.invincible_finished > time, LAMBDA(return superspec_Spectate(player, it))); + + superspec_msg("", "", player, "No active Shield\n", 1); + return true; + } +#undef OPTIONINFO +} + +MUTATOR_HOOKFUNCTION(superspec, BuildMutatorsString) +{ + M_ARGV(0, string) = strcat(M_ARGV(0, string), ":SS"); +} + +MUTATOR_HOOKFUNCTION(superspec, BuildMutatorsPrettyString) +{ + M_ARGV(0, string) = strcat(M_ARGV(0, string), ", Super Spectators"); +} + +void superspec_hello(entity this) +{ + if(this.enemy.crypto_idfp == "") + Send_Notification(NOTIF_ONE_ONLY, this.enemy, MSG_INFO, INFO_SUPERSPEC_MISSING_UID); + + delete(this); +} + +MUTATOR_HOOKFUNCTION(superspec, ClientConnect) +{ + entity player = M_ARGV(0, entity); + + if(!IS_REAL_CLIENT(player)) + return; + + string fn = "superspec-local.options"; + float fh; + + player.superspec_flags = SSF_VERBOSE; + player.superspec_itemfilter = ""; + + entity _hello = spawn(); + _hello.enemy = player; + setthink(_hello, superspec_hello); + _hello.nextthink = time + 5; + + if (!_ISLOCAL(player)) + { + if(player.crypto_idfp == "") + return; + + fn = sprintf("superspec-%s.options", uri_escape(player.crypto_idfp)); + } + + fh = fopen(fn, FILE_READ); + if(fh < 0) + { + LOG_TRACE("^1ERROR: ^7 superspec can not open ", fn, " for reading."); + } + else + { + string _magic = fgets(fh); + if(_magic != _SSMAGIX) + { + LOG_TRACE("^1ERROR^7 While reading superspec options file: unknown magic"); + } + else + { + player.autospec_flags = stof(fgets(fh)); + player.superspec_flags = stof(fgets(fh)); + player.superspec_itemfilter = strzone(fgets(fh)); + } + fclose(fh); + } +} + +MUTATOR_HOOKFUNCTION(superspec, PlayerDies) +{ + entity frag_attacker = M_ARGV(1, entity); + entity frag_target = M_ARGV(2, entity); + + FOREACH_CLIENT(IS_SPEC(it), LAMBDA( + if(it.autospec_flags & ASF_FOLLOWKILLER && IS_PLAYER(frag_attacker) && it.enemy == frag_target) + { + if(it.autospec_flags & ASF_SHOWWHAT) + superspec_msg("", "", it, sprintf("^7Following %s^7 due to followkiller\n", frag_attacker.netname), 2); + + superspec_Spectate(it, frag_attacker); + } + )); +} + +MUTATOR_HOOKFUNCTION(superspec, ClientDisconnect) +{ + entity player = M_ARGV(0, entity); + + superspec_save_client_conf(player); +} diff --git a/qcsrc/common/mutators/mutator/superspec/sv_superspec.qh b/qcsrc/common/mutators/mutator/superspec/sv_superspec.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/mutators/mutator/superspec/sv_superspec.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mutators/mutator/touchexplode/_mod.inc b/qcsrc/common/mutators/mutator/touchexplode/_mod.inc index 42dad3926e..f341e2afe1 100644 --- a/qcsrc/common/mutators/mutator/touchexplode/_mod.inc +++ b/qcsrc/common/mutators/mutator/touchexplode/_mod.inc @@ -1,2 +1,4 @@ // generated file; do not modify -#include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/mutators/mutator/touchexplode/_mod.qh b/qcsrc/common/mutators/mutator/touchexplode/_mod.qh index ec71f52d74..18cdcc60f6 100644 --- a/qcsrc/common/mutators/mutator/touchexplode/_mod.qh +++ b/qcsrc/common/mutators/mutator/touchexplode/_mod.qh @@ -1,2 +1,4 @@ // generated file; do not modify -#include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/mutators/mutator/touchexplode/module.inc b/qcsrc/common/mutators/mutator/touchexplode/module.inc deleted file mode 100644 index d3b0ea5af9..0000000000 --- a/qcsrc/common/mutators/mutator/touchexplode/module.inc +++ /dev/null @@ -1,3 +0,0 @@ -#ifdef SVQC -#include "touchexplode.qc" -#endif diff --git a/qcsrc/common/mutators/mutator/touchexplode/sv_touchexplode.qc b/qcsrc/common/mutators/mutator/touchexplode/sv_touchexplode.qc new file mode 100644 index 0000000000..38ac30595d --- /dev/null +++ b/qcsrc/common/mutators/mutator/touchexplode/sv_touchexplode.qc @@ -0,0 +1,47 @@ +#include "sv_touchexplode.qh" + +float autocvar_g_touchexplode_radius; +float autocvar_g_touchexplode_damage; +float autocvar_g_touchexplode_edgedamage; +float autocvar_g_touchexplode_force; + +REGISTER_MUTATOR(touchexplode, cvar("g_touchexplode")); + +.float touchexplode_time; + +void PlayerTouchExplode(entity p1, entity p2) +{ + vector org = (p1.origin + p2.origin) * 0.5; + org.z += (p1.mins.z + p2.mins.z) * 0.5; + + sound(p1, CH_TRIGGER, SND_GRENADE_IMPACT, VOL_BASE, ATTEN_NORM); + Send_Effect(EFFECT_EXPLOSION_SMALL, org, '0 0 0', 1); + + entity e = spawn(); + setorigin(e, org); + RadiusDamage(e, NULL, autocvar_g_touchexplode_damage, autocvar_g_touchexplode_edgedamage, autocvar_g_touchexplode_radius, NULL, NULL, autocvar_g_touchexplode_force, DEATH_TOUCHEXPLODE.m_id, NULL); + delete(e); +} + +MUTATOR_HOOKFUNCTION(touchexplode, PlayerPreThink) +{ + entity player = M_ARGV(0, entity); + + if(time > player.touchexplode_time) + if(!gameover) + if(!STAT(FROZEN, player)) + if(IS_PLAYER(player)) + if(!IS_DEAD(player)) + if(!IS_INDEPENDENT_PLAYER(player)) + FOREACH_CLIENT(IS_PLAYER(it) && it != player, LAMBDA( + if(time > it.touchexplode_time) + if(!STAT(FROZEN, it)) + if(!IS_DEAD(it)) + if (!IS_INDEPENDENT_PLAYER(it)) + if(boxesoverlap(player.absmin, player.absmax, it.absmin, it.absmax)) + { + PlayerTouchExplode(player, it); + player.touchexplode_time = it.touchexplode_time = time + 0.2; + } + )); +} diff --git a/qcsrc/common/mutators/mutator/touchexplode/sv_touchexplode.qh b/qcsrc/common/mutators/mutator/touchexplode/sv_touchexplode.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/mutators/mutator/touchexplode/sv_touchexplode.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mutators/mutator/touchexplode/touchexplode.qc b/qcsrc/common/mutators/mutator/touchexplode/touchexplode.qc deleted file mode 100644 index c585e7e906..0000000000 --- a/qcsrc/common/mutators/mutator/touchexplode/touchexplode.qc +++ /dev/null @@ -1,47 +0,0 @@ -#ifdef IMPLEMENTATION -float autocvar_g_touchexplode_radius; -float autocvar_g_touchexplode_damage; -float autocvar_g_touchexplode_edgedamage; -float autocvar_g_touchexplode_force; - -REGISTER_MUTATOR(touchexplode, cvar("g_touchexplode")); - -.float touchexplode_time; - -void PlayerTouchExplode(entity p1, entity p2) -{ - vector org = (p1.origin + p2.origin) * 0.5; - org.z += (p1.mins.z + p2.mins.z) * 0.5; - - sound(p1, CH_TRIGGER, SND_GRENADE_IMPACT, VOL_BASE, ATTEN_NORM); - Send_Effect(EFFECT_EXPLOSION_SMALL, org, '0 0 0', 1); - - entity e = spawn(); - setorigin(e, org); - RadiusDamage(e, NULL, autocvar_g_touchexplode_damage, autocvar_g_touchexplode_edgedamage, autocvar_g_touchexplode_radius, NULL, NULL, autocvar_g_touchexplode_force, DEATH_TOUCHEXPLODE.m_id, NULL); - delete(e); -} - -MUTATOR_HOOKFUNCTION(touchexplode, PlayerPreThink) -{ - entity player = M_ARGV(0, entity); - - if(time > player.touchexplode_time) - if(!gameover) - if(!STAT(FROZEN, player)) - if(IS_PLAYER(player)) - if(!IS_DEAD(player)) - if(!IS_INDEPENDENT_PLAYER(player)) - FOREACH_CLIENT(IS_PLAYER(it) && it != player, LAMBDA( - if(time > it.touchexplode_time) - if(!STAT(FROZEN, it)) - if(!IS_DEAD(it)) - if (!IS_INDEPENDENT_PLAYER(it)) - if(boxesoverlap(player.absmin, player.absmax, it.absmin, it.absmax)) - { - PlayerTouchExplode(player, it); - player.touchexplode_time = it.touchexplode_time = time + 0.2; - } - )); -} -#endif diff --git a/qcsrc/common/mutators/mutator/vampire/_mod.inc b/qcsrc/common/mutators/mutator/vampire/_mod.inc index 856ed84c46..57b844451a 100644 --- a/qcsrc/common/mutators/mutator/vampire/_mod.inc +++ b/qcsrc/common/mutators/mutator/vampire/_mod.inc @@ -1,2 +1,4 @@ // generated file; do not modify -#include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/mutators/mutator/vampire/_mod.qh b/qcsrc/common/mutators/mutator/vampire/_mod.qh index 551184c77b..d7d8a4143c 100644 --- a/qcsrc/common/mutators/mutator/vampire/_mod.qh +++ b/qcsrc/common/mutators/mutator/vampire/_mod.qh @@ -1,2 +1,4 @@ // generated file; do not modify -#include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/mutators/mutator/vampire/module.inc b/qcsrc/common/mutators/mutator/vampire/module.inc deleted file mode 100644 index 864ea28b24..0000000000 --- a/qcsrc/common/mutators/mutator/vampire/module.inc +++ /dev/null @@ -1,3 +0,0 @@ -#ifdef SVQC -#include "vampire.qc" -#endif diff --git a/qcsrc/common/mutators/mutator/vampire/sv_vampire.qc b/qcsrc/common/mutators/mutator/vampire/sv_vampire.qc new file mode 100644 index 0000000000..3a435c5ed1 --- /dev/null +++ b/qcsrc/common/mutators/mutator/vampire/sv_vampire.qc @@ -0,0 +1,28 @@ +#include "sv_vampire.qh" + +REGISTER_MUTATOR(vampire, cvar("g_vampire") && !cvar("g_instagib")); + +MUTATOR_HOOKFUNCTION(vampire, PlayerDamage_SplitHealthArmor) +{ + entity frag_attacker = M_ARGV(1, entity); + entity frag_target = M_ARGV(2, entity); + float damage_take = M_ARGV(4, float); + + if(time >= frag_target.spawnshieldtime) + if(frag_target != frag_attacker) + if(!IS_DEAD(frag_target)) + { + frag_attacker.health += bound(0, damage_take, frag_target.health); + frag_attacker.health = bound(0, frag_attacker.health, autocvar_g_balance_health_limit); + } +} + +MUTATOR_HOOKFUNCTION(vampire, BuildMutatorsString) +{ + M_ARGV(0, string) = strcat(M_ARGV(0, string), ":Vampire"); +} + +MUTATOR_HOOKFUNCTION(vampire, BuildMutatorsPrettyString) +{ + M_ARGV(0, string) = strcat(M_ARGV(0, string), ", Vampire"); +} diff --git a/qcsrc/common/mutators/mutator/vampire/sv_vampire.qh b/qcsrc/common/mutators/mutator/vampire/sv_vampire.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/mutators/mutator/vampire/sv_vampire.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mutators/mutator/vampire/vampire.qc b/qcsrc/common/mutators/mutator/vampire/vampire.qc deleted file mode 100644 index d245c8059b..0000000000 --- a/qcsrc/common/mutators/mutator/vampire/vampire.qc +++ /dev/null @@ -1,28 +0,0 @@ -#ifdef IMPLEMENTATION -REGISTER_MUTATOR(vampire, cvar("g_vampire") && !cvar("g_instagib")); - -MUTATOR_HOOKFUNCTION(vampire, PlayerDamage_SplitHealthArmor) -{ - entity frag_attacker = M_ARGV(1, entity); - entity frag_target = M_ARGV(2, entity); - float damage_take = M_ARGV(4, float); - - if(time >= frag_target.spawnshieldtime) - if(frag_target != frag_attacker) - if(!IS_DEAD(frag_target)) - { - frag_attacker.health += bound(0, damage_take, frag_target.health); - frag_attacker.health = bound(0, frag_attacker.health, autocvar_g_balance_health_limit); - } -} - -MUTATOR_HOOKFUNCTION(vampire, BuildMutatorsString) -{ - M_ARGV(0, string) = strcat(M_ARGV(0, string), ":Vampire"); -} - -MUTATOR_HOOKFUNCTION(vampire, BuildMutatorsPrettyString) -{ - M_ARGV(0, string) = strcat(M_ARGV(0, string), ", Vampire"); -} -#endif diff --git a/qcsrc/common/mutators/mutator/vampirehook/_mod.inc b/qcsrc/common/mutators/mutator/vampirehook/_mod.inc index 868a4ef3fc..72b2bacb9c 100644 --- a/qcsrc/common/mutators/mutator/vampirehook/_mod.inc +++ b/qcsrc/common/mutators/mutator/vampirehook/_mod.inc @@ -1,2 +1,4 @@ // generated file; do not modify -#include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/mutators/mutator/vampirehook/_mod.qh b/qcsrc/common/mutators/mutator/vampirehook/_mod.qh index 5d57816c96..eaa7b320a8 100644 --- a/qcsrc/common/mutators/mutator/vampirehook/_mod.qh +++ b/qcsrc/common/mutators/mutator/vampirehook/_mod.qh @@ -1,2 +1,4 @@ // generated file; do not modify -#include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/mutators/mutator/vampirehook/module.inc b/qcsrc/common/mutators/mutator/vampirehook/module.inc deleted file mode 100644 index 17ecf6005f..0000000000 --- a/qcsrc/common/mutators/mutator/vampirehook/module.inc +++ /dev/null @@ -1,3 +0,0 @@ -#ifdef SVQC -#include "vampirehook.qc" -#endif diff --git a/qcsrc/common/mutators/mutator/vampirehook/sv_vampirehook.qc b/qcsrc/common/mutators/mutator/vampirehook/sv_vampirehook.qc new file mode 100644 index 0000000000..e2b0f57d76 --- /dev/null +++ b/qcsrc/common/mutators/mutator/vampirehook/sv_vampirehook.qc @@ -0,0 +1,37 @@ +#include "sv_vampirehook.qh" + +REGISTER_MUTATOR(vh, cvar("g_vampirehook")); + +bool autocvar_g_vampirehook_teamheal; +float autocvar_g_vampirehook_damage; +float autocvar_g_vampirehook_damagerate; +float autocvar_g_vampirehook_health_steal; + +.float last_dmg; + +MUTATOR_HOOKFUNCTION(vh, GrappleHookThink) +{ + entity thehook = M_ARGV(0, entity); + + entity dmgent = ((SAME_TEAM(thehook.owner, thehook.aiment) && autocvar_g_vampirehook_teamheal) ? thehook.owner : thehook.aiment); + + if(IS_PLAYER(thehook.aiment)) + if(thehook.last_dmg < time) + if(!STAT(FROZEN, thehook.aiment)) + if(time >= game_starttime) + if(DIFF_TEAM(thehook.owner, thehook.aiment) || autocvar_g_vampirehook_teamheal) + if(thehook.aiment.health > 0) + if(autocvar_g_vampirehook_damage) + { + thehook.last_dmg = time + autocvar_g_vampirehook_damagerate; + thehook.owner.damage_dealt += autocvar_g_vampirehook_damage; + Damage(dmgent, thehook, thehook.owner, autocvar_g_vampirehook_damage, WEP_HOOK.m_id, thehook.origin, '0 0 0'); + if(SAME_TEAM(thehook.owner, thehook.aiment)) + thehook.aiment.health = min(thehook.aiment.health + autocvar_g_vampirehook_health_steal, g_pickup_healthsmall_max); + else + thehook.owner.health = min(thehook.owner.health + autocvar_g_vampirehook_health_steal, g_pickup_healthsmall_max); + + if(dmgent == thehook.owner) + dmgent.health -= autocvar_g_vampirehook_damage; // FIXME: friendly fire?! + } +} diff --git a/qcsrc/common/mutators/mutator/vampirehook/sv_vampirehook.qh b/qcsrc/common/mutators/mutator/vampirehook/sv_vampirehook.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/mutators/mutator/vampirehook/sv_vampirehook.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mutators/mutator/vampirehook/vampirehook.qc b/qcsrc/common/mutators/mutator/vampirehook/vampirehook.qc deleted file mode 100644 index a54dc74e40..0000000000 --- a/qcsrc/common/mutators/mutator/vampirehook/vampirehook.qc +++ /dev/null @@ -1,38 +0,0 @@ -#ifdef IMPLEMENTATION -REGISTER_MUTATOR(vh, cvar("g_vampirehook")); - -bool autocvar_g_vampirehook_teamheal; -float autocvar_g_vampirehook_damage; -float autocvar_g_vampirehook_damagerate; -float autocvar_g_vampirehook_health_steal; - -.float last_dmg; - -MUTATOR_HOOKFUNCTION(vh, GrappleHookThink) -{ - entity thehook = M_ARGV(0, entity); - - entity dmgent = ((SAME_TEAM(thehook.owner, thehook.aiment) && autocvar_g_vampirehook_teamheal) ? thehook.owner : thehook.aiment); - - if(IS_PLAYER(thehook.aiment)) - if(thehook.last_dmg < time) - if(!STAT(FROZEN, thehook.aiment)) - if(time >= game_starttime) - if(DIFF_TEAM(thehook.owner, thehook.aiment) || autocvar_g_vampirehook_teamheal) - if(thehook.aiment.health > 0) - if(autocvar_g_vampirehook_damage) - { - thehook.last_dmg = time + autocvar_g_vampirehook_damagerate; - thehook.owner.damage_dealt += autocvar_g_vampirehook_damage; - Damage(dmgent, thehook, thehook.owner, autocvar_g_vampirehook_damage, WEP_HOOK.m_id, thehook.origin, '0 0 0'); - if(SAME_TEAM(thehook.owner, thehook.aiment)) - thehook.aiment.health = min(thehook.aiment.health + autocvar_g_vampirehook_health_steal, g_pickup_healthsmall_max); - else - thehook.owner.health = min(thehook.owner.health + autocvar_g_vampirehook_health_steal, g_pickup_healthsmall_max); - - if(dmgent == thehook.owner) - dmgent.health -= autocvar_g_vampirehook_damage; // FIXME: friendly fire?! - } -} - -#endif diff --git a/qcsrc/common/mutators/mutator/waypoints/all.qh b/qcsrc/common/mutators/mutator/waypoints/all.qh index 4645130551..d99b27e932 100644 --- a/qcsrc/common/mutators/mutator/waypoints/all.qh +++ b/qcsrc/common/mutators/mutator/waypoints/all.qh @@ -1,5 +1,4 @@ -#ifndef WAYPOINTS_ALL_H -#define WAYPOINTS_ALL_H +#pragma once #include "waypointsprites.qh" @@ -58,5 +57,3 @@ REGISTER_RADARICON(Vehicle, 1); REGISTER_RADARICON(Weapon, 1); #include "all.inc" - -#endif diff --git a/qcsrc/common/mutators/mutator/waypoints/module.inc b/qcsrc/common/mutators/mutator/waypoints/module.inc deleted file mode 100644 index 50bb5b4d6e..0000000000 --- a/qcsrc/common/mutators/mutator/waypoints/module.inc +++ /dev/null @@ -1 +0,0 @@ -#include "waypointsprites.qc" diff --git a/qcsrc/common/mutators/mutator/waypoints/waypointsprites.qc b/qcsrc/common/mutators/mutator/waypoints/waypointsprites.qc index e99303823b..8bcb2d62ac 100644 --- a/qcsrc/common/mutators/mutator/waypoints/waypointsprites.qc +++ b/qcsrc/common/mutators/mutator/waypoints/waypointsprites.qc @@ -1,7 +1,5 @@ #include "waypointsprites.qh" -#ifdef IMPLEMENTATION - REGISTER_MUTATOR(waypointsprites, true); REGISTER_NET_LINKED(waypointsprites) @@ -1147,4 +1145,3 @@ void WaypointSprite_PlayerGone(entity this) WaypointSprite_DetachCarrier(this); } #endif -#endif diff --git a/qcsrc/common/mutators/mutator/weaponarena_random/_mod.inc b/qcsrc/common/mutators/mutator/weaponarena_random/_mod.inc index 742510b883..034b51d117 100644 --- a/qcsrc/common/mutators/mutator/weaponarena_random/_mod.inc +++ b/qcsrc/common/mutators/mutator/weaponarena_random/_mod.inc @@ -1,2 +1,4 @@ // generated file; do not modify -#include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/mutators/mutator/weaponarena_random/_mod.qh b/qcsrc/common/mutators/mutator/weaponarena_random/_mod.qh index 68d6a24c42..05b87c73d3 100644 --- a/qcsrc/common/mutators/mutator/weaponarena_random/_mod.qh +++ b/qcsrc/common/mutators/mutator/weaponarena_random/_mod.qh @@ -1,2 +1,4 @@ // generated file; do not modify -#include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/mutators/mutator/weaponarena_random/module.inc b/qcsrc/common/mutators/mutator/weaponarena_random/module.inc deleted file mode 100644 index b7a5f66901..0000000000 --- a/qcsrc/common/mutators/mutator/weaponarena_random/module.inc +++ /dev/null @@ -1,3 +0,0 @@ -#ifdef SVQC -#include "weaponarena_random.qc" -#endif diff --git a/qcsrc/common/mutators/mutator/weaponarena_random/sv_weaponarena_random.qc b/qcsrc/common/mutators/mutator/weaponarena_random/sv_weaponarena_random.qc new file mode 100644 index 0000000000..7ac4504ec9 --- /dev/null +++ b/qcsrc/common/mutators/mutator/weaponarena_random/sv_weaponarena_random.qc @@ -0,0 +1,14 @@ +#include "sv_weaponarena_random.qh" + +// WEAPONTODO: rename the cvars +REGISTER_MUTATOR(weaponarena_random, true); + +MUTATOR_HOOKFUNCTION(weaponarena_random, PlayerSpawn) +{ + if (!g_weaponarena_random) return; + entity player = M_ARGV(0, entity); + + if (g_weaponarena_random_with_blaster) player.weapons &= ~WEPSET(BLASTER); + W_RandomWeapons(player, g_weaponarena_random); + if (g_weaponarena_random_with_blaster) player.weapons |= WEPSET(BLASTER); +} diff --git a/qcsrc/common/mutators/mutator/weaponarena_random/sv_weaponarena_random.qh b/qcsrc/common/mutators/mutator/weaponarena_random/sv_weaponarena_random.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/mutators/mutator/weaponarena_random/sv_weaponarena_random.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mutators/mutator/weaponarena_random/weaponarena_random.qc b/qcsrc/common/mutators/mutator/weaponarena_random/weaponarena_random.qc deleted file mode 100644 index e4d400db70..0000000000 --- a/qcsrc/common/mutators/mutator/weaponarena_random/weaponarena_random.qc +++ /dev/null @@ -1,15 +0,0 @@ -#ifdef IMPLEMENTATION -// WEAPONTODO: rename the cvars -REGISTER_MUTATOR(weaponarena_random, true); - -MUTATOR_HOOKFUNCTION(weaponarena_random, PlayerSpawn) -{ - if (!g_weaponarena_random) return; - entity player = M_ARGV(0, entity); - - if (g_weaponarena_random_with_blaster) player.weapons &= ~WEPSET(BLASTER); - W_RandomWeapons(player, g_weaponarena_random); - if (g_weaponarena_random_with_blaster) player.weapons |= WEPSET(BLASTER); -} - -#endif diff --git a/qcsrc/common/physics/movetypes/follow.qh b/qcsrc/common/physics/movetypes/follow.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/physics/movetypes/follow.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/physics/movetypes/step.qh b/qcsrc/common/physics/movetypes/step.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/physics/movetypes/step.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/physics/movetypes/toss.qh b/qcsrc/common/physics/movetypes/toss.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/physics/movetypes/toss.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/physics/movetypes/walk.qh b/qcsrc/common/physics/movetypes/walk.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/physics/movetypes/walk.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/stats.qh b/qcsrc/common/stats.qh index a24243bb6d..61a4bfeaed 100644 --- a/qcsrc/common/stats.qh +++ b/qcsrc/common/stats.qh @@ -1,7 +1,7 @@ #pragma once #ifdef SVQC -#include +#include #endif // Full list of all stat constants, included in a single location for easy reference diff --git a/qcsrc/common/triggers/func/bobbing.qh b/qcsrc/common/triggers/func/bobbing.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/triggers/func/bobbing.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/triggers/func/button.qh b/qcsrc/common/triggers/func/button.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/triggers/func/button.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/triggers/func/conveyor.qh b/qcsrc/common/triggers/func/conveyor.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/triggers/func/conveyor.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/triggers/func/door_rotating.qh b/qcsrc/common/triggers/func/door_rotating.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/triggers/func/door_rotating.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/triggers/func/door_secret.qh b/qcsrc/common/triggers/func/door_secret.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/triggers/func/door_secret.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/triggers/func/fourier.qh b/qcsrc/common/triggers/func/fourier.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/triggers/func/fourier.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/triggers/func/pendulum.qh b/qcsrc/common/triggers/func/pendulum.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/triggers/func/pendulum.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/triggers/func/plat.qh b/qcsrc/common/triggers/func/plat.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/triggers/func/plat.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/triggers/func/pointparticles.qh b/qcsrc/common/triggers/func/pointparticles.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/triggers/func/pointparticles.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/triggers/func/rainsnow.qh b/qcsrc/common/triggers/func/rainsnow.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/triggers/func/rainsnow.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/triggers/func/rotating.qh b/qcsrc/common/triggers/func/rotating.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/triggers/func/rotating.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/triggers/func/stardust.qh b/qcsrc/common/triggers/func/stardust.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/triggers/func/stardust.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/triggers/func/vectormamamam.qh b/qcsrc/common/triggers/func/vectormamamam.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/triggers/func/vectormamamam.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/triggers/misc/corner.qh b/qcsrc/common/triggers/misc/corner.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/triggers/misc/corner.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/triggers/misc/follow.qh b/qcsrc/common/triggers/misc/follow.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/triggers/misc/follow.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/triggers/misc/include.qh b/qcsrc/common/triggers/misc/include.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/triggers/misc/include.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/triggers/misc/laser.qh b/qcsrc/common/triggers/misc/laser.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/triggers/misc/laser.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/triggers/misc/teleport_dest.qh b/qcsrc/common/triggers/misc/teleport_dest.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/triggers/misc/teleport_dest.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/triggers/target/changelevel.qh b/qcsrc/common/triggers/target/changelevel.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/triggers/target/changelevel.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/triggers/target/location.qh b/qcsrc/common/triggers/target/location.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/triggers/target/location.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/triggers/target/spawn.qh b/qcsrc/common/triggers/target/spawn.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/triggers/target/spawn.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/triggers/target/speaker.qh b/qcsrc/common/triggers/target/speaker.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/triggers/target/speaker.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/triggers/target/voicescript.qh b/qcsrc/common/triggers/target/voicescript.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/triggers/target/voicescript.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/triggers/trigger/counter.qh b/qcsrc/common/triggers/trigger/counter.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/triggers/trigger/counter.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/triggers/trigger/delay.qh b/qcsrc/common/triggers/trigger/delay.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/triggers/trigger/delay.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/triggers/trigger/disablerelay.qh b/qcsrc/common/triggers/trigger/disablerelay.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/triggers/trigger/disablerelay.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/triggers/trigger/flipflop.qh b/qcsrc/common/triggers/trigger/flipflop.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/triggers/trigger/flipflop.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/triggers/trigger/gamestart.qh b/qcsrc/common/triggers/trigger/gamestart.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/triggers/trigger/gamestart.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/triggers/trigger/gravity.qh b/qcsrc/common/triggers/trigger/gravity.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/triggers/trigger/gravity.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/triggers/trigger/heal.qh b/qcsrc/common/triggers/trigger/heal.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/triggers/trigger/heal.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/triggers/trigger/hurt.qh b/qcsrc/common/triggers/trigger/hurt.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/triggers/trigger/hurt.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/triggers/trigger/magicear.qh b/qcsrc/common/triggers/trigger/magicear.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/triggers/trigger/magicear.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/triggers/trigger/monoflop.qh b/qcsrc/common/triggers/trigger/monoflop.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/triggers/trigger/monoflop.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/triggers/trigger/multivibrator.qh b/qcsrc/common/triggers/trigger/multivibrator.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/triggers/trigger/multivibrator.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/triggers/trigger/relay.qh b/qcsrc/common/triggers/trigger/relay.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/triggers/trigger/relay.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/triggers/trigger/relay_activators.qh b/qcsrc/common/triggers/trigger/relay_activators.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/triggers/trigger/relay_activators.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/triggers/trigger/relay_if.qh b/qcsrc/common/triggers/trigger/relay_if.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/triggers/trigger/relay_if.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/triggers/trigger/relay_teamcheck.qh b/qcsrc/common/triggers/trigger/relay_teamcheck.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/triggers/trigger/relay_teamcheck.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/triggers/trigger/teleport.qh b/qcsrc/common/triggers/trigger/teleport.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/triggers/trigger/teleport.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/turrets/_mod.inc b/qcsrc/common/turrets/_mod.inc index 40c3114bab..8fff9535c5 100644 --- a/qcsrc/common/turrets/_mod.inc +++ b/qcsrc/common/turrets/_mod.inc @@ -1,8 +1,13 @@ // generated file; do not modify #include #include -#include #include -#include #include +#include +#ifdef CSQC + #include +#endif +#ifdef SVQC + #include +#endif #include diff --git a/qcsrc/common/turrets/_mod.qh b/qcsrc/common/turrets/_mod.qh index 6da539e852..06978f1d41 100644 --- a/qcsrc/common/turrets/_mod.qh +++ b/qcsrc/common/turrets/_mod.qh @@ -1,8 +1,13 @@ // generated file; do not modify #include #include -#include #include -#include #include +#include +#ifdef CSQC + #include +#endif +#ifdef SVQC + #include +#endif #include diff --git a/qcsrc/common/turrets/checkpoint.qh b/qcsrc/common/turrets/checkpoint.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/turrets/checkpoint.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/turrets/cl_turrets.qh b/qcsrc/common/turrets/cl_turrets.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/turrets/cl_turrets.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/turrets/targettrigger.qh b/qcsrc/common/turrets/targettrigger.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/turrets/targettrigger.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/turrets/turrets.qc b/qcsrc/common/turrets/turrets.qc new file mode 100644 index 0000000000..e69de29bb2 diff --git a/qcsrc/common/turrets/turrets.qh b/qcsrc/common/turrets/turrets.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/turrets/turrets.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/vehicles/_mod.inc b/qcsrc/common/vehicles/_mod.inc index 269858f847..ed26659daa 100644 --- a/qcsrc/common/vehicles/_mod.inc +++ b/qcsrc/common/vehicles/_mod.inc @@ -1,4 +1,9 @@ // generated file; do not modify #include -#include -#include +#include +#ifdef CSQC + #include +#endif +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/vehicles/_mod.qh b/qcsrc/common/vehicles/_mod.qh index d21e829f7a..4892b0f317 100644 --- a/qcsrc/common/vehicles/_mod.qh +++ b/qcsrc/common/vehicles/_mod.qh @@ -1,4 +1,9 @@ // generated file; do not modify #include -#include -#include +#include +#ifdef CSQC + #include +#endif +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/vehicles/vehicles.qc b/qcsrc/common/vehicles/vehicles.qc new file mode 100644 index 0000000000..e69de29bb2 diff --git a/qcsrc/common/vehicles/vehicles.qh b/qcsrc/common/vehicles/vehicles.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/vehicles/vehicles.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/weapons/weapon/arc.qh b/qcsrc/common/weapons/weapon/arc.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/weapons/weapon/arc.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/weapons/weapon/blaster.qh b/qcsrc/common/weapons/weapon/blaster.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/weapons/weapon/blaster.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/weapons/weapon/crylink.qh b/qcsrc/common/weapons/weapon/crylink.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/weapons/weapon/crylink.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/weapons/weapon/devastator.qh b/qcsrc/common/weapons/weapon/devastator.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/weapons/weapon/devastator.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/weapons/weapon/electro.qh b/qcsrc/common/weapons/weapon/electro.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/weapons/weapon/electro.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/weapons/weapon/fireball.qh b/qcsrc/common/weapons/weapon/fireball.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/weapons/weapon/fireball.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/weapons/weapon/hagar.qh b/qcsrc/common/weapons/weapon/hagar.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/weapons/weapon/hagar.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/weapons/weapon/hlac.qh b/qcsrc/common/weapons/weapon/hlac.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/weapons/weapon/hlac.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/weapons/weapon/hook.qh b/qcsrc/common/weapons/weapon/hook.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/weapons/weapon/hook.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/weapons/weapon/machinegun.qh b/qcsrc/common/weapons/weapon/machinegun.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/weapons/weapon/machinegun.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/weapons/weapon/minelayer.qh b/qcsrc/common/weapons/weapon/minelayer.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/weapons/weapon/minelayer.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/weapons/weapon/mortar.qh b/qcsrc/common/weapons/weapon/mortar.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/weapons/weapon/mortar.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/weapons/weapon/porto.qh b/qcsrc/common/weapons/weapon/porto.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/weapons/weapon/porto.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/weapons/weapon/rifle.qh b/qcsrc/common/weapons/weapon/rifle.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/weapons/weapon/rifle.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/weapons/weapon/seeker.qh b/qcsrc/common/weapons/weapon/seeker.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/weapons/weapon/seeker.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/weapons/weapon/shockwave.qh b/qcsrc/common/weapons/weapon/shockwave.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/weapons/weapon/shockwave.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/weapons/weapon/shotgun.qh b/qcsrc/common/weapons/weapon/shotgun.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/weapons/weapon/shotgun.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/weapons/weapon/tuba.qh b/qcsrc/common/weapons/weapon/tuba.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/weapons/weapon/tuba.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/weapons/weapon/vaporizer.qh b/qcsrc/common/weapons/weapon/vaporizer.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/weapons/weapon/vaporizer.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/weapons/weapon/vortex.qh b/qcsrc/common/weapons/weapon/vortex.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/common/weapons/weapon/vortex.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/lib/angle.qh b/qcsrc/lib/angle.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/lib/angle.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/lib/csqcmodel/_mod.inc b/qcsrc/lib/csqcmodel/_mod.inc index a2012c8fa2..cd9569e501 100644 --- a/qcsrc/lib/csqcmodel/_mod.inc +++ b/qcsrc/lib/csqcmodel/_mod.inc @@ -1,5 +1,13 @@ // generated file; do not modify -#include -#include #include -#include +#include +#ifdef CSQC + #include +#endif +#ifdef SVQC + #include +#endif +#include +#ifdef CSQC + #include +#endif diff --git a/qcsrc/lib/csqcmodel/_mod.qh b/qcsrc/lib/csqcmodel/_mod.qh index de3102fa85..1b05351928 100644 --- a/qcsrc/lib/csqcmodel/_mod.qh +++ b/qcsrc/lib/csqcmodel/_mod.qh @@ -1,5 +1,13 @@ // generated file; do not modify -#include -#include #include -#include +#include +#ifdef CSQC + #include +#endif +#ifdef SVQC + #include +#endif +#include +#ifdef CSQC + #include +#endif diff --git a/qcsrc/lib/csqcmodel/model.qc b/qcsrc/lib/csqcmodel/model.qc new file mode 100644 index 0000000000..e69de29bb2 diff --git a/qcsrc/lib/csqcmodel/model.qh b/qcsrc/lib/csqcmodel/model.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/lib/csqcmodel/model.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/lib/csqcmodel/player.qc b/qcsrc/lib/csqcmodel/player.qc new file mode 100644 index 0000000000..e69de29bb2 diff --git a/qcsrc/lib/csqcmodel/player.qh b/qcsrc/lib/csqcmodel/player.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/lib/csqcmodel/player.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/lib/json.qh b/qcsrc/lib/json.qh new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/qcsrc/lib/json.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/server/_mod.inc b/qcsrc/server/_mod.inc index f22742f1dd..4ecc610c7d 100644 --- a/qcsrc/server/_mod.inc +++ b/qcsrc/server/_mod.inc @@ -3,20 +3,20 @@ #include #include #include -#include -#include -#include +#include #include #include #include #include #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -25,7 +25,9 @@ #include #include #include -#include +#ifdef SVQC + #include +#endif #include #include #include diff --git a/qcsrc/server/_mod.qh b/qcsrc/server/_mod.qh index 17e0a829d4..8162606c78 100644 --- a/qcsrc/server/_mod.qh +++ b/qcsrc/server/_mod.qh @@ -3,20 +3,20 @@ #include #include #include -#include -#include -#include +#include #include #include #include #include #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -25,7 +25,9 @@ #include #include #include -#include +#ifdef SVQC + #include +#endif #include #include #include diff --git a/qcsrc/server/bot/default/bot.qc b/qcsrc/server/bot/default/bot.qc index 3f117de841..7b742e5aa9 100644 --- a/qcsrc/server/bot/default/bot.qc +++ b/qcsrc/server/bot/default/bot.qc @@ -15,7 +15,7 @@ #include "../../antilag.qh" #include "../../autocvars.qh" #include "../../campaign.qh" -#include "../../cl_client.qh" +#include "../../client.qh" #include "../../constants.qh" #include "../../defs.qh" #include "../../race.qh" diff --git a/qcsrc/server/cl_client.qc b/qcsrc/server/cl_client.qc deleted file mode 100644 index a280b275e4..0000000000 --- a/qcsrc/server/cl_client.qc +++ /dev/null @@ -1,2596 +0,0 @@ -#include "cl_client.qh" - -#include "anticheat.qh" -#include "cl_impulse.qh" -#include "cl_player.qh" -#include "ipban.qh" -#include "miscfunctions.qh" -#include "portals.qh" -#include "teamplay.qh" -#include "playerdemo.qh" -#include "spawnpoints.qh" -#include "g_damage.qh" -#include "g_hook.qh" -#include "command/common.qh" -#include "cheats.qh" -#include "g_world.qh" -#include "race.qh" -#include "antilag.qh" -#include "campaign.qh" -#include "command/common.qh" - -#include "bot/api.qh" - -#include "../common/ent_cs.qh" -#include - -#include - -#include "../common/triggers/teleporters.qh" - -#include "../common/vehicles/all.qh" - -#include "weapons/hitplot.qh" -#include "weapons/weaponsystem.qh" - -#include "../common/net_notice.qh" -#include "../common/physics/player.qh" - -#include "../common/items/all.qc" - -#include "../common/mutators/mutator/waypoints/all.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/items/inventory.qh" - -#include "../common/monsters/sv_monsters.qh" - -#include "../lib/warpzone/server.qh" - -STATIC_METHOD(Client, Add, void(Client this, int _team)) -{ - ClientConnect(this); - TRANSMUTE(Player, this); - this.frame = 12; // 7 - this.team = _team; - PutClientInServer(this); -} - -void PutObserverInServer(entity this); - -STATIC_METHOD(Client, Remove, void(Client this)) -{ - TRANSMUTE(Observer, this); - PutClientInServer(this); - ClientDisconnect(this); -} - -void send_CSQC_teamnagger() { - WriteHeader(MSG_BROADCAST, TE_CSQC_TEAMNAGGER); -} - -int CountSpectators(entity player, entity to) -{ - if(!player) { return 0; } // not sure how, but best to be safe - - int spec_count = 0; - - FOREACH_CLIENT(IS_REAL_CLIENT(it) && IS_SPEC(it) && it != to && it.enemy == player, - { - spec_count++; - }); - - return spec_count; -} - -void WriteSpectators(entity player, entity to) -{ - if(!player) { return; } // not sure how, but best to be safe - - FOREACH_CLIENT(IS_REAL_CLIENT(it) && IS_SPEC(it) && it != to && it.enemy == player, - { - WriteByte(MSG_ENTITY, num_for_edict(it)); - }); -} - -bool ClientData_Send(entity this, entity to, int sf) -{ - assert(to == this.owner, return false); - - entity e = to; - if (IS_SPEC(e)) e = e.enemy; - - sf = 0; - if (e.race_completed) sf |= 1; // forced scoreboard - if (to.spectatee_status) sf |= 2; // spectator ent number follows - if (e.zoomstate) sf |= 4; // zoomed - if (e.porto_v_angle_held) sf |= 8; // angles held - if (autocvar_sv_showspectators) sf |= 16; // show spectators - - WriteHeader(MSG_ENTITY, ENT_CLIENT_CLIENTDATA); - WriteByte(MSG_ENTITY, sf); - - if (sf & 2) - { - WriteByte(MSG_ENTITY, to.spectatee_status); - } - if (sf & 8) - { - WriteAngle(MSG_ENTITY, e.v_angle.x); - WriteAngle(MSG_ENTITY, e.v_angle.y); - } - - if(sf & 16) - { - float specs = CountSpectators(e, to); - WriteByte(MSG_ENTITY, specs); - WriteSpectators(e, to); - } - - return true; -} - -void ClientData_Attach(entity this) -{ - Net_LinkEntity(this.clientdata = new_pure(clientdata), false, 0, ClientData_Send); - this.clientdata.drawonlytoclient = this; - this.clientdata.owner = this; -} - -void ClientData_Detach(entity this) -{ - delete(this.clientdata); - this.clientdata = NULL; -} - -void ClientData_Touch(entity e) -{ - e.clientdata.SendFlags = 1; - - // make it spectatable - FOREACH_CLIENT(IS_REAL_CLIENT(it) && it != e && IS_SPEC(it) && it.enemy == e, LAMBDA(it.clientdata.SendFlags = 1)); -} - -.string netname_previous; - -void SetSpectatee(entity player, entity spectatee); - - -/* -============= -CheckPlayerModel - -Checks if the argument string can be a valid playermodel. -Returns a valid one in doubt. -============= -*/ -string FallbackPlayerModel; -string CheckPlayerModel(string plyermodel) { - if(FallbackPlayerModel != cvar_defstring("_cl_playermodel")) - { - // note: we cannot summon Don Strunzone here, some player may - // still have the model string set. In case anyone manages how - // to change a cvar default, we'll have a small leak here. - FallbackPlayerModel = strzone(cvar_defstring("_cl_playermodel")); - } - // only in right path - if( substring(plyermodel,0,14) != "models/player/") - return FallbackPlayerModel; - // only good file extensions - if(substring(plyermodel,-4,4) != ".zym") - if(substring(plyermodel,-4,4) != ".dpm") - if(substring(plyermodel,-4,4) != ".iqm") - if(substring(plyermodel,-4,4) != ".md3") - if(substring(plyermodel,-4,4) != ".psk") - return FallbackPlayerModel; - // forbid the LOD models - if(substring(plyermodel, -9,5) == "_lod1") - return FallbackPlayerModel; - if(substring(plyermodel, -9,5) == "_lod2") - return FallbackPlayerModel; - if(plyermodel != strtolower(plyermodel)) - return FallbackPlayerModel; - // also, restrict to server models - if(autocvar_sv_servermodelsonly) - { - if(!fexists(plyermodel)) - return FallbackPlayerModel; - } - return plyermodel; -} - -void setplayermodel(entity e, string modelname) -{ - precache_model(modelname); - _setmodel(e, modelname); - player_setupanimsformodel(e); - if(!autocvar_g_debug_globalsounds) - UpdatePlayerSounds(e); -} - -void FixPlayermodel(entity player); -/** putting a client as observer in the server */ -void PutObserverInServer(entity this) -{ - bool mutator_returnvalue = MUTATOR_CALLHOOK(MakePlayerObserver, this); - PlayerState_detach(this); - - if (IS_PLAYER(this) && this.health >= 1) { - // despawn effect - Send_Effect(EFFECT_SPAWN_NEUTRAL, this.origin, '0 0 0', 1); - } - - { - entity spot = SelectSpawnPoint(this, true); - if (!spot) LOG_FATAL("No spawnpoints for observers?!?"); - this.angles = spot.angles; - this.angles_z = 0; - this.fixangle = true; - // offset it so that the spectator spawns higher off the ground, looks better this way - setorigin(this, spot.origin + STAT(PL_VIEW_OFS, NULL)); - this.prevorigin = this.origin; - if (IS_REAL_CLIENT(this)) - { - msg_entity = this; - WriteByte(MSG_ONE, SVC_SETVIEW); - WriteEntity(MSG_ONE, this); - } - // give the spectator some space between walls for MOVETYPE_FLY_WORLDONLY - // so that your view doesn't go into the ceiling with MOVETYPE_FLY_WORLDONLY, previously "PL_VIEW_OFS" - if(!autocvar_g_debug_globalsounds) - { - // needed for player sounds - this.model = ""; - FixPlayermodel(this); - } - setmodel(this, MDL_Null); - setsize(this, STAT(PL_CROUCH_MIN, NULL), STAT(PL_CROUCH_MAX, NULL)); - this.view_ofs = '0 0 0'; - } - - RemoveGrapplingHook(this); - Portal_ClearAll(this); - Unfreeze(this); - SetSpectatee(this, NULL); - - if (this.alivetime) - { - if (!warmup_stage) - PS_GR_P_ADDVAL(this, PLAYERSTATS_ALIVETIME, time - this.alivetime); - this.alivetime = 0; - } - - if (this.vehicle) vehicles_exit(this.vehicle, VHEF_RELEASE); - - WaypointSprite_PlayerDead(this); - - if (mutator_returnvalue) { - // mutator prevents resetting teams+score - } else { - this.team = -1; // move this as it is needed to log the player spectating in eventlog - this.frags = FRAGS_SPECTATOR; - PlayerScore_Clear(this); // clear scores when needed - } - - if (this.killcount != FRAGS_SPECTATOR) - { - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_QUIT_SPECTATE, this.netname); - if(!intermission_running) - if(autocvar_g_chat_nospectators == 1 || (!(warmup_stage || gameover) && autocvar_g_chat_nospectators == 2)) - Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_CHAT_NOSPECTATORS); - - if(this.just_joined == false) { - LogTeamchange(this.playerid, -1, 4); - } else - this.just_joined = false; - } - - accuracy_resend(this); - - this.spectatortime = time; - this.bot_attack = false; - this.hud = HUD_NORMAL; - TRANSMUTE(Observer, this); - this.iscreature = false; - this.teleportable = TELEPORT_SIMPLE; - this.damagedbycontents = false; - this.health = FRAGS_SPECTATOR; - this.takedamage = DAMAGE_NO; - this.solid = SOLID_NOT; - set_movetype(this, MOVETYPE_FLY_WORLDONLY); // user preference is controlled by playerprethink - this.flags = FL_CLIENT | FL_NOTARGET; - this.armorvalue = 666; - this.effects = 0; - this.armorvalue = autocvar_g_balance_armor_start; - this.pauserotarmor_finished = 0; - this.pauserothealth_finished = 0; - this.pauseregen_finished = 0; - this.damageforcescale = 0; - this.death_time = 0; - this.respawn_flags = 0; - this.respawn_time = 0; - this.stat_respawn_time = 0; - this.alpha = 0; - this.scale = 0; - this.fade_time = 0; - this.pain_frame = 0; - this.pain_finished = 0; - this.strength_finished = 0; - this.invincible_finished = 0; - this.superweapons_finished = 0; - this.pushltime = 0; - this.istypefrag = 0; - setthink(this, func_null); - this.nextthink = 0; - this.hook_time = 0; - this.deadflag = DEAD_NO; - this.crouch = false; - this.revival_time = 0; - - this.items = 0; - this.weapons = '0 0 0'; - this.drawonlytoclient = this; - - this.weaponname = ""; - this.weaponmodel = ""; - for (int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) - { - this.weaponentities[slot] = NULL; - } - this.exteriorweaponentity = NULL; - this.killcount = FRAGS_SPECTATOR; - this.velocity = '0 0 0'; - this.avelocity = '0 0 0'; - this.punchangle = '0 0 0'; - this.punchvector = '0 0 0'; - this.oldvelocity = this.velocity; - this.fire_endtime = -1; - this.event_damage = func_null; - - STAT(ACTIVEWEAPON, this) = WEP_Null.m_id; - STAT(SWITCHINGWEAPON, this) = WEP_Null.m_id; - STAT(SWITCHWEAPON, this) = WEP_Null.m_id; -} - -int player_getspecies(entity this) -{ - get_model_parameters(this.model, this.skin); - int s = get_model_parameters_species; - get_model_parameters(string_null, 0); - if (s < 0) return SPECIES_HUMAN; - return s; -} - -.float model_randomizer; -void FixPlayermodel(entity player) -{ - string defaultmodel = ""; - int defaultskin = 0; - if(autocvar_sv_defaultcharacter) - { - if(teamplay) - { - string s = Static_Team_ColorName_Lower(player.team); - if (s != "neutral") - { - defaultmodel = cvar_string(strcat("sv_defaultplayermodel_", s)); - defaultskin = cvar(strcat("sv_defaultplayerskin_", s)); - } - } - - if(defaultmodel == "") - { - defaultmodel = autocvar_sv_defaultplayermodel; - defaultskin = autocvar_sv_defaultplayerskin; - } - - int n = tokenize_console(defaultmodel); - if(n > 0) - { - defaultmodel = argv(floor(n * player.model_randomizer)); - // However, do NOT randomize if the player-selected model is in the list. - for (int i = 0; i < n; ++i) - if ((argv(i) == player.playermodel && defaultskin == stof(player.playerskin)) || argv(i) == strcat(player.playermodel, ":", player.playerskin)) - defaultmodel = argv(i); - } - - int i = strstrofs(defaultmodel, ":", 0); - if(i >= 0) - { - defaultskin = stof(substring(defaultmodel, i+1, -1)); - defaultmodel = substring(defaultmodel, 0, i); - } - } - if(autocvar_sv_defaultcharacterskin && !defaultskin) - { - if(teamplay) - { - string s = Static_Team_ColorName_Lower(player.team); - if (s != "neutral") - defaultskin = cvar(strcat("sv_defaultplayerskin_", s)); - } - - if(!defaultskin) - defaultskin = autocvar_sv_defaultplayerskin; - } - - MUTATOR_CALLHOOK(FixPlayermodel, defaultmodel, defaultskin, player); - defaultmodel = M_ARGV(0, string); - defaultskin = M_ARGV(1, int); - - bool chmdl = false; - int oldskin; - if(defaultmodel != "") - { - if (defaultmodel != player.model) - { - vector m1 = player.mins; - vector m2 = player.maxs; - setplayermodel (player, defaultmodel); - setsize (player, m1, m2); - chmdl = true; - } - - oldskin = player.skin; - player.skin = defaultskin; - } else { - if (player.playermodel != player.model || player.playermodel == "") - { - player.playermodel = CheckPlayerModel(player.playermodel); // this is never "", so no endless loop - vector m1 = player.mins; - vector m2 = player.maxs; - setplayermodel (player, player.playermodel); - setsize (player, m1, m2); - chmdl = true; - } - - if(!autocvar_sv_defaultcharacterskin) - { - oldskin = player.skin; - player.skin = stof(player.playerskin); - } - else - { - oldskin = player.skin; - player.skin = defaultskin; - } - } - - if(chmdl || oldskin != player.skin) // model or skin has changed - { - player.species = player_getspecies(player); // update species - if(!autocvar_g_debug_globalsounds) - UpdatePlayerSounds(player); // update skin sounds - } - - if(!teamplay) - if(strlen(autocvar_sv_defaultplayercolors)) - if(player.clientcolors != stof(autocvar_sv_defaultplayercolors)) - setcolor(player, stof(autocvar_sv_defaultplayercolors)); -} - - -/** Called when a client spawns in the server */ -void PutClientInServer(entity this) -{ - if (IS_BOT_CLIENT(this)) { - TRANSMUTE(Player, this); - } else if (IS_REAL_CLIENT(this)) { - msg_entity = this; - WriteByte(MSG_ONE, SVC_SETVIEW); - WriteEntity(MSG_ONE, this); - } - if (gameover) { - TRANSMUTE(Observer, this); - } - - SetSpectatee(this, NULL); - - // reset player keys - this.itemkeys = 0; - - MUTATOR_CALLHOOK(PutClientInServer, this); - - if (IS_OBSERVER(this)) { - PutObserverInServer(this); - } else if (IS_PLAYER(this)) { - if (this.vehicle) vehicles_exit(this.vehicle, VHEF_RELEASE); - - PlayerState_attach(this); - accuracy_resend(this); - - if (this.team < 0) - JoinBestTeam(this, false, true); - - entity spot = SelectSpawnPoint(this, false); - if (!spot) { - Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_JOIN_NOSPAWNS); - return; // spawn failed - } - - TRANSMUTE(Player, this); - this.wasplayer = true; - this.iscreature = true; - this.teleportable = TELEPORT_NORMAL; - this.damagedbycontents = true; - set_movetype(this, MOVETYPE_WALK); - this.solid = SOLID_SLIDEBOX; - this.dphitcontentsmask = DPCONTENTS_BODY | DPCONTENTS_SOLID; - if (autocvar_g_playerclip_collisions) - this.dphitcontentsmask |= DPCONTENTS_PLAYERCLIP; - if (IS_BOT_CLIENT(this) && autocvar_g_botclip_collisions) - this.dphitcontentsmask |= DPCONTENTS_BOTCLIP; - this.frags = FRAGS_PLAYER; - if (INDEPENDENT_PLAYERS) MAKE_INDEPENDENT_PLAYER(this); - this.flags = FL_CLIENT | FL_PICKUPITEMS; - if (autocvar__notarget) - this.flags |= FL_NOTARGET; - this.takedamage = DAMAGE_AIM; - this.effects = EF_TELEPORT_BIT | EF_RESTARTANIM_BIT; - this.dmg = 2; // WTF - - if (warmup_stage) { - this.ammo_shells = warmup_start_ammo_shells; - this.ammo_nails = warmup_start_ammo_nails; - this.ammo_rockets = warmup_start_ammo_rockets; - this.ammo_cells = warmup_start_ammo_cells; - this.ammo_plasma = warmup_start_ammo_plasma; - this.ammo_fuel = warmup_start_ammo_fuel; - this.health = warmup_start_health; - this.armorvalue = warmup_start_armorvalue; - this.weapons = WARMUP_START_WEAPONS; - } else { - this.ammo_shells = start_ammo_shells; - this.ammo_nails = start_ammo_nails; - this.ammo_rockets = start_ammo_rockets; - this.ammo_cells = start_ammo_cells; - this.ammo_plasma = start_ammo_plasma; - this.ammo_fuel = start_ammo_fuel; - this.health = start_health; - this.armorvalue = start_armorvalue; - this.weapons = start_weapons; - } - - this.superweapons_finished = (this.weapons & WEPSET_SUPERWEAPONS) ? time + autocvar_g_balance_superweapons_time : 0; - - this.items = start_items; - - this.spawnshieldtime = time + autocvar_g_spawnshieldtime; - this.pauserotarmor_finished = time + autocvar_g_balance_pause_armor_rot_spawn; - this.pauserothealth_finished = time + autocvar_g_balance_pause_health_rot_spawn; - this.pauserotfuel_finished = time + autocvar_g_balance_pause_fuel_rot_spawn; - this.pauseregen_finished = time + autocvar_g_balance_pause_health_regen_spawn; - // extend the pause of rotting if client was reset at the beginning of the countdown - if (!autocvar_sv_ready_restart_after_countdown && time < game_starttime) { // TODO why is this cvar NOTted? - float f = game_starttime - time; - this.spawnshieldtime += f; - this.pauserotarmor_finished += f; - this.pauserothealth_finished += f; - this.pauseregen_finished += f; - } - this.damageforcescale = 2; - this.death_time = 0; - this.respawn_flags = 0; - this.respawn_time = 0; - this.stat_respawn_time = 0; - this.scale = autocvar_sv_player_scale; - this.fade_time = 0; - this.pain_frame = 0; - this.pain_finished = 0; - this.pushltime = 0; - setthink(this, func_null); // players have no think function - this.nextthink = 0; - this.dmg_team = 0; - this.ballistics_density = autocvar_g_ballistics_density_player; - - this.deadflag = DEAD_NO; - - this.angles = spot.angles; - this.angles_z = 0; // never spawn tilted even if the spot says to - if (IS_BOT_CLIENT(this)) - this.v_angle = this.angles; - this.fixangle = true; // turn this way immediately - this.oldvelocity = this.velocity = '0 0 0'; - this.avelocity = '0 0 0'; - this.punchangle = '0 0 0'; - this.punchvector = '0 0 0'; - - this.strength_finished = 0; - this.invincible_finished = 0; - this.fire_endtime = -1; - this.revival_time = 0; - this.air_finished = time + 12; - - entity spawnevent = new_pure(spawnevent); - spawnevent.owner = this; - Net_LinkEntity(spawnevent, false, 0.5, SpawnEvent_Send); - - // Cut off any still running player sounds. - stopsound(this, CH_PLAYER_SINGLE); - - this.model = ""; - FixPlayermodel(this); - this.drawonlytoclient = NULL; - - this.crouch = false; - this.view_ofs = STAT(PL_VIEW_OFS, NULL); - setsize(this, STAT(PL_MIN, NULL), STAT(PL_MAX, NULL)); - this.spawnorigin = spot.origin; - setorigin(this, spot.origin + '0 0 1' * (1 - this.mins.z - 24)); - // don't reset back to last position, even if new position is stuck in solid - this.oldorigin = this.origin; - this.prevorigin = this.origin; - this.lastteleporttime = time; // prevent insane speeds due to changing origin - this.conveyor = NULL; // prevent conveyors at the previous location from moving a freshly spawned player - this.hud = HUD_NORMAL; - - this.event_damage = PlayerDamage; - - this.bot_attack = true; - this.monster_attack = true; - - PHYS_INPUT_BUTTON_ATCK(this) = PHYS_INPUT_BUTTON_JUMP(this) = PHYS_INPUT_BUTTON_ATCK2(this) = false; - - if (this.killcount == FRAGS_SPECTATOR) { - PlayerScore_Clear(this); - this.killcount = 0; - } - - for (int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) - { - CL_SpawnWeaponentity(this, weaponentities[slot]); - } - this.alpha = default_player_alpha; - this.colormod = '1 1 1' * autocvar_g_player_brightness; - this.exteriorweaponentity.alpha = default_weapon_alpha; - - this.speedrunning = false; - - target_voicescript_clear(this); - - // reset fields the weapons may use - FOREACH(Weapons, true, LAMBDA( - it.wr_resetplayer(it, this); - // reload all reloadable weapons - if (it.spawnflags & WEP_FLAG_RELOADABLE) { - this.weapon_load[it.m_id] = it.reloading_ammo; - } - )); - - { - string s = spot.target; - spot.target = string_null; - SUB_UseTargets(spot, this, NULL); - spot.target = s; - } - - Unfreeze(this); - - MUTATOR_CALLHOOK(PlayerSpawn, this, spot); - - if (autocvar_spawn_debug) - { - sprint(this, strcat("spawnpoint origin: ", vtos(spot.origin), "\n")); - delete(spot); // usefull for checking if there are spawnpoints, that let drop through the floor - } - - PS(this).m_switchweapon = w_getbestweapon(this); - this.cnt = -1; // W_LastWeapon will not complain - PS(this).m_weapon = WEP_Null; - this.weaponname = ""; - PS(this).m_switchingweapon = WEP_Null; - - if (!warmup_stage && !this.alivetime) - this.alivetime = time; - - antilag_clear(this, CS(this)); - } -} - -void ClientInit_misc(entity this); - -.float ebouncefactor, ebouncestop; // electro's values -// TODO do we need all these fields, or should we stop autodetecting runtime -// changes and just have a console command to update this? -bool ClientInit_SendEntity(entity this, entity to, int sf) -{ - WriteHeader(MSG_ENTITY, _ENT_CLIENT_INIT); - return = true; - msg_entity = to; - // MSG_INIT replacement - // TODO: make easier to use - Registry_send_all(); - W_PROP_reload(MSG_ONE, to); - ClientInit_misc(this); - MUTATOR_CALLHOOK(Ent_Init); -} -void ClientInit_misc(entity this) -{ - int channel = MSG_ONE; - WriteHeader(channel, ENT_CLIENT_INIT); - WriteByte(channel, g_nexball_meter_period * 32); - WriteInt24_t(channel, compressShotOrigin(hook_shotorigin[0])); - WriteInt24_t(channel, compressShotOrigin(hook_shotorigin[1])); - WriteInt24_t(channel, compressShotOrigin(hook_shotorigin[2])); - WriteInt24_t(channel, compressShotOrigin(hook_shotorigin[3])); - WriteInt24_t(channel, compressShotOrigin(arc_shotorigin[0])); - WriteInt24_t(channel, compressShotOrigin(arc_shotorigin[1])); - WriteInt24_t(channel, compressShotOrigin(arc_shotorigin[2])); - WriteInt24_t(channel, compressShotOrigin(arc_shotorigin[3])); - - if(sv_foginterval && world.fog != "") - WriteString(channel, world.fog); - else - WriteString(channel, ""); - WriteByte(channel, this.count * 255.0); // g_balance_armor_blockpercent - WriteByte(channel, serverflags); // client has to know if it should zoom or not - WriteCoord(channel, autocvar_g_trueaim_minrange); -} - -void ClientInit_CheckUpdate(entity this) -{ - this.nextthink = time; - if(this.count != autocvar_g_balance_armor_blockpercent) - { - this.count = autocvar_g_balance_armor_blockpercent; - this.SendFlags |= 1; - } -} - -void ClientInit_Spawn() -{ - entity e = new_pure(clientinit); - setthink(e, ClientInit_CheckUpdate); - Net_LinkEntity(e, false, 0, ClientInit_SendEntity); - - ClientInit_CheckUpdate(e); -} - -/* -============= -SetNewParms -============= -*/ -void SetNewParms () -{ - // initialize parms for a new player - parm1 = -(86400 * 366); - - MUTATOR_CALLHOOK(SetNewParms); -} - -/* -============= -SetChangeParms -============= -*/ -void SetChangeParms (entity this) -{ - // save parms for level change - parm1 = this.parm_idlesince - time; - - MUTATOR_CALLHOOK(SetChangeParms); -} - -/* -============= -DecodeLevelParms -============= -*/ -void DecodeLevelParms(entity this) -{ - // load parms - this.parm_idlesince = parm1; - if (this.parm_idlesince == -(86400 * 366)) - this.parm_idlesince = time; - - // whatever happens, allow 60 seconds of idling directly after connect for map loading - this.parm_idlesince = max(this.parm_idlesince, time - sv_maxidle + 60); - - MUTATOR_CALLHOOK(DecodeLevelParms); -} - -/* -============= -ClientKill - -Called when a client types 'kill' in the console -============= -*/ - -.float clientkill_nexttime; -void ClientKill_Now_TeamChange(entity this) -{ - if(this.killindicator_teamchange == -1) - { - JoinBestTeam( this, false, true ); - } - else if(this.killindicator_teamchange == -2) - { - if(blockSpectators) - Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_SPECTATE_WARNING, autocvar_g_maxplayers_spectator_blocktime); - PutObserverInServer(this); - } - else - SV_ChangeTeam(this, this.killindicator_teamchange - 1); - this.killindicator_teamchange = 0; -} - -void ClientKill_Now(entity this) -{ - if(this.vehicle) - { - vehicles_exit(this.vehicle, VHEF_RELEASE); - if(!this.killindicator_teamchange) - { - this.vehicle_health = -1; - Damage(this, this, this, 1 , DEATH_KILL.m_id, this.origin, '0 0 0'); - } - } - - if(this.killindicator && !wasfreed(this.killindicator)) - delete(this.killindicator); - - this.killindicator = NULL; - - if(this.killindicator_teamchange) - ClientKill_Now_TeamChange(this); - - if(!IS_SPEC(this) && !IS_OBSERVER(this)) - Damage(this, this, this, 100000, DEATH_KILL.m_id, this.origin, '0 0 0'); - - // now I am sure the player IS dead -} -void KillIndicator_Think(entity this) -{ - if (gameover) - { - this.owner.killindicator = NULL; - delete(this); - return; - } - - if (this.owner.alpha < 0 && !this.owner.vehicle) - { - this.owner.killindicator = NULL; - delete(this); - return; - } - - if(this.cnt <= 0) - { - ClientKill_Now(this.owner); - return; - } - else if(g_cts && this.health == 1) // health == 1 means that it's silent - { - this.nextthink = time + 1; - this.cnt -= 1; - } - else - { - if(this.cnt <= 10) - setmodel(this, MDL_NUM(this.cnt)); - if(IS_REAL_CLIENT(this.owner)) - { - if(this.cnt <= 10) - { Send_Notification(NOTIF_ONE, this.owner, MSG_ANNCE, Announcer_PickNumber(CNT_KILL, this.cnt)); } - } - this.nextthink = time + 1; - this.cnt -= 1; - } -} - -float clientkilltime; -void ClientKill_TeamChange (entity this, float targetteam) // 0 = don't change, -1 = auto, -2 = spec -{ - float killtime; - float starttime; - - if (gameover) - return; - - killtime = autocvar_g_balance_kill_delay; - - if(g_race_qualifying || g_cts) - killtime = 0; - - if(MUTATOR_CALLHOOK(ClientKill, this, killtime)) - return; - - this.killindicator_teamchange = targetteam; - - if(!this.killindicator) - { - if(!IS_DEAD(this)) - { - killtime = max(killtime, this.clientkill_nexttime - time); - this.clientkill_nexttime = time + killtime + autocvar_g_balance_kill_antispam; - } - - if(killtime <= 0 || !IS_PLAYER(this) || IS_DEAD(this)) - { - ClientKill_Now(this); - } - else - { - starttime = max(time, clientkilltime); - - this.killindicator = spawn(); - this.killindicator.owner = this; - this.killindicator.scale = 0.5; - setattachment(this.killindicator, this, ""); - setorigin(this.killindicator, '0 0 52'); - setthink(this.killindicator, KillIndicator_Think); - this.killindicator.nextthink = starttime + (this.lip) * 0.05; - clientkilltime = max(clientkilltime, this.killindicator.nextthink + 0.05); - this.killindicator.cnt = ceil(killtime); - this.killindicator.count = bound(0, ceil(killtime), 10); - //sprint(this, strcat("^1You'll be dead in ", ftos(this.killindicator.cnt), " seconds\n")); - - FOREACH_ENTITY_ENT(enemy, this, - { - if(it.classname != "body") - continue; - it.killindicator = spawn(); - it.killindicator.owner = it; - it.killindicator.scale = 0.5; - setattachment(it.killindicator, it, ""); - setorigin(it.killindicator, '0 0 52'); - setthink(it.killindicator, KillIndicator_Think); - it.killindicator.nextthink = starttime + (it.lip) * 0.05; - //clientkilltime = max(clientkilltime, it.killindicator.nextthink + 0.05); - it.killindicator.cnt = ceil(killtime); - }); - this.lip = 0; - } - } - if(this.killindicator) - { - if(targetteam == 0) // just die - { - this.killindicator.colormod = '0 0 0'; - if(IS_REAL_CLIENT(this)) - if(this.killindicator.cnt > 0) - Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_TEAMCHANGE_SUICIDE, this.killindicator.cnt); - } - else if(targetteam == -1) // auto - { - this.killindicator.colormod = '0 1 0'; - if(IS_REAL_CLIENT(this)) - if(this.killindicator.cnt > 0) - Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_TEAMCHANGE_AUTO, this.killindicator.cnt); - } - else if(targetteam == -2) // spectate - { - this.killindicator.colormod = '0.5 0.5 0.5'; - if(IS_REAL_CLIENT(this)) - if(this.killindicator.cnt > 0) - Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_TEAMCHANGE_SPECTATE, this.killindicator.cnt); - } - else - { - this.killindicator.colormod = Team_ColorRGB(targetteam); - if(IS_REAL_CLIENT(this)) - if(this.killindicator.cnt > 0) - Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, APP_TEAM_NUM(targetteam, CENTER_TEAMCHANGE), this.killindicator.cnt); - } - } - -} - -void ClientKill (entity this) -{ - if(gameover) return; - if(this.player_blocked) return; - if(STAT(FROZEN, this)) return; - - ClientKill_TeamChange(this, 0); -} - -void FixClientCvars(entity e) -{ - // send prediction settings to the client - stuffcmd(e, "\nin_bindmap 0 0\n"); - if(autocvar_g_antilag == 3) // client side hitscan - stuffcmd(e, "cl_cmd settemp cl_prydoncursor_notrace 0\n"); - if(autocvar_sv_gentle) - stuffcmd(e, "cl_cmd settemp cl_gentle 1\n"); - - MUTATOR_CALLHOOK(FixClientCvars, e); -} - -float PlayerInIDList(entity p, string idlist) -{ - float n, i; - string s; - - // NOTE: we do NOT check crypto_idfp_signed here, an unsigned ID is fine too for this - if (!p.crypto_idfp) - return 0; - - // this function allows abbreviated player IDs too! - n = tokenize_console(idlist); - for(i = 0; i < n; ++i) - { - s = argv(i); - if(s == substring(p.crypto_idfp, 0, strlen(s))) - return 1; - } - - return 0; -} - -#ifdef DP_EXT_PRECONNECT -/* -============= -ClientPreConnect - -Called once (not at each match start) when a client begins a connection to the server -============= -*/ -void ClientPreConnect () -{ENGINE_EVENT(); - if(autocvar_sv_eventlog) - { - GameLogEcho(sprintf(":connect:%d:%d:%s", - this.playerid, - etof(this), - ((IS_REAL_CLIENT(this)) ? this.netaddress : "bot") - )); - } -} -#endif - -/** -============= -ClientConnect - -Called when a client connects to the server -============= -*/ -void ClientConnect(entity this) -{ - if (Ban_MaybeEnforceBanOnce(this)) return; - assert(!IS_CLIENT(this), return); - this.flags |= FL_CLIENT; - assert(player_count >= 0, player_count = 0); - -#ifdef WATERMARK - Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_WATERMARK, WATERMARK); -#endif - this.version_nagtime = time + 10 + random() * 10; - TRANSMUTE(Client, this); - - // identify the right forced team - if (autocvar_g_campaign) - { - if (IS_REAL_CLIENT(this)) // only players, not bots - { - switch (autocvar_g_campaign_forceteam) - { - case 1: this.team_forced = NUM_TEAM_1; break; - case 2: this.team_forced = NUM_TEAM_2; break; - case 3: this.team_forced = NUM_TEAM_3; break; - case 4: this.team_forced = NUM_TEAM_4; break; - default: this.team_forced = 0; - } - } - } - else if (PlayerInIDList(this, autocvar_g_forced_team_red)) this.team_forced = NUM_TEAM_1; - else if (PlayerInIDList(this, autocvar_g_forced_team_blue)) this.team_forced = NUM_TEAM_2; - else if (PlayerInIDList(this, autocvar_g_forced_team_yellow)) this.team_forced = NUM_TEAM_3; - else if (PlayerInIDList(this, autocvar_g_forced_team_pink)) this.team_forced = NUM_TEAM_4; - else switch (autocvar_g_forced_team_otherwise) - { - default: this.team_forced = 0; break; - case "red": this.team_forced = NUM_TEAM_1; break; - case "blue": this.team_forced = NUM_TEAM_2; break; - case "yellow": this.team_forced = NUM_TEAM_3; break; - case "pink": this.team_forced = NUM_TEAM_4; break; - case "spectate": - case "spectator": - this.team_forced = -1; - break; - } - if (!teamplay && this.team_forced > 0) this.team_forced = 0; - - { - int id = this.playerid; - this.playerid = 0; // silent - JoinBestTeam(this, false, false); // if the team number is valid, keep it - this.playerid = id; - } - - if (autocvar_sv_spectate || autocvar_g_campaign || this.team_forced < 0) { - TRANSMUTE(Observer, this); - } else { - if (!teamplay || autocvar_g_balance_teams) { - TRANSMUTE(Player, this); - campaign_bots_may_start = true; - } else { - TRANSMUTE(Observer, this); // do it anyway - } - } - - PlayerStats_GameReport_AddEvent(sprintf("kills-%d", this.playerid)); - - // always track bots, don't ask for cl_allow_uidtracking - if (IS_BOT_CLIENT(this)) PlayerStats_GameReport_AddPlayer(this); - - if (autocvar_sv_eventlog) - GameLogEcho(strcat(":join:", ftos(this.playerid), ":", ftos(etof(this)), ":", ((IS_REAL_CLIENT(this)) ? this.netaddress : "bot"), ":", this.netname)); - - LogTeamchange(this.playerid, this.team, 1); - - this.just_joined = true; // stop spamming the eventlog with additional lines when the client connects - - this.netname_previous = strzone(this.netname); - - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, ((teamplay && IS_PLAYER(this)) ? APP_TEAM_ENT(this, INFO_JOIN_CONNECT_TEAM) : INFO_JOIN_CONNECT), this.netname); - - stuffcmd(this, clientstuff, "\n"); - stuffcmd(this, "cl_particles_reloadeffects\n"); // TODO do we still need this? - - FixClientCvars(this); - - // get version info from player - stuffcmd(this, "cmd clientversion $gameversion\n"); - - // notify about available teams - if (teamplay) - { - CheckAllowedTeams(this); - int t = 0; - if (c1 >= 0) t |= BIT(0); - if (c2 >= 0) t |= BIT(1); - if (c3 >= 0) t |= BIT(2); - if (c4 >= 0) t |= BIT(3); - stuffcmd(this, sprintf("set _teams_available %d\n", t)); - } - else - { - stuffcmd(this, "set _teams_available 0\n"); - } - - bot_relinkplayerlist(); - - this.spectatortime = time; - if (blockSpectators) - { - Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_SPECTATE_WARNING, autocvar_g_maxplayers_spectator_blocktime); - } - - this.jointime = time; - this.allowed_timeouts = autocvar_sv_timeout_number; - - if (IS_REAL_CLIENT(this)) - { - if (!autocvar_g_campaign) - { - this.motd_actived_time = -1; - Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_MOTD, getwelcomemessage(this)); - } - - if (g_weaponarena_weapons == WEPSET(TUBA)) - stuffcmd(this, "cl_cmd settemp chase_active 1\n"); - } - - if (!sv_foginterval && world.fog != "") - stuffcmd(this, strcat("\nfog ", world.fog, "\nr_fog_exp2 0\nr_drawfog 1\n")); - - if (autocvar_sv_teamnagger && !(autocvar_bot_vs_human && AvailableTeams() == 2)) - if (!g_ca && !g_cts && !g_race) // teamnagger is currently bad for ca, race & cts - send_CSQC_teamnagger(); - - CSQCMODEL_AUTOINIT(this); - - this.model_randomizer = random(); - - if (IS_REAL_CLIENT(this)) - sv_notice_join(this); - - FOREACH_ENTITY_FLOAT(init_for_player_needed, true, { - it.init_for_player(it, this); - }); - - MUTATOR_CALLHOOK(ClientConnect, this); -} -/* -============= -ClientDisconnect - -Called when a client disconnects from the server -============= -*/ -.entity chatbubbleentity; -void ReadyCount(); -void ClientDisconnect(entity this) -{ - assert(IS_CLIENT(this), return); - - PlayerStats_GameReport_FinalizePlayer(this); - if (this.vehicle) vehicles_exit(this.vehicle, VHEF_RELEASE); - if (this.active_minigame) part_minigame(this); - if (IS_PLAYER(this)) Send_Effect(EFFECT_SPAWN_NEUTRAL, this.origin, '0 0 0', 1); - - if (autocvar_sv_eventlog) - GameLogEcho(strcat(":part:", ftos(this.playerid))); - - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_QUIT_DISCONNECT, this.netname); - - SetSpectatee(this, NULL); - - MUTATOR_CALLHOOK(ClientDisconnect, this); - - ClientState_detach(this); - - Portal_ClearAll(this); - - Unfreeze(this); - - RemoveGrapplingHook(this); - - // Here, everything has been done that requires this player to be a client. - - this.flags &= ~FL_CLIENT; - - if (this.chatbubbleentity) delete(this.chatbubbleentity); - if (this.killindicator) delete(this.killindicator); - - WaypointSprite_PlayerGone(this); - - bot_relinkplayerlist(); - - if (this.netname_previous) strunzone(this.netname_previous); - if (this.clientstatus) strunzone(this.clientstatus); - if (this.weaponorder_byimpulse) strunzone(this.weaponorder_byimpulse); - if (this.personal) delete(this.personal); - - this.playerid = 0; - ReadyCount(); - if (vote_called && IS_REAL_CLIENT(this)) VoteCount(false); -} - -void ChatBubbleThink(entity this) -{ - this.nextthink = time; - if ((this.owner.alpha < 0) || this.owner.chatbubbleentity != this) - { - if(this.owner) // but why can that ever be NULL? - this.owner.chatbubbleentity = NULL; - delete(this); - return; - } - - this.mdl = ""; - - if ( !IS_DEAD(this.owner) && IS_PLAYER(this.owner) ) - { - if ( this.owner.active_minigame ) - this.mdl = "models/sprites/minigame_busy.iqm"; - else if (PHYS_INPUT_BUTTON_CHAT(this.owner)) - this.mdl = "models/misc/chatbubble.spr"; - } - - if ( this.model != this.mdl ) - _setmodel(this, this.mdl); - -} - -void UpdateChatBubble(entity this) -{ - if (this.alpha < 0) - return; - // spawn a chatbubble entity if needed - if (!this.chatbubbleentity) - { - this.chatbubbleentity = new(chatbubbleentity); - this.chatbubbleentity.owner = this; - this.chatbubbleentity.exteriormodeltoclient = this; - setthink(this.chatbubbleentity, ChatBubbleThink); - this.chatbubbleentity.nextthink = time; - setmodel(this.chatbubbleentity, MDL_CHAT); // precision set below - //setorigin(this.chatbubbleentity, this.origin + '0 0 15' + this.maxs_z * '0 0 1'); - setorigin(this.chatbubbleentity, '0 0 15' + this.maxs_z * '0 0 1'); - setattachment(this.chatbubbleentity, this, ""); // sticks to moving player better, also conserves bandwidth - this.chatbubbleentity.mdl = this.chatbubbleentity.model; - //this.chatbubbleentity.model = ""; - this.chatbubbleentity.effects = EF_LOWPRECISION; - } -} - - -// LordHavoc: this hack will be removed when proper _pants/_shirt layers are -// added to the model skins -/*void UpdateColorModHack() -{ - float c; - c = this.clientcolors & 15; - // LordHavoc: only bothering to support white, green, red, yellow, blue - if (!teamplay) this.colormod = '0 0 0'; - else if (c == 0) this.colormod = '1.00 1.00 1.00'; - else if (c == 3) this.colormod = '0.10 1.73 0.10'; - else if (c == 4) this.colormod = '1.73 0.10 0.10'; - else if (c == 12) this.colormod = '1.22 1.22 0.10'; - else if (c == 13) this.colormod = '0.10 0.10 1.73'; - else this.colormod = '1 1 1'; -}*/ - -void respawn(entity this) -{ - if(this.alpha >= 0 && autocvar_g_respawn_ghosts) - { - this.solid = SOLID_NOT; - this.takedamage = DAMAGE_NO; - set_movetype(this, MOVETYPE_FLY); - this.velocity = '0 0 1' * autocvar_g_respawn_ghosts_speed; - this.avelocity = randomvec() * autocvar_g_respawn_ghosts_speed * 3 - randomvec() * autocvar_g_respawn_ghosts_speed * 3; - this.effects |= CSQCMODEL_EF_RESPAWNGHOST; - Send_Effect(EFFECT_RESPAWN_GHOST, this.origin, '0 0 0', 1); - if(autocvar_g_respawn_ghosts_maxtime) - SUB_SetFade (this, time + autocvar_g_respawn_ghosts_maxtime / 2 + random () * (autocvar_g_respawn_ghosts_maxtime - autocvar_g_respawn_ghosts_maxtime / 2), 1.5); - } - - CopyBody(this, 1); - - this.effects |= EF_NODRAW; // prevent another CopyBody - PutClientInServer(this); -} - -void play_countdown(entity this, float finished, Sound samp) -{ - TC(Sound, samp); - if(IS_REAL_CLIENT(this)) - if(floor(finished - time - frametime) != floor(finished - time)) - if(finished - time < 6) - sound (this, CH_INFO, samp, VOL_BASE, ATTEN_NORM); -} - -void player_powerups(entity this) -{ - // add a way to see what the items were BEFORE all of these checks for the mutator hook - int items_prev = this.items; - - if((this.items & IT_USING_JETPACK) && !IS_DEAD(this) && !gameover) - this.modelflags |= MF_ROCKET; - else - this.modelflags &= ~MF_ROCKET; - - this.effects &= ~(EF_RED | EF_BLUE | EF_ADDITIVE | EF_FULLBRIGHT | EF_FLAME | EF_NODEPTHTEST); - - if((this.alpha < 0 || IS_DEAD(this)) && !this.vehicle) // don't apply the flags if the player is gibbed - return; - - Fire_ApplyDamage(this); - Fire_ApplyEffect(this); - - if (!g_instagib) - { - if (this.items & ITEM_Strength.m_itemid) - { - play_countdown(this, this.strength_finished, SND_POWEROFF); - this.effects = this.effects | (EF_BLUE | EF_ADDITIVE | EF_FULLBRIGHT); - if (time > this.strength_finished) - { - this.items = this.items - (this.items & ITEM_Strength.m_itemid); - //Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_POWERDOWN_STRENGTH, this.netname); - Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_POWERDOWN_STRENGTH); - } - } - else - { - if (time < this.strength_finished) - { - this.items = this.items | ITEM_Strength.m_itemid; - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_POWERUP_STRENGTH, this.netname); - Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_POWERUP_STRENGTH); - } - } - if (this.items & ITEM_Shield.m_itemid) - { - play_countdown(this, this.invincible_finished, SND_POWEROFF); - this.effects = this.effects | (EF_RED | EF_ADDITIVE | EF_FULLBRIGHT); - if (time > this.invincible_finished) - { - this.items = this.items - (this.items & ITEM_Shield.m_itemid); - //Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_POWERDOWN_SHIELD, this.netname); - Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_POWERDOWN_SHIELD); - } - } - else - { - if (time < this.invincible_finished) - { - this.items = this.items | ITEM_Shield.m_itemid; - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_POWERUP_SHIELD, this.netname); - Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_POWERUP_SHIELD); - } - } - if (this.items & IT_SUPERWEAPON) - { - if (!(this.weapons & WEPSET_SUPERWEAPONS)) - { - this.superweapons_finished = 0; - this.items = this.items - (this.items & IT_SUPERWEAPON); - //Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SUPERWEAPON_LOST, this.netname); - Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_SUPERWEAPON_LOST); - } - else if (this.items & IT_UNLIMITED_SUPERWEAPONS) - { - // don't let them run out - } - else - { - play_countdown(this, this.superweapons_finished, SND_POWEROFF); - if (time > this.superweapons_finished) - { - this.items = this.items - (this.items & IT_SUPERWEAPON); - this.weapons &= ~WEPSET_SUPERWEAPONS; - //Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SUPERWEAPON_BROKEN, this.netname); - Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_SUPERWEAPON_BROKEN); - } - } - } - else if(this.weapons & WEPSET_SUPERWEAPONS) - { - if (time < this.superweapons_finished || (this.items & IT_UNLIMITED_SUPERWEAPONS)) - { - this.items = this.items | IT_SUPERWEAPON; - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SUPERWEAPON_PICKUP, this.netname); - Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_SUPERWEAPON_PICKUP); - } - else - { - this.superweapons_finished = 0; - this.weapons &= ~WEPSET_SUPERWEAPONS; - } - } - else - { - this.superweapons_finished = 0; - } - } - - if(autocvar_g_nodepthtestplayers) - this.effects = this.effects | EF_NODEPTHTEST; - - if(autocvar_g_fullbrightplayers) - this.effects = this.effects | EF_FULLBRIGHT; - - if (time >= game_starttime) - if (time < this.spawnshieldtime) - this.effects = this.effects | (EF_ADDITIVE | EF_FULLBRIGHT); - - MUTATOR_CALLHOOK(PlayerPowerups, this, items_prev); -} - -float CalcRegen(float current, float stable, float regenfactor, float regenframetime) -{ - if(current > stable) - return current; - else if(current > stable - 0.25) // when close enough, "snap" - return stable; - else - return min(stable, current + (stable - current) * regenfactor * regenframetime); -} - -float CalcRot(float current, float stable, float rotfactor, float rotframetime) -{ - if(current < stable) - return current; - else if(current < stable + 0.25) // when close enough, "snap" - return stable; - else - return max(stable, current + (stable - current) * rotfactor * rotframetime); -} - -float CalcRotRegen(float current, float regenstable, float regenfactor, float regenlinear, float regenframetime, float rotstable, float rotfactor, float rotlinear, float rotframetime, float limit) -{ - if(current > rotstable) - { - if(rotframetime > 0) - { - current = CalcRot(current, rotstable, rotfactor, rotframetime); - current = max(rotstable, current - rotlinear * rotframetime); - } - } - else if(current < regenstable) - { - if(regenframetime > 0) - { - current = CalcRegen(current, regenstable, regenfactor, regenframetime); - current = min(regenstable, current + regenlinear * regenframetime); - } - } - - if(current > limit) - current = limit; - - return current; -} - -void player_regen(entity this) -{ - float max_mod, regen_mod, rot_mod, limit_mod; - max_mod = regen_mod = rot_mod = limit_mod = 1; - - float regen_health = autocvar_g_balance_health_regen; - float regen_health_linear = autocvar_g_balance_health_regenlinear; - float regen_health_rot = autocvar_g_balance_health_rot; - float regen_health_rotlinear = autocvar_g_balance_health_rotlinear; - float regen_health_stable = autocvar_g_balance_health_regenstable; - float regen_health_rotstable = autocvar_g_balance_health_rotstable; - bool mutator_returnvalue = MUTATOR_CALLHOOK(PlayerRegen, this, max_mod, regen_mod, rot_mod, limit_mod, regen_health, regen_health_linear, regen_health_rot, - regen_health_rotlinear, regen_health_stable, regen_health_rotstable); - max_mod = M_ARGV(1, float); - regen_mod = M_ARGV(2, float); - rot_mod = M_ARGV(3, float); - limit_mod = M_ARGV(4, float); - regen_health = M_ARGV(5, float); - regen_health_linear = M_ARGV(6, float); - regen_health_rot = M_ARGV(7, float); - regen_health_rotlinear = M_ARGV(8, float); - regen_health_stable = M_ARGV(9, float); - regen_health_rotstable = M_ARGV(10, float); - - - if(!mutator_returnvalue) - if(!STAT(FROZEN, this)) - { - float mina, maxa, limith, limita; - maxa = autocvar_g_balance_armor_rotstable; - mina = autocvar_g_balance_armor_regenstable; - limith = autocvar_g_balance_health_limit; - limita = autocvar_g_balance_armor_limit; - - regen_health_rotstable = regen_health_rotstable * max_mod; - regen_health_stable = regen_health_stable * max_mod; - limith = limith * limit_mod; - limita = limita * limit_mod; - - this.armorvalue = CalcRotRegen(this.armorvalue, mina, autocvar_g_balance_armor_regen, autocvar_g_balance_armor_regenlinear, regen_mod * frametime * (time > this.pauseregen_finished), maxa, autocvar_g_balance_armor_rot, autocvar_g_balance_armor_rotlinear, rot_mod * frametime * (time > this.pauserotarmor_finished), limita); - this.health = CalcRotRegen(this.health, regen_health_stable, regen_health, regen_health_linear, regen_mod * frametime * (time > this.pauseregen_finished), regen_health_rotstable, regen_health_rot, regen_health_rotlinear, rot_mod * frametime * (time > this.pauserothealth_finished), limith); - } - - // if player rotted to death... die! - // check this outside above checks, as player may still be able to rot to death - if(this.health < 1) - { - if(this.vehicle) - vehicles_exit(this.vehicle, VHEF_RELEASE); - if(this.event_damage) - this.event_damage(this, this, this, 1, DEATH_ROT.m_id, this.origin, '0 0 0'); - } - - if (!(this.items & IT_UNLIMITED_WEAPON_AMMO)) - { - float minf, maxf, limitf; - - maxf = autocvar_g_balance_fuel_rotstable; - minf = autocvar_g_balance_fuel_regenstable; - limitf = autocvar_g_balance_fuel_limit; - - this.ammo_fuel = CalcRotRegen(this.ammo_fuel, minf, autocvar_g_balance_fuel_regen, autocvar_g_balance_fuel_regenlinear, frametime * (time > this.pauseregen_finished) * ((this.items & ITEM_JetpackRegen.m_itemid) != 0), maxf, autocvar_g_balance_fuel_rot, autocvar_g_balance_fuel_rotlinear, frametime * (time > this.pauserotfuel_finished), limitf); - } -} - -bool zoomstate_set; -void SetZoomState(entity this, float z) -{ - if(z != this.zoomstate) - { - this.zoomstate = z; - ClientData_Touch(this); - } - zoomstate_set = true; -} - -void GetPressedKeys(entity this) -{ - MUTATOR_CALLHOOK(GetPressedKeys, this); - int keys = this.pressedkeys; - keys = BITSET(keys, KEY_FORWARD, this.movement.x > 0); - keys = BITSET(keys, KEY_BACKWARD, this.movement.x < 0); - keys = BITSET(keys, KEY_RIGHT, this.movement.y > 0); - keys = BITSET(keys, KEY_LEFT, this.movement.y < 0); - - keys = BITSET(keys, KEY_JUMP, PHYS_INPUT_BUTTON_JUMP(this)); - keys = BITSET(keys, KEY_CROUCH, PHYS_INPUT_BUTTON_CROUCH(this)); - keys = BITSET(keys, KEY_ATCK, PHYS_INPUT_BUTTON_ATCK(this)); - keys = BITSET(keys, KEY_ATCK2, PHYS_INPUT_BUTTON_ATCK2(this)); - this.pressedkeys = keys; -} - -/* -====================== -spectate mode routines -====================== -*/ - -void SpectateCopy(entity this, entity spectatee) -{ - TC(Client, this); TC(Client, spectatee); - - MUTATOR_CALLHOOK(SpectateCopy, spectatee, this); - PS(this) = PS(spectatee); - this.armortype = spectatee.armortype; - this.armorvalue = spectatee.armorvalue; - this.ammo_cells = spectatee.ammo_cells; - this.ammo_plasma = spectatee.ammo_plasma; - this.ammo_shells = spectatee.ammo_shells; - this.ammo_nails = spectatee.ammo_nails; - this.ammo_rockets = spectatee.ammo_rockets; - this.ammo_fuel = spectatee.ammo_fuel; - this.clip_load = spectatee.clip_load; - this.clip_size = spectatee.clip_size; - this.effects = spectatee.effects & EFMASK_CHEAP; // eat performance - this.health = spectatee.health; - this.impulse = 0; - this.items = spectatee.items; - this.last_pickup = spectatee.last_pickup; - this.hit_time = spectatee.hit_time; - this.strength_finished = spectatee.strength_finished; - this.invincible_finished = spectatee.invincible_finished; - this.pressedkeys = spectatee.pressedkeys; - this.weapons = spectatee.weapons; - this.vortex_charge = spectatee.vortex_charge; - this.vortex_chargepool_ammo = spectatee.vortex_chargepool_ammo; - this.hagar_load = spectatee.hagar_load; - this.arc_heat_percent = spectatee.arc_heat_percent; - this.minelayer_mines = spectatee.minelayer_mines; - this.punchangle = spectatee.punchangle; - this.view_ofs = spectatee.view_ofs; - this.velocity = spectatee.velocity; - this.dmg_take = spectatee.dmg_take; - this.dmg_save = spectatee.dmg_save; - this.dmg_inflictor = spectatee.dmg_inflictor; - this.v_angle = spectatee.v_angle; - this.angles = spectatee.v_angle; - STAT(FROZEN, this) = STAT(FROZEN, spectatee); - this.revive_progress = spectatee.revive_progress; - if(!PHYS_INPUT_BUTTON_USE(this) && STAT(CAMERA_SPECTATOR, this) != 2) - this.fixangle = true; - setorigin(this, spectatee.origin); - setsize(this, spectatee.mins, spectatee.maxs); - SetZoomState(this, spectatee.zoomstate); - - anticheat_spectatecopy(this, spectatee); - this.hud = spectatee.hud; - if(spectatee.vehicle) - { - this.angles = spectatee.v_angle; - - //this.fixangle = false; - //this.velocity = spectatee.vehicle.velocity; - this.vehicle_health = spectatee.vehicle_health; - this.vehicle_shield = spectatee.vehicle_shield; - this.vehicle_energy = spectatee.vehicle_energy; - this.vehicle_ammo1 = spectatee.vehicle_ammo1; - this.vehicle_ammo2 = spectatee.vehicle_ammo2; - this.vehicle_reload1 = spectatee.vehicle_reload1; - this.vehicle_reload2 = spectatee.vehicle_reload2; - - //msg_entity = this; - - // WriteByte (MSG_ONE, SVC_SETVIEWANGLES); - //WriteAngle(MSG_ONE, spectatee.v_angle.x); - // WriteAngle(MSG_ONE, spectatee.v_angle.y); - // WriteAngle(MSG_ONE, spectatee.v_angle.z); - - //WriteByte (MSG_ONE, SVC_SETVIEW); - // WriteEntity(MSG_ONE, this); - //makevectors(spectatee.v_angle); - //setorigin(this, spectatee.origin - v_forward * 400 + v_up * 300);*/ - } -} - -bool SpectateUpdate(entity this) -{ - if(!this.enemy) - return false; - - if(!IS_PLAYER(this.enemy) || this == this.enemy) - { - SetSpectatee(this, NULL); - return false; - } - - SpectateCopy(this, this.enemy); - - return true; -} - -bool SpectateSet(entity this) -{ - if(!IS_PLAYER(this.enemy)) - return false; - - ClientData_Touch(this.enemy); - - msg_entity = this; - WriteByte(MSG_ONE, SVC_SETVIEW); - WriteEntity(MSG_ONE, this.enemy); - set_movetype(this, MOVETYPE_NONE); - accuracy_resend(this); - - if(!SpectateUpdate(this)) - PutObserverInServer(this); - - return true; -} - -void SetSpectatee(entity this, entity spectatee) -{ - entity old_spectatee = this.enemy; - - this.enemy = spectatee; - - // WEAPONTODO - // these are required to fix the spectator bug with arc - if(old_spectatee) - { - for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) - { - .entity weaponentity = weaponentities[slot]; - if(old_spectatee.(weaponentity).arc_beam) - old_spectatee.(weaponentity).arc_beam.SendFlags |= ARC_SF_SETTINGS; - } - } - if(this.enemy) - { - for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) - { - .entity weaponentity = weaponentities[slot]; - if(this.enemy.(weaponentity).arc_beam) - this.enemy.(weaponentity).arc_beam.SendFlags |= ARC_SF_SETTINGS; - } - } - - // needed to update spectator list - if(old_spectatee) { ClientData_Touch(old_spectatee); } -} - -bool Spectate(entity this, entity pl) -{ - if(MUTATOR_CALLHOOK(SpectateSet, this, pl)) - return false; - pl = M_ARGV(1, entity); - - SetSpectatee(this, pl); - return SpectateSet(this); -} - -bool SpectateNext(entity this) -{ - entity ent = find(this.enemy, classname, STR_PLAYER); - - if (MUTATOR_CALLHOOK(SpectateNext, this, ent)) - ent = M_ARGV(1, entity); - else if (!ent) - ent = find(ent, classname, STR_PLAYER); - - if(ent) { SetSpectatee(this, ent); } - - return SpectateSet(this); -} - -bool SpectatePrev(entity this) -{ - // NOTE: chain order is from the highest to the lower entnum (unlike find) - entity ent = findchain(classname, STR_PLAYER); - if (!ent) // no player - return false; - - entity first = ent; - // skip players until current spectated player - if(this.enemy) - while(ent && ent != this.enemy) - ent = ent.chain; - - switch (MUTATOR_CALLHOOK(SpectatePrev, this, ent, first)) - { - case MUT_SPECPREV_FOUND: - ent = M_ARGV(1, entity); - break; - case MUT_SPECPREV_RETURN: - return true; - case MUT_SPECPREV_CONTINUE: - default: - { - if(ent.chain) - ent = ent.chain; - else - ent = first; - break; - } - } - - SetSpectatee(this, ent); - return SpectateSet(this); -} - -/* -============= -ShowRespawnCountdown() - -Update a respawn countdown display. -============= -*/ -void ShowRespawnCountdown(entity this) -{ - float number; - if(!IS_DEAD(this)) // just respawned? - return; - else - { - number = ceil(this.respawn_time - time); - if(number <= 0) - return; - if(number <= this.respawn_countdown) - { - this.respawn_countdown = number - 1; - if(ceil(this.respawn_time - (time + 0.5)) == number) // only say it if it is the same number even in 0.5s; to prevent overlapping sounds - { Send_Notification(NOTIF_ONE, this, MSG_ANNCE, Announcer_PickNumber(CNT_RESPAWN, number)); } - } - } -} - -void LeaveSpectatorMode(entity this) -{ - if(this.caplayer) - return; - if(nJoinAllowed(this, this)) - { - if(!teamplay || autocvar_g_campaign || autocvar_g_balance_teams || (this.wasplayer && autocvar_g_changeteam_banned) || this.team_forced > 0) - { - TRANSMUTE(Player, this); - - SetSpectatee(this, NULL); - - if(autocvar_g_campaign || autocvar_g_balance_teams) - { JoinBestTeam(this, false, true); } - - if(autocvar_g_campaign) - { campaign_bots_may_start = true; } - - Kill_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CPID_PREVENT_JOIN); - - PutClientInServer(this); - - if(IS_PLAYER(this)) { Send_Notification(NOTIF_ALL, NULL, MSG_INFO, ((teamplay && this.team != -1) ? APP_TEAM_ENT(this, INFO_JOIN_PLAY_TEAM) : INFO_JOIN_PLAY), this.netname); } - } - else - stuffcmd(this, "menu_showteamselect\n"); - } - else - { - // Player may not join because g_maxplayers is set - Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_JOIN_PREVENT); - } -} - -/** - * Determines whether the player is allowed to join. This depends on cvar - * g_maxplayers, if it isn't used this function always return true, otherwise - * it checks whether the number of currently playing players exceeds g_maxplayers. - * @return int number of free slots for players, 0 if none - */ -bool nJoinAllowed(entity this, entity ignore) -{ - if(!ignore) - // this is called that way when checking if anyone may be able to join (to build qcstatus) - // so report 0 free slots if restricted - { - if(autocvar_g_forced_team_otherwise == "spectate") - return false; - if(autocvar_g_forced_team_otherwise == "spectator") - return false; - } - - if(this.team_forced < 0) - return false; // forced spectators can never join - - // TODO simplify this - int totalClients = 0; - int currentlyPlaying = 0; - FOREACH_CLIENT(true, LAMBDA( - if(it != ignore) - ++totalClients; - if(IS_REAL_CLIENT(it)) - if(IS_PLAYER(it) || it.caplayer) - ++currentlyPlaying; - )); - - if (!autocvar_g_maxplayers) - return maxclients - totalClients; - - if(currentlyPlaying < autocvar_g_maxplayers) - return min(maxclients - totalClients, autocvar_g_maxplayers - currentlyPlaying); - - return false; -} - -/** - * Checks whether the client is an observer or spectator, if so, he will get kicked after - * g_maxplayers_spectator_blocktime seconds - */ -void checkSpectatorBlock(entity this) -{ - if(IS_SPEC(this) || IS_OBSERVER(this)) - if(!this.caplayer) - if(IS_REAL_CLIENT(this)) - { - if( time > (this.spectatortime + autocvar_g_maxplayers_spectator_blocktime) ) { - Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_QUIT_KICK_SPECTATING); - dropclient(this); - } - } -} - -void PrintWelcomeMessage(entity this) -{ - if(this.motd_actived_time == 0) - { - if (autocvar_g_campaign) { - if ((IS_PLAYER(this) && PHYS_INPUT_BUTTON_INFO(this)) || (!IS_PLAYER(this))) { - this.motd_actived_time = time; - Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_MOTD, campaign_message); - } - } else { - if (PHYS_INPUT_BUTTON_INFO(this)) { - this.motd_actived_time = time; - Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_MOTD, getwelcomemessage(this)); - } - } - } - else if(this.motd_actived_time > 0) // showing MOTD or campaign message - { - if (autocvar_g_campaign) { - if (PHYS_INPUT_BUTTON_INFO(this)) - this.motd_actived_time = time; - else if ((time - this.motd_actived_time > 2) && IS_PLAYER(this)) { // hide it some seconds after BUTTON_INFO has been released - this.motd_actived_time = 0; - Kill_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CPID_MOTD); - } - } else { - if (PHYS_INPUT_BUTTON_INFO(this)) - this.motd_actived_time = time; - else if (time - this.motd_actived_time > 2) { // hide it some seconds after BUTTON_INFO has been released - this.motd_actived_time = 0; - Kill_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CPID_MOTD); - } - } - } - else //if(this.motd_actived_time < 0) // just connected, motd is active - { - if(PHYS_INPUT_BUTTON_INFO(this)) // BUTTON_INFO hides initial MOTD - this.motd_actived_time = -2; // wait until BUTTON_INFO gets released - else if(this.motd_actived_time == -2 || IS_PLAYER(this) || IS_SPEC(this)) - { - // instanctly hide MOTD - this.motd_actived_time = 0; - Kill_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CPID_MOTD); - } - } -} - -void ObserverThink(entity this) -{ - if ( this.impulse ) - { - MinigameImpulse(this, this.impulse); - this.impulse = 0; - } - if (this.flags & FL_JUMPRELEASED) { - if (PHYS_INPUT_BUTTON_JUMP(this) && !this.version_mismatch) { - this.flags &= ~FL_JUMPRELEASED; - this.flags |= FL_SPAWNING; - } else if(PHYS_INPUT_BUTTON_ATCK(this) && !this.version_mismatch) { - this.flags &= ~FL_JUMPRELEASED; - if(SpectateNext(this)) { - TRANSMUTE(Spectator, this); - } - } else { - int preferred_movetype = ((!PHYS_INPUT_BUTTON_USE(this) ? this.cvar_cl_clippedspectating : !this.cvar_cl_clippedspectating) ? MOVETYPE_FLY_WORLDONLY : MOVETYPE_NOCLIP); - set_movetype(this, preferred_movetype); - } - } else { - if (!(PHYS_INPUT_BUTTON_ATCK(this) || PHYS_INPUT_BUTTON_JUMP(this))) { - this.flags |= FL_JUMPRELEASED; - if(this.flags & FL_SPAWNING) - { - this.flags &= ~FL_SPAWNING; - LeaveSpectatorMode(this); - return; - } - } - } -} - -void SpectatorThink(entity this) -{ - if ( this.impulse ) - { - if(MinigameImpulse(this, this.impulse)) - this.impulse = 0; - - if (this.impulse == IMP_weapon_drop.impulse) - { - STAT(CAMERA_SPECTATOR, this) = (STAT(CAMERA_SPECTATOR, this) + 1) % 3; - this.impulse = 0; - return; - } - } - if (this.flags & FL_JUMPRELEASED) { - if (PHYS_INPUT_BUTTON_JUMP(this) && !this.version_mismatch) { - this.flags &= ~FL_JUMPRELEASED; - this.flags |= FL_SPAWNING; - } else if(PHYS_INPUT_BUTTON_ATCK(this) || this.impulse == 10 || this.impulse == 15 || this.impulse == 18 || (this.impulse >= 200 && this.impulse <= 209)) { - this.flags &= ~FL_JUMPRELEASED; - if(SpectateNext(this)) { - TRANSMUTE(Spectator, this); - } else { - TRANSMUTE(Observer, this); - PutClientInServer(this); - } - this.impulse = 0; - } else if(this.impulse == 12 || this.impulse == 16 || this.impulse == 19 || (this.impulse >= 220 && this.impulse <= 229)) { - this.flags &= ~FL_JUMPRELEASED; - if(SpectatePrev(this)) { - TRANSMUTE(Spectator, this); - } else { - TRANSMUTE(Observer, this); - PutClientInServer(this); - } - this.impulse = 0; - } else if (PHYS_INPUT_BUTTON_ATCK2(this)) { - this.flags &= ~FL_JUMPRELEASED; - TRANSMUTE(Observer, this); - PutClientInServer(this); - } else { - if(!SpectateUpdate(this)) - PutObserverInServer(this); - } - } else { - if (!(PHYS_INPUT_BUTTON_ATCK(this) || PHYS_INPUT_BUTTON_ATCK2(this))) { - this.flags |= FL_JUMPRELEASED; - if(this.flags & FL_SPAWNING) - { - this.flags &= ~FL_SPAWNING; - LeaveSpectatorMode(this); - return; - } - } - if(!SpectateUpdate(this)) - PutObserverInServer(this); - } - - this.flags |= FL_CLIENT | FL_NOTARGET; -} - -void vehicles_enter (entity pl, entity veh); -void PlayerUseKey(entity this) -{ - if (!IS_PLAYER(this)) - return; - - if(this.vehicle) - { - if(!gameover) - { - vehicles_exit(this.vehicle, VHEF_NORMAL); - return; - } - } - else if(autocvar_g_vehicles_enter) - { - if(!STAT(FROZEN, this)) - if(!IS_DEAD(this)) - if(!gameover) - { - entity head, closest_target = NULL; - head = WarpZone_FindRadius(this.origin, autocvar_g_vehicles_enter_radius, true); - - while(head) // find the closest acceptable target to enter - { - if(IS_VEHICLE(head)) - if(!IS_DEAD(head)) - if(!head.owner || ((head.vehicle_flags & VHF_MULTISLOT) && SAME_TEAM(head.owner, this))) - if(head.takedamage != DAMAGE_NO) - { - if(closest_target) - { - if(vlen2(this.origin - head.origin) < vlen2(this.origin - closest_target.origin)) - { closest_target = head; } - } - else { closest_target = head; } - } - - head = head.chain; - } - - if(closest_target) { vehicles_enter(this, closest_target); return; } - } - } - - // a use key was pressed; call handlers - MUTATOR_CALLHOOK(PlayerUseKey, this); -} - - -/* -============= -PlayerPreThink - -Called every frame for each client before the physics are run -============= -*/ -.float usekeypressed; -.float last_vehiclecheck; -.int items_added; -void PlayerPreThink (entity this) -{ - WarpZone_PlayerPhysics_FixVAngle(this); - - STAT(GAMESTARTTIME, this) = game_starttime; - STAT(ROUNDSTARTTIME, this) = round_starttime; - STAT(ALLOW_OLDVORTEXBEAM, this) = autocvar_g_allow_oldvortexbeam; - STAT(LEADLIMIT, this) = autocvar_leadlimit; - - STAT(WEAPONSINMAP, this) = weaponsInMap; - - if (frametime) { - // physics frames: update anticheat stuff - anticheat_prethink(this); - } - - if (blockSpectators && frametime) { - // WORKAROUND: only use dropclient in server frames (frametime set). - // Never use it in cl_movement frames (frametime zero). - checkSpectatorBlock(this); - } - - zoomstate_set = false; - - // Check for nameless players - if (isInvisibleString(this.netname)) { - this.netname = strzone(sprintf("Player#%d", this.playerid)); - // stuffcmd(this, strcat("name ", this.netname, "\n")); // maybe? - } - if (this.netname != this.netname_previous) { - if (autocvar_sv_eventlog) { - GameLogEcho(strcat(":name:", ftos(this.playerid), ":", this.netname)); - } - if (this.netname_previous) strunzone(this.netname_previous); - this.netname_previous = strzone(this.netname); - } - - // version nagging - if (this.version_nagtime && this.cvar_g_xonoticversion && time > this.version_nagtime) { - this.version_nagtime = 0; - if (strstrofs(this.cvar_g_xonoticversion, "git", 0) >= 0 || strstrofs(this.cvar_g_xonoticversion, "autobuild", 0) >= 0) { - // git client - } else if (strstrofs(autocvar_g_xonoticversion, "git", 0) >= 0 || strstrofs(autocvar_g_xonoticversion, "autobuild", 0) >= 0) { - // git server - Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_VERSION_BETA, autocvar_g_xonoticversion, this.cvar_g_xonoticversion); - } else { - int r = vercmp(this.cvar_g_xonoticversion, autocvar_g_xonoticversion); - if (r < 0) { // old client - Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_VERSION_OUTDATED, autocvar_g_xonoticversion, this.cvar_g_xonoticversion); - } else if (r > 0) { // old server - Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_VERSION_OLD, autocvar_g_xonoticversion, this.cvar_g_xonoticversion); - } - } - } - - // GOD MODE info - if (!(this.flags & FL_GODMODE) && this.max_armorvalue) - { - Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_GODMODE_OFF, this.max_armorvalue); - this.max_armorvalue = 0; - } - - if (STAT(FROZEN, this) == 2) - { - this.revive_progress = bound(0, this.revive_progress + frametime * this.revive_speed, 1); - this.health = max(1, this.revive_progress * start_health); - this.iceblock.alpha = bound(0.2, 1 - this.revive_progress, 1); - - if (this.revive_progress >= 1) - Unfreeze(this); - } - else if (STAT(FROZEN, this) == 3) - { - this.revive_progress = bound(0, this.revive_progress - frametime * this.revive_speed, 1); - this.health = max(0, autocvar_g_nades_ice_health + (start_health-autocvar_g_nades_ice_health) * this.revive_progress ); - - if (this.health < 1) - { - if (this.vehicle) - vehicles_exit(this.vehicle, VHEF_RELEASE); - if(this.event_damage) - this.event_damage(this, this, this.frozen_by, 1, DEATH_NADE_ICE_FREEZE.m_id, this.origin, '0 0 0'); - } - else if (this.revive_progress <= 0) - Unfreeze(this); - } - - MUTATOR_CALLHOOK(PlayerPreThink, this); - - if(autocvar_g_vehicles_enter && (time > this.last_vehiclecheck) && !gameover && !this.vehicle) - if(IS_PLAYER(this) && !STAT(FROZEN, this) && !IS_DEAD(this)) - { - FOREACH_ENTITY_RADIUS(this.origin, autocvar_g_vehicles_enter_radius, IS_VEHICLE(it), - { - if(!IS_DEAD(it) && it.takedamage != DAMAGE_NO) - if((it.vehicle_flags & VHF_MULTISLOT) && SAME_TEAM(it.owner, this)) - { - Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_VEHICLE_ENTER_GUNNER); - } - else if(!it.owner) - { - if(!it.team || SAME_TEAM(this, it)) - Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_VEHICLE_ENTER); - else if(autocvar_g_vehicles_steal) - Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_VEHICLE_ENTER_STEAL); - } - }); - - this.last_vehiclecheck = time + 1; - } - - if(!this.cvar_cl_newusekeysupported) // FIXME remove this - it was a stupid idea to begin with, we can JUST use the button - { - if(PHYS_INPUT_BUTTON_USE(this) && !this.usekeypressed) - PlayerUseKey(this); - this.usekeypressed = PHYS_INPUT_BUTTON_USE(this); - } - - if (IS_REAL_CLIENT(this)) - PrintWelcomeMessage(this); - - if (IS_PLAYER(this)) { - CheckRules_Player(this); - - if (intermission_running) { - IntermissionThink(this); - return; - } - - if (timeout_status == TIMEOUT_ACTIVE) { - // don't allow the player to turn around while game is paused - // FIXME turn this into CSQC stuff - this.v_angle = this.lastV_angle; - this.angles = this.lastV_angle; - this.fixangle = true; - } - - if (frametime) player_powerups(this); - - if (IS_DEAD(this)) { - if (this.personal && g_race_qualifying) { - if (time > this.respawn_time) { - STAT(RESPAWN_TIME, this) = this.respawn_time = time + 1; // only retry once a second - respawn(this); - this.impulse = CHIMPULSE_SPEEDRUN.impulse; - } - } else { - if (frametime) player_anim(this); - bool button_pressed = (PHYS_INPUT_BUTTON_ATCK(this) || PHYS_INPUT_BUTTON_JUMP(this) || PHYS_INPUT_BUTTON_ATCK2(this) || PHYS_INPUT_BUTTON_HOOK(this) || PHYS_INPUT_BUTTON_USE(this)); - - switch(this.deadflag) - { - case DEAD_DYING: - { - if ((this.respawn_flags & RESPAWN_FORCE) && !(this.respawn_time < this.respawn_time_max)) - this.deadflag = DEAD_RESPAWNING; - else if (!button_pressed || (this.respawn_flags & RESPAWN_FORCE)) - this.deadflag = DEAD_DEAD; - break; - } - case DEAD_DEAD: - { - if (button_pressed) - this.deadflag = DEAD_RESPAWNABLE; - else if (time >= this.respawn_time_max && (this.respawn_flags & RESPAWN_FORCE)) - this.deadflag = DEAD_RESPAWNING; - break; - } - case DEAD_RESPAWNABLE: - { - if (!button_pressed || (this.respawn_flags & RESPAWN_FORCE)) - this.deadflag = DEAD_RESPAWNING; - break; - } - case DEAD_RESPAWNING: - { - if (time > this.respawn_time) - { - this.respawn_time = time + 1; // only retry once a second - this.respawn_time_max = this.respawn_time; - respawn(this); - } - break; - } - } - - ShowRespawnCountdown(this); - - if (this.respawn_flags & RESPAWN_SILENT) - STAT(RESPAWN_TIME, this) = 0; - else if ((this.respawn_flags & RESPAWN_FORCE) && this.respawn_time < this.respawn_time_max) - { - if (time < this.respawn_time) - STAT(RESPAWN_TIME, this) = this.respawn_time; - else if (this.deadflag != DEAD_RESPAWNING) - STAT(RESPAWN_TIME, this) = -this.respawn_time_max; - } - else - STAT(RESPAWN_TIME, this) = this.respawn_time; - } - - // if respawning, invert stat_respawn_time to indicate this, the client translates it - if (this.deadflag == DEAD_RESPAWNING && STAT(RESPAWN_TIME, this) > 0) - STAT(RESPAWN_TIME, this) *= -1; - - return; - } - - this.prevorigin = this.origin; - - bool do_crouch = PHYS_INPUT_BUTTON_CROUCH(this); - if (this.hook.state) { - do_crouch = false; - } else if (this.waterlevel >= WATERLEVEL_SWIMMING) { - do_crouch = false; - } else if (this.vehicle) { - do_crouch = false; - } else if (STAT(FROZEN, this)) { - do_crouch = false; - } - - if (do_crouch) { - if (!this.crouch) { - this.crouch = true; - this.view_ofs = STAT(PL_CROUCH_VIEW_OFS, this); - setsize(this, STAT(PL_CROUCH_MIN, this), STAT(PL_CROUCH_MAX, this)); - // setanim(this, this.anim_duck, false, true, true); // this anim is BROKEN anyway - } - } else if (this.crouch) { - tracebox(this.origin, STAT(PL_MIN, this), STAT(PL_MAX, this), this.origin, false, this); - if (!trace_startsolid) { - this.crouch = false; - this.view_ofs = STAT(PL_VIEW_OFS, this); - setsize(this, STAT(PL_MIN, this), STAT(PL_MAX, this)); - } - } - - FixPlayermodel(this); - - // LordHavoc: allow firing on move frames (sub-ticrate), this gives better timing on slow servers - //if(frametime) - { - this.items &= ~this.items_added; - - //for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) - //{ - //.entity weaponentity = weaponentities[slot]; - //W_WeaponFrame(this, weaponentity); - //} - .entity weaponentity = weaponentities[0]; // TODO - W_WeaponFrame(this, weaponentity); - - this.items_added = 0; - if (this.items & ITEM_Jetpack.m_itemid && (this.items & ITEM_JetpackRegen.m_itemid || this.ammo_fuel >= 0.01)) - this.items_added |= IT_FUEL; - - this.items |= this.items_added; - } - - player_regen(this); - - // WEAPONTODO: Add a weapon request for this - // rot vortex charge to the charge limit - if (WEP_CVAR(vortex, charge_rot_rate) && this.vortex_charge > WEP_CVAR(vortex, charge_limit) && this.vortex_charge_rottime < time) - this.vortex_charge = bound(WEP_CVAR(vortex, charge_limit), this.vortex_charge - WEP_CVAR(vortex, charge_rot_rate) * frametime / W_TICSPERFRAME, 1); - - if (frametime) player_anim(this); - - // secret status - secrets_setstatus(this); - - // monsters status - monsters_setstatus(this); - - this.dmg_team = max(0, this.dmg_team - autocvar_g_teamdamage_resetspeed * frametime); - } - else if (gameover) { - if (intermission_running) IntermissionThink(this); - return; - } - else if (IS_OBSERVER(this)) { - ObserverThink(this); - } - else if (IS_SPEC(this)) { - SpectatorThink(this); - } - - // WEAPONTODO: Add weapon request for this - if (!zoomstate_set) { - SetZoomState(this, - PHYS_INPUT_BUTTON_ZOOM(this) || PHYS_INPUT_BUTTON_ZOOMSCRIPT(this) - || (PHYS_INPUT_BUTTON_ATCK2(this) && PS(this).m_weapon == WEP_VORTEX) - || (PHYS_INPUT_BUTTON_ATCK2(this) && PS(this).m_weapon == WEP_RIFLE && WEP_CVAR(rifle, secondary) == 0) - ); - } - - int oldspectatee_status = this.spectatee_status; - if (IS_SPEC(this)) { - this.spectatee_status = etof(this.enemy); - } else if (IS_OBSERVER(this)) { - this.spectatee_status = etof(this); - } else { - this.spectatee_status = 0; - } - if (this.spectatee_status != oldspectatee_status) { - ClientData_Touch(this); - if (g_race || g_cts) race_InitSpectator(); - } - - if (this.teamkill_soundtime && time > this.teamkill_soundtime) - { - this.teamkill_soundtime = 0; - - entity e = this.teamkill_soundsource; - entity oldpusher = e.pusher; - e.pusher = this; - PlayerSound(e, playersound_teamshoot, CH_VOICE, VOL_BASEVOICE, VOICETYPE_LASTATTACKER_ONLY); - e.pusher = oldpusher; - } - - if (this.taunt_soundtime && time > this.taunt_soundtime) { - this.taunt_soundtime = 0; - PlayerSound(this, playersound_taunt, CH_VOICE, VOL_BASEVOICE, VOICETYPE_AUTOTAUNT); - } - - target_voicescript_next(this); - - // WEAPONTODO: Move into weaponsystem somehow - // if a player goes unarmed after holding a loaded weapon, empty his clip size and remove the crosshair ammo ring - if (PS(this).m_weapon == WEP_Null) - this.clip_load = this.clip_size = 0; -} - -void DrownPlayer(entity this) -{ - if(IS_DEAD(this)) - return; - - if (this.waterlevel != WATERLEVEL_SUBMERGED || this.vehicle) - { - if(this.air_finished < time) - PlayerSound(this, playersound_gasp, CH_PLAYER, VOL_BASE, VOICETYPE_PLAYERSOUND); - this.air_finished = time + autocvar_g_balance_contents_drowndelay; - this.dmg = 2; - } - else if (this.air_finished < time) - { // drown! - if (this.pain_finished < time) - { - Damage (this, NULL, NULL, autocvar_g_balance_contents_playerdamage_drowning * autocvar_g_balance_contents_damagerate, DEATH_DROWN.m_id, this.origin, '0 0 0'); - this.pain_finished = time + 0.5; - } - } -} - -.bool move_qcphysics; - -void Player_Physics(entity this) -{ - set_movetype(this, ((this.move_qcphysics) ? MOVETYPE_NONE : this.move_movetype)); - - if(!this.move_qcphysics) - return; - - int mt = this.move_movetype; - - if(mt == MOVETYPE_PUSH || mt == MOVETYPE_FAKEPUSH || mt == MOVETYPE_PHYSICS) - { - this.move_qcphysics = false; - set_movetype(this, mt); - return; - } - - if(!frametime && !this.pm_frametime) - return; - - Movetype_Physics_NoMatchTicrate(this, this.pm_frametime, true); - - this.pm_frametime = 0; -} - -/* -============= -PlayerPostThink - -Called every frame for each client after the physics are run -============= -*/ -.float idlekick_lasttimeleft; -void PlayerPostThink (entity this) -{ - Player_Physics(this); - - if (sv_maxidle > 0) - if (frametime) // WORKAROUND: only use dropclient in server frames (frametime set). Never use it in cl_movement frames (frametime zero). - if (IS_REAL_CLIENT(this)) - if (IS_PLAYER(this) || sv_maxidle_spectatorsareidle) - { - int totalClients = 0; - if(sv_maxidle_slots > 0) - { - FOREACH_CLIENT(IS_REAL_CLIENT(it) || sv_maxidle_slots_countbots, - { - ++totalClients; - }); - } - - if (sv_maxidle_slots > 0 && (maxclients - totalClients) > sv_maxidle_slots) - { /* do nothing */ } - else if (time - this.parm_idlesince < 1) // instead of (time == this.parm_idlesince) to support sv_maxidle <= 10 - { - if (this.idlekick_lasttimeleft) - { - this.idlekick_lasttimeleft = 0; - Kill_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CPID_IDLING); - } - } - else - { - float timeleft = ceil(sv_maxidle - (time - this.parm_idlesince)); - if (timeleft == min(10, sv_maxidle - 1)) { // - 1 to support sv_maxidle <= 10 - if (!this.idlekick_lasttimeleft) - Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_DISCONNECT_IDLING, timeleft); - } - if (timeleft <= 0) { - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_QUIT_KICK_IDLING, this.netname); - dropclient(this); - return; - } - else if (timeleft <= 10) { - if (timeleft != this.idlekick_lasttimeleft) { - Send_Notification(NOTIF_ONE, this, MSG_ANNCE, Announcer_PickNumber(CNT_IDLE, timeleft)); - } - this.idlekick_lasttimeleft = timeleft; - } - } - } - - CheatFrame(this); - - //CheckPlayerJump(); - - if (IS_PLAYER(this)) { - DrownPlayer(this); - CheckRules_Player(this); - UpdateChatBubble(this); - if (this.impulse) ImpulseCommands(this); - if (intermission_running) return; // intermission or finale - GetPressedKeys(this); - } - - if (this.waypointsprite_attachedforcarrier) { - vector v = healtharmor_maxdamage(this.health, this.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id); - WaypointSprite_UpdateHealth(this.waypointsprite_attachedforcarrier, '1 0 0' * v); - } - - playerdemo_write(this); - - CSQCMODEL_AUTOUPDATE(this); -} diff --git a/qcsrc/server/cl_client.qh b/qcsrc/server/cl_client.qh deleted file mode 100644 index 35ff6e961c..0000000000 --- a/qcsrc/server/cl_client.qh +++ /dev/null @@ -1,109 +0,0 @@ -#pragma once - -void ClientState_attach(entity this); - -CLASS(Client, Object) - /** Client name */ - ATTRIB(Client, netname, string, this.netname); - ATTRIB(Client, colormap, int, this.colormap); - ATTRIB(Client, team, int, this.team); - ATTRIB(Client, clientcolors, int, this.clientcolors); - /** Client IP */ - ATTRIB(Client, netaddress, string, this.netaddress); - ATTRIB(Client, playermodel, string, this.playermodel); - ATTRIB(Client, playerskin, int, this.playerskin); - - /** fingerprint of CA key the player used to authenticate */ - ATTRIB(Client, crypto_keyfp, string, this.crypto_keyfp); - /** fingerprint of CA key the server used to authenticate to the player */ - ATTRIB(Client, crypto_mykeyfp, string, this.crypto_mykeyfp); - /** fingerprint of ID used by the player entity, or string_null if not identified */ - ATTRIB(Client, crypto_idfp, string, this.crypto_idfp); - /** set if the player's ID has been signed */ - ATTRIB(Client, crypto_idfp_signed, bool, this.crypto_idfp_signed); - /** the string "AES128" if encrypting, and string_null if plaintext */ - ATTRIB(Client, crypto_encryptmethod, string, this.crypto_encryptmethod); - /** the string "HMAC-SHA256" if signing, and string_null if plaintext */ - ATTRIB(Client, crypto_signmethod, string, this.crypto_signmethod); - - // custom - - ATTRIB(Client, playerid, int, this.playerid); - - METHOD(Client, m_unwind, bool(Client this)); - - STATIC_METHOD(Client, Add, void(Client this, int _team)); - STATIC_METHOD(Client, Remove, void(Client this)); - - INIT(Client) { - if (this.m_unwind(this)) return this; - make_impure(this); - this.classname = "player_joining"; - static int playerid_last; - this.playerid = ++playerid_last; - ClientState_attach(this); - } - DESTRUCTOR(Client) { - Client_Remove(this); - } - CONSTRUCTOR(Client, string name) { - CONSTRUCT(Client); - this.netname = name; - this.netaddress = "local"; - this.playermodel = "models/player/megaerebus.iqm"; - } -ENDCLASS(Client) - -CLASS(Observer, Client) - INIT(Observer) { - this.classname = STR_OBSERVER; - } - DESTRUCTOR(Observer) { } -ENDCLASS(Observer) - -CLASS(Spectator, Client) - INIT(Spectator) { - this.classname = STR_SPECTATOR; - } - DESTRUCTOR(Spectator) { } -ENDCLASS(Spectator) - -CLASS(Player, Client) - INIT(Player) { - this.classname = STR_PLAYER; - } - DESTRUCTOR(Player) { } -ENDCLASS(Player) - -METHOD(Client, m_unwind, bool(Client this)) -{ - TC(Client, this); - #define UNWIND(class) MACRO_BEGIN if (this.instanceOf##class) { METHOD_REFERENCE(class, dtorimpl)(this); } MACRO_END - switch (this.classname) { - case "Observer": - UNWIND(Spectator); - UNWIND(Player); - return true; - case "Spectator": - UNWIND(Observer); - UNWIND(Player); - return true; - case "Player": - UNWIND(Observer); - UNWIND(Spectator); - return true; - } - #undef UNWIND - return false; -} - -float c1, c2, c3, c4; - -void play_countdown(entity this, float finished, Sound samp); - -float CalcRotRegen(float current, float regenstable, float regenfactor, float regenlinear, float regenframetime, float rotstable, float rotfactor, float rotlinear, float rotframetime, float limit); - -bool Spectate(entity this, entity pl); - -#define SPECTATE_COPY() [[accumulate]] void SpectateCopy(entity this, entity spectatee) -#define SPECTATE_COPYFIELD(fld) SPECTATE_COPY() { this.(fld) = spectatee.(fld); } diff --git a/qcsrc/server/cl_impulse.qc b/qcsrc/server/cl_impulse.qc deleted file mode 100644 index 00c4ec22cd..0000000000 --- a/qcsrc/server/cl_impulse.qc +++ /dev/null @@ -1,606 +0,0 @@ -#include "cl_impulse.qh" -#include "round_handler.qh" - -#include "bot/api.qh" - -#include "weapons/throwing.qh" -#include "command/common.qh" -#include "cheats.qh" -#include "weapons/selection.qh" -#include "weapons/tracing.qh" -#include "weapons/weaponsystem.qh" - -#include - -#include "../common/minigames/sv_minigames.qh" - -#include "../common/weapons/all.qh" -#include "../common/vehicles/sv_vehicles.qh" - -#include "../common/mutators/mutator/waypoints/waypointsprites.qh" - -.entity vehicle; - -#define IMPULSE(id) _IMPULSE(IMP_##id) -#define _IMPULSE(id) \ - void id##_handle(entity this); \ - STATIC_INIT_LATE(id) \ - { \ - id.impulse_handle = id##_handle; \ - } \ - void id##_handle(entity this) - -/** - * Impulse map: - * - * 0 reserved (no input) - * - * 99: loaded - * - * 140: moving clone - * 141: ctf speedrun - * 142: fixed clone - * 143: emergency teleport - * 148: unfairly eliminate - * - * TODO: - * 200 to 209: prev weapon shortcuts - * 210 to 219: best weapon shortcuts - * 220 to 229: next weapon shortcuts - * 230 to 253: individual weapons (up to 24) - */ - -// weapon switching impulses - -#define X(slot) \ - IMPULSE(weapon_group_##slot) \ - { \ - if (IS_DEAD(this)) \ - { \ - this.impulse = IMP_weapon_group_##slot.impulse; \ - return; \ - } \ - W_NextWeaponOnImpulse(this, slot); \ - } -X(1) -X(2) -X(3) -X(4) -X(5) -X(6) -X(7) -X(8) -X(9) -X(0) -#undef X - -// custom order weapon cycling - -#define X(slot, dir) \ - IMPULSE(weapon_priority_##slot##_##dir) \ - { \ - if (this.vehicle) return; \ - if (IS_DEAD(this)) \ - { \ - this.impulse = IMP_weapon_priority_##slot##_##dir.impulse; \ - return; \ - } \ - noref int prev = -1; \ - noref int best = 0; \ - noref int next = +1; \ - W_CycleWeapon(this, this.cvar_cl_weaponpriorities[slot], dir); \ - } -X(0, prev) -X(1, prev) -X(2, prev) -X(3, prev) -X(4, prev) -X(5, prev) -X(6, prev) -X(7, prev) -X(8, prev) -X(9, prev) - -X(0, best) -X(1, best) -X(2, best) -X(3, best) -X(4, best) -X(5, best) -X(6, best) -X(7, best) -X(8, best) -X(9, best) - -X(0, next) -X(1, next) -X(2, next) -X(3, next) -X(4, next) -X(5, next) -X(6, next) -X(7, next) -X(8, next) -X(9, next) -#undef X - -// direct weapons - -#define X(i) \ - IMPULSE(weapon_byid_##i) \ - { \ - if (this.vehicle) return; \ - if (IS_DEAD(this)) \ - { \ - this.impulse = IMP_weapon_byid_##i.impulse; \ - return; \ - } \ - W_SwitchWeapon(this, Weapons_from(WEP_FIRST + i)); \ - } -X(0) -X(1) -X(2) -X(3) -X(4) -X(5) -X(6) -X(7) -X(8) -X(9) -X(10) -X(11) -X(12) -X(13) -X(14) -X(15) -X(16) -X(17) -X(18) -X(19) -X(20) -X(21) -X(22) -X(23) -#undef X - -IMPULSE(weapon_next_byid) -{ - if (this.vehicle) return; - if (IS_DEAD(this)) - { - this.impulse = IMP_weapon_next_byid.impulse; - return; - } - W_NextWeapon(this, 0); -} - -IMPULSE(weapon_prev_byid) -{ - if (this.vehicle) return; - if (IS_DEAD(this)) - { - this.impulse = IMP_weapon_prev_byid.impulse; - return; - } - W_PreviousWeapon(this, 0); -} - -IMPULSE(weapon_next_bygroup) -{ - if (this.vehicle) return; - if (IS_DEAD(this)) - { - this.impulse = IMP_weapon_next_bygroup.impulse; - return; - } - W_NextWeapon(this, 1); -} - -IMPULSE(weapon_prev_bygroup) -{ - if (this.vehicle) return; - if (IS_DEAD(this)) - { - this.impulse = IMP_weapon_prev_bygroup.impulse; - return; - } - W_PreviousWeapon(this, 1); -} - -IMPULSE(weapon_next_bypriority) -{ - if (this.vehicle) return; - if (IS_DEAD(this)) - { - this.impulse = IMP_weapon_next_bypriority.impulse; - return; - } - W_NextWeapon(this, 2); -} - -IMPULSE(weapon_prev_bypriority) -{ - if (this.vehicle) return; - if (IS_DEAD(this)) - { - this.impulse = IMP_weapon_prev_bypriority.impulse; - return; - } - W_PreviousWeapon(this, 2); -} - -IMPULSE(weapon_last) -{ - if (this.vehicle) return; - if (IS_DEAD(this)) return; - W_LastWeapon(this); -} - -IMPULSE(weapon_best) -{ - if (this.vehicle) return; - if (IS_DEAD(this)) return; - W_SwitchWeapon(this, w_getbestweapon(this)); -} - -IMPULSE(weapon_drop) -{ - if (this.vehicle) return; - if (IS_DEAD(this)) return; - W_ThrowWeapon(this, weaponentities[0], W_CalculateProjectileVelocity(this, this.velocity, v_forward * 750, false), '0 0 0', true); -} - -IMPULSE(weapon_reload) -{ - if (this.vehicle) return; - if (IS_DEAD(this)) return; - if (forbidWeaponUse(this)) return; - Weapon w = PS(this).m_weapon; - entity actor = this; - for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) - { - .entity weaponentity = weaponentities[slot]; - w.wr_reload(w, actor, weaponentity); - } -} - -void ImpulseCommands(entity this) -{ - if (gameover) return; - - int imp = this.impulse; - if (!imp) return; - this.impulse = 0; - - if (MinigameImpulse(this, imp)) return; - - if (timeout_status == TIMEOUT_ACTIVE) return; // don't allow any impulses while the game is paused - - // allow only weapon change impulses when not in round time - if (round_handler_IsActive() && !round_handler_IsRoundStarted()) - { - #define X(id) case IMP_##id.impulse: - switch (imp) - { - X(weapon_group_0) - X(weapon_group_1) - X(weapon_group_2) - X(weapon_group_3) - X(weapon_group_4) - X(weapon_group_5) - X(weapon_group_6) - X(weapon_group_7) - X(weapon_group_8) - X(weapon_group_9) - X(weapon_next_byid) - X(weapon_prev_byid) - X(weapon_next_bygroup) - X(weapon_prev_bygroup) - X(weapon_next_bypriority) - X(weapon_prev_bypriority) - X(weapon_last) - X(weapon_best) - X(weapon_reload) - X(weapon_priority_0_prev) - X(weapon_priority_1_prev) - X(weapon_priority_2_prev) - X(weapon_priority_3_prev) - X(weapon_priority_4_prev) - X(weapon_priority_5_prev) - X(weapon_priority_6_prev) - X(weapon_priority_7_prev) - X(weapon_priority_8_prev) - X(weapon_priority_9_prev) - X(weapon_priority_0_next) - X(weapon_priority_1_next) - X(weapon_priority_2_next) - X(weapon_priority_3_next) - X(weapon_priority_4_next) - X(weapon_priority_5_next) - X(weapon_priority_6_next) - X(weapon_priority_7_next) - X(weapon_priority_8_next) - X(weapon_priority_9_next) - X(weapon_priority_0_best) - X(weapon_priority_1_best) - X(weapon_priority_2_best) - X(weapon_priority_3_best) - X(weapon_priority_4_best) - X(weapon_priority_5_best) - X(weapon_priority_6_best) - X(weapon_priority_7_best) - X(weapon_priority_8_best) - X(weapon_priority_9_best) - X(weapon_byid_0) - X(weapon_byid_1) - X(weapon_byid_2) - X(weapon_byid_3) - X(weapon_byid_4) - X(weapon_byid_5) - X(weapon_byid_6) - X(weapon_byid_7) - X(weapon_byid_8) - X(weapon_byid_9) - X(weapon_byid_10) - X(weapon_byid_11) - X(weapon_byid_12) - X(weapon_byid_13) - X(weapon_byid_14) - X(weapon_byid_15) - X(weapon_byid_16) - X(weapon_byid_17) - X(weapon_byid_18) - X(weapon_byid_19) - X(weapon_byid_20) - X(weapon_byid_21) - X(weapon_byid_22) - X(weapon_byid_23) - break; - default: return; - } -#undef X - } - - if (vehicle_impulse(this, imp)) return; - - if (CheatImpulse(this, imp)) return; - - FOREACH(IMPULSES, it.impulse == imp, { - void(entity) f = it.impulse_handle; - if (!f) continue; - f(this); - return; - }); -} - -IMPULSE(use) -{ - PlayerUseKey(this); -} - -IMPULSE(waypoint_personal_here) -{ - entity wp = WaypointSprite_DeployPersonal(WP_Waypoint, this, this.origin, RADARICON_WAYPOINT); - if (wp) WaypointSprite_Ping(wp); - sprint(this, "personal waypoint spawned at location\n"); -} - -IMPULSE(waypoint_personal_crosshair) -{ - WarpZone_crosshair_trace(this); - entity wp = WaypointSprite_DeployPersonal(WP_Waypoint, this, trace_endpos, RADARICON_WAYPOINT); - if (wp) WaypointSprite_Ping(wp); - sprint(this, "personal waypoint spawned at crosshair\n"); -} - -IMPULSE(waypoint_personal_death) -{ - if (!this.death_origin) return; - entity wp = WaypointSprite_DeployPersonal(WP_Waypoint, this, this.death_origin, RADARICON_WAYPOINT); - if (wp) WaypointSprite_Ping(wp); - sprint(this, "personal waypoint spawned at death location\n"); -} - -IMPULSE(waypoint_here_follow) -{ - if (!teamplay) return; - if (IS_DEAD(this)) return; - if (!MUTATOR_CALLHOOK(HelpMePing, this)) - { - entity wp = WaypointSprite_Attach(WP_Helpme, this, true, RADARICON_HELPME); - if (!wp) WaypointSprite_HelpMePing(this.waypointsprite_attachedforcarrier); - else WaypointSprite_Ping(wp); - } - sprint(this, "HELP ME attached\n"); -} - -IMPULSE(waypoint_here_here) -{ - entity wp = WaypointSprite_DeployFixed(WP_Here, false, this, this.origin, RADARICON_HERE); - if (wp) WaypointSprite_Ping(wp); - sprint(this, "HERE spawned at location\n"); -} - -IMPULSE(waypoint_here_crosshair) -{ - WarpZone_crosshair_trace(this); - entity wp = WaypointSprite_DeployFixed(WP_Here, false, this, trace_endpos, RADARICON_HERE); - if (wp) WaypointSprite_Ping(wp); - sprint(this, "HERE spawned at crosshair\n"); -} - -IMPULSE(waypoint_here_death) -{ - if (!this.death_origin) return; - entity wp = WaypointSprite_DeployFixed(WP_Here, false, this, this.death_origin, RADARICON_HERE); - if (wp) WaypointSprite_Ping(wp); - sprint(this, "HERE spawned at death location\n"); -} - -IMPULSE(waypoint_danger_here) -{ - entity wp = WaypointSprite_DeployFixed(WP_Danger, false, this, this.origin, RADARICON_DANGER); - if (wp) WaypointSprite_Ping(wp); - sprint(this, "DANGER spawned at location\n"); -} - -IMPULSE(waypoint_danger_crosshair) -{ - WarpZone_crosshair_trace(this); - entity wp = WaypointSprite_DeployFixed(WP_Danger, false, this, trace_endpos, RADARICON_DANGER); - if (wp) WaypointSprite_Ping(wp); - sprint(this, "DANGER spawned at crosshair\n"); -} - -IMPULSE(waypoint_danger_death) -{ - if (!this.death_origin) return; - entity wp = WaypointSprite_DeployFixed(WP_Danger, false, this, this.death_origin, RADARICON_DANGER); - if (wp) WaypointSprite_Ping(wp); - sprint(this, "DANGER spawned at death location\n"); -} - -IMPULSE(waypoint_clear_personal) -{ - WaypointSprite_ClearPersonal(this); - if (this.personal) - { - delete(this.personal); - this.personal = NULL; - } - sprint(this, "personal waypoint cleared\n"); -} - -IMPULSE(waypoint_clear) -{ - WaypointSprite_ClearOwned(this); - if (this.personal) - { - delete(this.personal); - this.personal = NULL; - } - sprint(this, "all waypoints cleared\n"); -} - -IMPULSE(navwaypoint_spawn) -{ - if (!autocvar_g_waypointeditor) return; - waypoint_schedulerelink(waypoint_spawn(this.origin, this.origin, 0)); - bprint(strcat("Waypoint spawned at ", vtos(this.origin), "\n")); -} - -IMPULSE(navwaypoint_remove) -{ - if (!autocvar_g_waypointeditor) return; - entity e = navigation_findnearestwaypoint(this, false); - if (!e) return; - if (e.wpflags & WAYPOINTFLAG_GENERATED) return; - bprint(strcat("Waypoint removed at ", vtos(e.origin), "\n")); - waypoint_remove(e); -} - -IMPULSE(navwaypoint_relink) -{ - if (!autocvar_g_waypointeditor) return; - waypoint_schedulerelinkall(); -} - -IMPULSE(navwaypoint_save) -{ - if (!autocvar_g_waypointeditor) return; - waypoint_saveall(); -} - -IMPULSE(navwaypoint_unreachable) -{ - if (!autocvar_g_waypointeditor) return; - IL_EACH(g_waypoints, true, - { - it.colormod = '0.5 0.5 0.5'; - it.effects &= ~(EF_NODEPTHTEST | EF_RED | EF_BLUE); - }); - entity e2 = navigation_findnearestwaypoint(this, false); - navigation_markroutes(this, e2); - - int j, m; - - j = 0; - m = 0; - IL_EACH(g_waypoints, it.wpcost >= 10000000, - { - LOG_INFO("unreachable: ", etos(it), " ", vtos(it.origin), "\n"); - it.colormod_z = 8; - it.effects |= EF_NODEPTHTEST | EF_BLUE; - ++j; - ++m; - }); - if (j) LOG_INFOF("%d waypoints cannot be reached from here in any way (marked with blue light)\n", j); - navigation_markroutes_inverted(e2); - - j = 0; - IL_EACH(g_waypoints, it.wpcost >= 10000000, - { - LOG_INFO("cannot reach me: ", etos(it), " ", vtos(it.origin), "\n"); - it.colormod_x = 8; - if (!(it.effects & EF_NODEPTHTEST)) // not already reported before - ++m; - it.effects |= EF_NODEPTHTEST | EF_RED; - ++j; - }); - if (j) LOG_INFOF("%d waypoints cannot walk to here in any way (marked with red light)\n", j); - if (m) LOG_INFOF("%d waypoints have been marked total\n", m); - - j = 0; - FOREACH_ENTITY_CLASS("info_player_deathmatch", true, - { - vector org = it.origin; - tracebox(it.origin, STAT(PL_MIN, NULL), STAT(PL_MAX, NULL), it.origin - '0 0 512', MOVE_NOMONSTERS, NULL); - setorigin(it, trace_endpos); - if (navigation_findnearestwaypoint(it, false)) - { - setorigin(it, org); - it.effects &= ~EF_NODEPTHTEST; - it.model = ""; - } - else - { - setorigin(it, org); - LOG_INFO("spawn without waypoint: ", etos(it), " ", vtos(it.origin), "\n"); - it.effects |= EF_NODEPTHTEST; - _setmodel(it, this.model); - it.frame = this.frame; - it.skin = this.skin; - it.colormod = '8 0.5 8'; - setsize(it, '0 0 0', '0 0 0'); - ++j; - } - }); - if (j) LOG_INFOF("%d spawnpoints have no nearest waypoint (marked by player model)\n", j); - - j = 0; - FOREACH_ENTITY_FLAGS(flags, FL_ITEM, - { - it.effects &= ~(EF_NODEPTHTEST | EF_RED | EF_BLUE); - it.colormod = '0.5 0.5 0.5'; - }); - FOREACH_ENTITY_FLAGS(flags, FL_ITEM, - { - if (navigation_findnearestwaypoint(it, false)) continue; - LOG_INFO("item without waypoint: ", etos(it), " ", vtos(it.origin), "\n"); - it.effects |= EF_NODEPTHTEST | EF_RED; - it.colormod_x = 8; - ++j; - }); - if (j) LOG_INFOF("%d items have no nearest waypoint and cannot be walked away from (marked with red light)\n", j); - - j = 0; - FOREACH_ENTITY_FLAGS(flags, FL_ITEM, - { - if (navigation_findnearestwaypoint(it, true)) continue; - LOG_INFO("item without waypoint: ", etos(it), " ", vtos(it.origin), "\n"); - it.effects |= EF_NODEPTHTEST | EF_BLUE; - it.colormod_z = 8; - ++j; - }); - if (j) LOG_INFOF("%d items have no nearest waypoint and cannot be walked to (marked with blue light)\n", j); -} diff --git a/qcsrc/server/cl_impulse.qh b/qcsrc/server/cl_impulse.qh deleted file mode 100644 index 50edc2c9c5..0000000000 --- a/qcsrc/server/cl_impulse.qh +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -void ImpulseCommands(entity this); diff --git a/qcsrc/server/cl_player.qc b/qcsrc/server/cl_player.qc deleted file mode 100644 index 1ae97c99e5..0000000000 --- a/qcsrc/server/cl_player.qc +++ /dev/null @@ -1,962 +0,0 @@ -#include "cl_player.qh" - -#include "bot/api.qh" -#include "cheats.qh" -#include "g_damage.qh" -#include "g_subs.qh" -#include "miscfunctions.qh" -#include "portals.qh" -#include "teamplay.qh" -#include "weapons/throwing.qh" -#include "command/common.qh" -#include "../common/state.qh" -#include "../common/anim.qh" -#include "../common/animdecide.qh" -#include "../common/csqcmodel_settings.qh" -#include "../common/deathtypes/all.qh" -#include "../common/triggers/subs.qh" -#include "../common/playerstats.qh" -#include "../lib/csqcmodel/sv_model.qh" - -#include "../common/minigames/sv_minigames.qh" - -#include "../common/physics/player.qh" -#include "../common/effects/qc/all.qh" -#include "../common/mutators/mutator/waypoints/waypointsprites.qh" -#include "../common/triggers/include.qh" - -#include "weapons/weaponstats.qh" - -#include "../common/animdecide.qh" - -void Drop_Special_Items(entity player) -{ - // called when the player has become stuck or frozen - // so objective items aren't stuck with the player - - MUTATOR_CALLHOOK(DropSpecialItems, player); -} - -void CopyBody_Think(entity this) -{ - if(this.CopyBody_nextthink && time > this.CopyBody_nextthink) - { - this.CopyBody_think(this); - if(wasfreed(this)) - return; - this.CopyBody_nextthink = this.nextthink; - this.CopyBody_think = getthink(this); - setthink(this, CopyBody_Think); - } - CSQCMODEL_AUTOUPDATE(this); - this.nextthink = time; -} -void CopyBody(entity this, float keepvelocity) -{ - if (this.effects & EF_NODRAW) - return; - entity clone = new(body); - clone.enemy = this; - clone.lip = this.lip; - clone.colormap = this.colormap; - clone.iscreature = this.iscreature; - clone.teleportable = this.teleportable; - clone.damagedbycontents = this.damagedbycontents; - clone.angles = this.angles; - clone.v_angle = this.v_angle; - clone.avelocity = this.avelocity; - clone.damageforcescale = this.damageforcescale; - clone.effects = this.effects; - clone.glowmod = this.glowmod; - clone.event_damage = this.event_damage; - clone.anim_state = this.anim_state; - clone.anim_time = this.anim_time; - clone.anim_lower_action = this.anim_lower_action; - clone.anim_lower_time = this.anim_lower_time; - clone.anim_upper_action = this.anim_upper_action; - clone.anim_upper_time = this.anim_upper_time; - clone.anim_implicit_state = this.anim_implicit_state; - clone.anim_implicit_time = this.anim_implicit_time; - clone.anim_lower_implicit_action = this.anim_lower_implicit_action; - clone.anim_lower_implicit_time = this.anim_lower_implicit_time; - clone.anim_upper_implicit_action = this.anim_upper_implicit_action; - clone.anim_upper_implicit_time = this.anim_upper_implicit_time; - clone.dphitcontentsmask = this.dphitcontentsmask; - clone.death_time = this.death_time; - clone.pain_finished = this.pain_finished; - clone.health = this.health; - clone.armorvalue = this.armorvalue; - clone.armortype = this.armortype; - clone.model = this.model; - clone.modelindex = this.modelindex; - clone.skin = this.skin; - clone.species = this.species; - clone.move_qcphysics = false; // don't run gamecode logic on clones, too many - set_movetype(clone, this.move_movetype); - clone.solid = this.solid; - clone.ballistics_density = this.ballistics_density; - clone.takedamage = this.takedamage; - setcefc(clone, getcefc(this)); - clone.uncustomizeentityforclient = this.uncustomizeentityforclient; - clone.uncustomizeentityforclient_set = this.uncustomizeentityforclient_set; - if (keepvelocity == 1) - clone.velocity = this.velocity; - clone.oldvelocity = clone.velocity; - clone.alpha = this.alpha; - clone.fade_time = this.fade_time; - clone.fade_rate = this.fade_rate; - //clone.weapon = this.weapon; - setorigin(clone, this.origin); - setsize(clone, this.mins, this.maxs); - clone.prevorigin = this.origin; - clone.reset = SUB_Remove; - clone._ps = this._ps; - - Drag_MoveDrag(this, clone); - - if(clone.colormap <= maxclients && clone.colormap > 0) - clone.colormap = 1024 + this.clientcolors; - - CSQCMODEL_AUTOINIT(clone); - clone.CopyBody_nextthink = this.nextthink; - clone.CopyBody_think = getthink(this); - clone.nextthink = time; - setthink(clone, CopyBody_Think); - // "bake" the current animation frame for clones (they don't get clientside animation) - animdecide_load_if_needed(clone); - animdecide_setframes(clone, false, frame, frame1time, frame2, frame2time); -} - -void player_setupanimsformodel(entity this) -{ - // load animation info - animdecide_load_if_needed(this); - animdecide_setstate(this, 0, false); -} - -void player_anim(entity this) -{ - int deadbits = (this.anim_state & (ANIMSTATE_DEAD1 | ANIMSTATE_DEAD2)); - if(IS_DEAD(this)) { - if (!deadbits) { - // Decide on which death animation to use. - if(random() < 0.5) - deadbits = ANIMSTATE_DEAD1; - else - deadbits = ANIMSTATE_DEAD2; - } - } else { - // Clear a previous death animation. - deadbits = 0; - } - int animbits = deadbits; - if(STAT(FROZEN, this)) - animbits |= ANIMSTATE_FROZEN; - if(this.move_movetype == MOVETYPE_FOLLOW) - animbits |= ANIMSTATE_FOLLOW; - if(this.crouch) - animbits |= ANIMSTATE_DUCK; - animdecide_setstate(this, animbits, false); - animdecide_setimplicitstate(this, IS_ONGROUND(this)); -} - -void PlayerCorpseDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force) -{ - float take, save; - vector v; - Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, this, attacker); - - // damage resistance (ignore most of the damage from a bullet or similar) - damage = max(damage - 5, 1); - - v = healtharmor_applydamage(this.armorvalue, autocvar_g_balance_armor_blockpercent, deathtype, damage); - take = v.x; - save = v.y; - - if(sound_allowed(MSG_BROADCAST, attacker)) - { - if (save > 10) - sound (this, CH_SHOTS, SND_ARMORIMPACT, VOL_BASE, ATTEN_NORM); - else if (take > 30) - sound (this, CH_SHOTS, SND_BODYIMPACT2, VOL_BASE, ATTEN_NORM); - else if (take > 10) - sound (this, CH_SHOTS, SND_BODYIMPACT1, VOL_BASE, ATTEN_NORM); - } - - if (take > 50) - Violence_GibSplash_At(hitloc, force * -0.1, 3, 1, this, attacker); - if (take > 100) - Violence_GibSplash_At(hitloc, force * -0.2, 3, 1, this, attacker); - - this.armorvalue = this.armorvalue - save; - this.health = this.health - take; - // pause regeneration for 5 seconds - this.pauseregen_finished = max(this.pauseregen_finished, time + autocvar_g_balance_pause_health_regen); - - this.dmg_save = this.dmg_save + save;//max(save - 10, 0); - this.dmg_take = this.dmg_take + take;//max(take - 10, 0); - this.dmg_inflictor = inflictor; - - if (this.health <= -autocvar_sv_gibhealth && this.alpha >= 0) - { - // don't use any animations as a gib - this.frame = 0; - // view just above the floor - this.view_ofs = '0 0 4'; - - Violence_GibSplash(this, 1, 1, attacker); - this.alpha = -1; - this.solid = SOLID_NOT; // restore later - this.takedamage = DAMAGE_NO; // restore later - this.damagedbycontents = false; - } -} - -void calculate_player_respawn_time(entity this) -{ - if(g_ca) - return; - - float gametype_setting_tmp; - float sdelay_max = GAMETYPE_DEFAULTED_SETTING(respawn_delay_max); - float sdelay_small = GAMETYPE_DEFAULTED_SETTING(respawn_delay_small); - float sdelay_large = GAMETYPE_DEFAULTED_SETTING(respawn_delay_large); - float sdelay_small_count = GAMETYPE_DEFAULTED_SETTING(respawn_delay_small_count); - float sdelay_large_count = GAMETYPE_DEFAULTED_SETTING(respawn_delay_large_count); - float waves = GAMETYPE_DEFAULTED_SETTING(respawn_waves); - - float pcount = 1; // Include myself whether or not team is already set right and I'm a "player". - if (teamplay) - { - FOREACH_CLIENT(IS_PLAYER(it) && it != this, LAMBDA( - if(it.team == this.team) - ++pcount; - )); - if (sdelay_small_count == 0) - sdelay_small_count = 1; - if (sdelay_large_count == 0) - sdelay_large_count = 1; - } - else - { - FOREACH_CLIENT(IS_PLAYER(it) && it != this, LAMBDA( - ++pcount; - )); - if (sdelay_small_count == 0) - { - if (g_cts) - { - // Players play independently. No point in requiring enemies. - sdelay_small_count = 1; - } - else - { - // Players play AGAINST each other. Enemies required. - sdelay_small_count = 2; - } - } - if (sdelay_large_count == 0) - { - if (g_cts) - { - // Players play independently. No point in requiring enemies. - sdelay_large_count = 1; - } - else - { - // Players play AGAINST each other. Enemies required. - sdelay_large_count = 2; - } - } - } - - float sdelay; - - if (pcount <= sdelay_small_count) - sdelay = sdelay_small; - else if (pcount >= sdelay_large_count) - sdelay = sdelay_large; - else // NOTE: this case implies sdelay_large_count > sdelay_small_count. - sdelay = sdelay_small + (sdelay_large - sdelay_small) * (pcount - sdelay_small_count) / (sdelay_large_count - sdelay_small_count); - - if(waves) - this.respawn_time = ceil((time + sdelay) / waves) * waves; - else - this.respawn_time = time + sdelay; - - if(sdelay < sdelay_max) - this.respawn_time_max = time + sdelay_max; - else - this.respawn_time_max = this.respawn_time; - - if((sdelay + waves >= 5.0) && (this.respawn_time - time > 1.75)) - this.respawn_countdown = 10; // first number to count down from is 10 - else - this.respawn_countdown = -1; // do not count down - - if(autocvar_g_forced_respawn) - this.respawn_flags = this.respawn_flags | RESPAWN_FORCE; -} - -void PlayerDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force) -{ - float take, save, dh, da; - vector v; - float valid_damage_for_weaponstats; - float excess; - - dh = max(this.health, 0); - da = max(this.armorvalue, 0); - - if(!DEATH_ISSPECIAL(deathtype)) - { - damage *= sqrt(bound(1.0, this.cvar_cl_handicap, 100.0)); - if(this != attacker) - damage /= sqrt(bound(1.0, attacker.cvar_cl_handicap, 100.0)); - } - - if(DEATH_ISWEAPON(deathtype, WEP_TUBA)) - { - // tuba causes blood to come out of the ears - vector ear1, ear2; - vector d; - float f; - ear1 = this.origin; - ear1_z += 0.125 * this.view_ofs.z + 0.875 * this.maxs.z; // 7/8 - ear2 = ear1; - makevectors(this.angles); - ear1 += v_right * -10; - ear2 += v_right * +10; - d = inflictor.origin - this.origin; - if (d) - f = (d * v_right) / vlen(d); // this is cos of angle of d and v_right! - else - f = 0; // Assum ecenter. - force = v_right * vlen(force); - Violence_GibSplash_At(ear1, force * -1, 2, bound(0, damage, 25) / 2 * (0.5 - 0.5 * f), this, attacker); - Violence_GibSplash_At(ear2, force, 2, bound(0, damage, 25) / 2 * (0.5 + 0.5 * f), this, attacker); - if(f > 0) - { - hitloc = ear1; - force = force * -1; - } - else - { - hitloc = ear2; - // force is already good - } - } - else - Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, this, attacker); - - - v = healtharmor_applydamage(this.armorvalue, autocvar_g_balance_armor_blockpercent, deathtype, damage); - take = v.x; - save = v.y; - - if(attacker == this) - { - // don't reset pushltime for this damage as it may be an attempt to - // escape a lava pit or similar - //this.pushltime = 0; - this.istypefrag = 0; - } - else if(IS_PLAYER(attacker)) - { - this.pusher = attacker; - this.pushltime = time + autocvar_g_maxpushtime; - this.istypefrag = PHYS_INPUT_BUTTON_CHAT(this); - } - else if(time < this.pushltime) - { - attacker = this.pusher; - this.pushltime = max(this.pushltime, time + 0.6); - } - else - { - this.pushltime = 0; - this.istypefrag = 0; - } - - if (time < this.spawnshieldtime && autocvar_g_spawnshield_blockdamage < 1) - { - vector v = healtharmor_applydamage(this.armorvalue, max(0, autocvar_g_spawnshield_blockdamage), deathtype, damage); - take = v.x; - save = v.y; - } - - MUTATOR_CALLHOOK(PlayerDamage_SplitHealthArmor, inflictor, attacker, this, force, take, save, deathtype, damage); - take = bound(0, M_ARGV(4, float), this.health); - save = bound(0, M_ARGV(5, float), this.armorvalue); - excess = max(0, damage - take - save); - - if(sound_allowed(MSG_BROADCAST, attacker)) - { - if (save > 10) - sound (this, CH_SHOTS, SND_ARMORIMPACT, VOL_BASE, ATTEN_NORM); - else if (take > 30) - sound (this, CH_SHOTS, SND_BODYIMPACT2, VOL_BASE, ATTEN_NORM); - else if (take > 10) - sound (this, CH_SHOTS, SND_BODYIMPACT1, VOL_BASE, ATTEN_NORM); // FIXME possibly remove them? - } - - if (take > 50) - Violence_GibSplash_At(hitloc, force * -0.1, 3, 1, this, attacker); - if (take > 100) - Violence_GibSplash_At(hitloc, force * -0.2, 3, 1, this, attacker); - - if (time >= this.spawnshieldtime || autocvar_g_spawnshield_blockdamage < 1) - { - if (!(this.flags & FL_GODMODE)) - { - this.armorvalue = this.armorvalue - save; - this.health = this.health - take; - // pause regeneration for 5 seconds - if(take) - this.pauseregen_finished = max(this.pauseregen_finished, time + autocvar_g_balance_pause_health_regen); - - if (time > this.pain_finished) //Don't switch pain sequences like crazy - { - this.pain_finished = time + 0.5; //Supajoe - - if(autocvar_sv_gentle < 1) { - if(this.classname != "body") // pain anim is BORKED on our ZYMs, FIXME remove this once we have good models - { - if (!this.animstate_override) - { - if (random() > 0.5) - animdecide_setaction(this, ANIMACTION_PAIN1, true); - else - animdecide_setaction(this, ANIMACTION_PAIN2, true); - } - } - - if(sound_allowed(MSG_BROADCAST, attacker)) - if((this.health < 2 * WEP_CVAR_PRI(blaster, damage) * autocvar_g_balance_selfdamagepercent + 1) || !(DEATH_WEAPONOF(deathtype).spawnflags & WEP_FLAG_CANCLIMB) || attacker != this) // WEAPONTODO: create separate limit for pain notification with laser - if(this.health > 1) - // exclude pain sounds for laserjumps as long as you aren't REALLY low on health and would die of the next two - { - if(deathtype == DEATH_FALL.m_id) - PlayerSound(this, playersound_fall, CH_PAIN, VOL_BASE, VOICETYPE_PLAYERSOUND); - else if(this.health > 75) - PlayerSound(this, playersound_pain100, CH_PAIN, VOL_BASE, VOICETYPE_PLAYERSOUND); - else if(this.health > 50) - PlayerSound(this, playersound_pain75, CH_PAIN, VOL_BASE, VOICETYPE_PLAYERSOUND); - else if(this.health > 25) - PlayerSound(this, playersound_pain50, CH_PAIN, VOL_BASE, VOICETYPE_PLAYERSOUND); - else - PlayerSound(this, playersound_pain25, CH_PAIN, VOL_BASE, VOICETYPE_PLAYERSOUND); - } - } - } - - // throw off bot aim temporarily - float shake; - if(IS_BOT_CLIENT(this) && this.health >= 1) - { - shake = damage * 5 / (bound(0,skill,100) + 1); - this.v_angle_x = this.v_angle.x + (random() * 2 - 1) * shake; - this.v_angle_y = this.v_angle.y + (random() * 2 - 1) * shake; - this.v_angle_x = bound(-90, this.v_angle.x, 90); - } - } - else - this.max_armorvalue += (save + take); - } - this.dmg_save = this.dmg_save + save;//max(save - 10, 0); - this.dmg_take = this.dmg_take + take;//max(take - 10, 0); - this.dmg_inflictor = inflictor; - - if (this != attacker) { - float realdmg = damage - excess; - if (IS_PLAYER(attacker)) { - PlayerScore_Add(attacker, SP_DMG, realdmg); - } - if (IS_PLAYER(this)) { - PlayerScore_Add(this, SP_DMGTAKEN, realdmg); - } - } - - bool abot = (IS_BOT_CLIENT(attacker)); - bool vbot = (IS_BOT_CLIENT(this)); - - valid_damage_for_weaponstats = 0; - Weapon awep = WEP_Null; - - if(vbot || IS_REAL_CLIENT(this)) - if(abot || IS_REAL_CLIENT(attacker)) - if(attacker && this != attacker) - if(DIFF_TEAM(this, attacker)) - { - if(DEATH_ISSPECIAL(deathtype)) - awep = PS(attacker).m_weapon; - else - awep = DEATH_WEAPONOF(deathtype); - valid_damage_for_weaponstats = 1; - } - - dh = dh - max(this.health, 0); - da = da - max(this.armorvalue, 0); - if(valid_damage_for_weaponstats) - { - WeaponStats_LogDamage(awep.m_id, abot, PS(this).m_weapon.m_id, vbot, dh + da); - } - if (dh + da) - { - MUTATOR_CALLHOOK(PlayerDamaged, attacker, this, dh, da, hitloc, deathtype); - } - - if (this.health < 1) - { - float defer_ClientKill_Now_TeamChange; - defer_ClientKill_Now_TeamChange = false; - - if(this.alivetime) - { - PS_GR_P_ADDVAL(this, PLAYERSTATS_ALIVETIME, time - this.alivetime); - this.alivetime = 0; - } - - if(valid_damage_for_weaponstats) - WeaponStats_LogKill(awep.m_id, abot, PS(this).m_weapon.m_id, vbot); - - if(autocvar_sv_gentle < 1) - if(sound_allowed(MSG_BROADCAST, attacker)) - { - if(deathtype == DEATH_DROWN.m_id) - PlayerSound(this, playersound_drown, CH_PAIN, VOL_BASE, VOICETYPE_PLAYERSOUND); - else - PlayerSound(this, playersound_death, CH_PAIN, VOL_BASE, VOICETYPE_PLAYERSOUND); - } - - // get rid of kill indicator - if(this.killindicator) - { - delete(this.killindicator); - this.killindicator = NULL; - if(this.killindicator_teamchange) - defer_ClientKill_Now_TeamChange = true; - - if(this.classname == "body") - if(deathtype == DEATH_KILL.m_id) - { - // for the lemmings fans, a small harmless explosion - Send_Effect(EFFECT_ROCKET_EXPLODE, this.origin, '0 0 0', 1); - } - } - - // print an obituary message - if(this.classname != "body") - Obituary (attacker, inflictor, this, deathtype); - - // increment frag counter for used weapon type - Weapon w = DEATH_WEAPONOF(deathtype); - if(w != WEP_Null) - if(accuracy_isgooddamage(attacker, this)) - attacker.accuracy.(accuracy_frags[w.m_id-1]) += 1; - - MUTATOR_CALLHOOK(PlayerDies, inflictor, attacker, this, deathtype, damage); - excess = M_ARGV(4, float); - - Weapon wep = PS(this).m_weapon; - /*for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) - { - .entity weaponentity = weaponentities[slot]; - wep.wr_playerdeath(wep, this, weaponentity); - }*/ - .entity weaponentity = weaponentities[0]; // TODO: unhardcode - wep.wr_playerdeath(wep, this, weaponentity); - - RemoveGrapplingHook(this); - - Portal_ClearAllLater(this); - - this.fixangle = true; - - if(defer_ClientKill_Now_TeamChange) - ClientKill_Now_TeamChange(this); // can turn player into spectator - - // player could have been miraculously resuscitated ;) - // e.g. players in freezetag get frozen, they don't really die - if(this.health >= 1 || !(IS_PLAYER(this) || this.classname == "body")) - return; - - // when we get here, player actually dies - - Unfreeze(this); // remove any icy remains - this.health = 0; // Unfreeze resets health, so we need to set it back - - // clear waypoints - WaypointSprite_PlayerDead(this); - // throw a weapon - SpawnThrownWeapon(this, this.origin + (this.mins + this.maxs) * 0.5, PS(this).m_switchweapon.m_id); - - // become fully visible - this.alpha = default_player_alpha; - // make the corpse upright (not tilted) - this.angles_x = 0; - this.angles_z = 0; - // don't spin - this.avelocity = '0 0 0'; - // view from the floor - this.view_ofs = '0 0 -8'; - // toss the corpse - set_movetype(this, MOVETYPE_TOSS); - // shootable corpse - this.solid = SOLID_CORPSE; - this.ballistics_density = autocvar_g_ballistics_density_corpse; - // don't stick to the floor - UNSET_ONGROUND(this); - // dying animation - this.deadflag = DEAD_DYING; - - // when to allow respawn - calculate_player_respawn_time(this); - - this.death_time = time; - if (random() < 0.5) - animdecide_setstate(this, this.anim_state | ANIMSTATE_DEAD1, true); - else - animdecide_setstate(this, this.anim_state | ANIMSTATE_DEAD2, true); - if (this.maxs.z > 5) - { - this.maxs_z = 5; - setsize(this, this.mins, this.maxs); - } - // set damage function to corpse damage - this.event_damage = PlayerCorpseDamage; - // call the corpse damage function just in case it wants to gib - this.event_damage(this, inflictor, attacker, excess, deathtype, hitloc, force); - - // set up to fade out later - SUB_SetFade (this, time + 6 + random (), 1); - // reset body think wrapper broken by SUB_SetFade - if(this.classname == "body" && getthink(this) != CopyBody_Think) { - this.CopyBody_think = getthink(this); - this.CopyBody_nextthink = this.nextthink; - setthink(this, CopyBody_Think); - this.nextthink = time; - } - - if(autocvar_sv_gentle > 0 || autocvar_ekg || this.classname == "body") { - // remove corpse - // clones don't run any animation code any more, so we must gib them when they die :( - PlayerCorpseDamage(this, inflictor, attacker, autocvar_sv_gibhealth+1.0, deathtype, hitloc, force); - } - - // reset fields the weapons may use just in case - FOREACH(Weapons, it != WEP_Null, LAMBDA( - it.wr_resetplayer(it, this); - for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) - { - ATTACK_FINISHED_FOR(this, it.m_id, slot) = 0; - } - )); - } -} - -void MoveToTeam(entity client, int team_colour, int type) -{ - int lockteams_backup = lockteams; // backup any team lock - lockteams = 0; // disable locked teams - TeamchangeFrags(client); // move the players frags - SetPlayerColors(client, team_colour - 1); // set the players colour - Damage(client, client, client, 100000, DEATH_AUTOTEAMCHANGE.m_id, client.origin, '0 0 0'); // kill the player - lockteams = lockteams_backup; // restore the team lock - LogTeamchange(client.playerid, client.team, type); -} - -/** print(), but only print if the server is not local */ -void dedicated_print(string input) -{ - if (server_is_dedicated) print(input); -} - -/** - * message "": do not say, just test flood control - * return value: - * 1 = accept - * 0 = reject - * -1 = fake accept - */ -int Say(entity source, int teamsay, entity privatesay, string msgin, bool floodcontrol) -{ - if (!teamsay && !privatesay) if (substring(msgin, 0, 1) == " ") - msgin = substring(msgin, 1, -1); // work around DP say bug (say_team does not have this!) - - msgin = formatmessage(source, msgin); - - string colorstr; - if (!IS_PLAYER(source)) - colorstr = "^0"; // black for spectators - else if(teamplay) - colorstr = Team_ColorCode(source.team); - else - { - colorstr = ""; - teamsay = false; - } - - if(intermission_running) - teamsay = false; - - if (!source) { - colorstr = ""; - teamsay = false; - } - - if(msgin != "") - msgin = trigger_magicear_processmessage_forallears(source, teamsay, privatesay, msgin); - - /* - * using bprint solves this... me stupid - // how can we prevent the message from appearing in a listen server? - // for now, just give "say" back and only handle say_team - if(!teamsay) - { - clientcommand(source, strcat("say ", msgin)); - return; - } - */ - - string namestr = ""; - if (source) - namestr = autocvar_g_chat_teamcolors ? playername(source) : source.netname; - - string colorprefix = (strdecolorize(namestr) == namestr) ? "^3" : "^7"; - - string msgstr, cmsgstr; - string privatemsgprefix = string_null; - int privatemsgprefixlen = 0; - if (msgin == "") { - msgstr = cmsgstr = ""; - } else { - if(privatesay) - { - msgstr = strcat("\{1}\{13}* ", colorprefix, namestr, "^3 tells you: ^7"); - privatemsgprefixlen = strlen(msgstr); - msgstr = strcat(msgstr, msgin); - cmsgstr = strcat(colorstr, colorprefix, namestr, "^3 tells you:\n^7", msgin); - if(autocvar_g_chat_teamcolors) - privatemsgprefix = strcat("\{1}\{13}* ^3You tell ", playername(privatesay), ": ^7"); - else - privatemsgprefix = strcat("\{1}\{13}* ^3You tell ", privatesay.netname, ": ^7"); - } - else if(teamsay) - { - if(strstrofs(msgin, "/me", 0) >= 0) - { - //msgin = strreplace("/me", "", msgin); - //msgin = substring(msgin, 3, strlen(msgin)); - msgin = strreplace("/me", strcat(colorstr, "(", colorprefix, namestr, colorstr, ")^7"), msgin); - msgstr = strcat("\{1}\{13}^4* ", "^7", msgin); - } - else - msgstr = strcat("\{1}\{13}", colorstr, "(", colorprefix, namestr, colorstr, ") ^7", msgin); - cmsgstr = strcat(colorstr, "(", colorprefix, namestr, colorstr, ")\n^7", msgin); - } - else - { - if(strstrofs(msgin, "/me", 0) >= 0) - { - //msgin = strreplace("/me", "", msgin); - //msgin = substring(msgin, 3, strlen(msgin)); - msgin = strreplace("/me", strcat(colorprefix, namestr), msgin); - msgstr = strcat("\{1}^4* ", "^7", msgin); - } - else { - msgstr = "\{1}"; - msgstr = strcat(msgstr, (namestr != "") ? strcat(colorprefix, namestr, "^7: ") : "^7"); - msgstr = strcat(msgstr, msgin); - } - cmsgstr = ""; - } - msgstr = strcat(strreplace("\n", " ", msgstr), "\n"); // newlines only are good for centerprint - } - - string fullmsgstr = msgstr; - string fullcmsgstr = cmsgstr; - - // FLOOD CONTROL - int flood = 0; - var .float flood_field = floodcontrol_chat; - if(floodcontrol) - { - float flood_spl; - float flood_burst; - float flood_lmax; - float lines; - if(privatesay) - { - flood_spl = autocvar_g_chat_flood_spl_tell; - flood_burst = autocvar_g_chat_flood_burst_tell; - flood_lmax = autocvar_g_chat_flood_lmax_tell; - flood_field = floodcontrol_chattell; - } - else if(teamsay) - { - flood_spl = autocvar_g_chat_flood_spl_team; - flood_burst = autocvar_g_chat_flood_burst_team; - flood_lmax = autocvar_g_chat_flood_lmax_team; - flood_field = floodcontrol_chatteam; - } - else - { - flood_spl = autocvar_g_chat_flood_spl; - flood_burst = autocvar_g_chat_flood_burst; - flood_lmax = autocvar_g_chat_flood_lmax; - flood_field = floodcontrol_chat; - } - flood_burst = max(0, flood_burst - 1); - // to match explanation in default.cfg, a value of 3 must allow three-line bursts and not four! - - // do flood control for the default line size - if(msgstr != "") - { - getWrappedLine_remaining = msgstr; - msgstr = ""; - lines = 0; - while(getWrappedLine_remaining && (!flood_lmax || lines <= flood_lmax)) - { - msgstr = strcat(msgstr, " ", getWrappedLineLen(82.4289758859709, strlennocol)); // perl averagewidth.pl < gfx/vera-sans.width - ++lines; - } - msgstr = substring(msgstr, 1, strlen(msgstr) - 1); - - if(getWrappedLine_remaining != "") - { - msgstr = strcat(msgstr, "\n"); - flood = 2; - } - - if (time >= source.(flood_field)) - { - source.(flood_field) = max(time - flood_burst * flood_spl, source.(flood_field)) + lines * flood_spl; - } - else - { - flood = 1; - msgstr = fullmsgstr; - } - } - else - { - if (time >= source.(flood_field)) - source.(flood_field) = max(time - flood_burst * flood_spl, source.(flood_field)) + flood_spl; - else - flood = 1; - } - - if (timeout_status == TIMEOUT_ACTIVE) // when game is paused, no flood protection - source.(flood_field) = flood = 0; - } - - string sourcemsgstr, sourcecmsgstr; - if(flood == 2) // cannot happen for empty msgstr - { - if(autocvar_g_chat_flood_notify_flooder) - { - sourcemsgstr = strcat(msgstr, "\n^3FLOOD CONTROL: ^7message too long, trimmed\n"); - sourcecmsgstr = ""; - } - else - { - sourcemsgstr = fullmsgstr; - sourcecmsgstr = fullcmsgstr; - } - cmsgstr = ""; - } - else - { - sourcemsgstr = msgstr; - sourcecmsgstr = cmsgstr; - } - - if (!privatesay && source && !IS_PLAYER(source)) - { - if (!intermission_running) - if(teamsay || (autocvar_g_chat_nospectators == 1) || (autocvar_g_chat_nospectators == 2 && !(warmup_stage || gameover))) - teamsay = -1; // spectators - } - - if(flood) - LOG_INFO("NOTE: ", playername(source), "^7 is flooding.\n"); - - // build sourcemsgstr by cutting off a prefix and replacing it by the other one - if(privatesay) - sourcemsgstr = strcat(privatemsgprefix, substring(sourcemsgstr, privatemsgprefixlen, -1)); - - int ret; - if(source.muted) - { - // always fake the message - ret = -1; - } - else if(flood == 1) - { - if (autocvar_g_chat_flood_notify_flooder) - { - sprint(source, strcat("^3FLOOD CONTROL: ^7wait ^1", ftos(source.(flood_field) - time), "^3 seconds\n")); - ret = 0; - } - else - ret = -1; - } - else - { - ret = 1; - } - - if(sourcemsgstr != "" && ret != 0) - { - if(ret < 0) // faked message, because the player is muted - { - sprint(source, sourcemsgstr); - if(sourcecmsgstr != "" && !privatesay) - centerprint(source, sourcecmsgstr); - } - else if(privatesay) // private message, between 2 people only - { - sprint(source, sourcemsgstr); - sprint(privatesay, msgstr); - if (!autocvar_g_chat_tellprivacy) { dedicated_print(msgstr); } // send to server console too if "tellprivacy" is disabled - if(cmsgstr != "") - centerprint(privatesay, cmsgstr); - } - else if ( teamsay && source.active_minigame ) - { - sprint(source, sourcemsgstr); - dedicated_print(msgstr); // send to server console too - FOREACH_CLIENT(IS_REAL_CLIENT(it) && it != source && it.active_minigame == source.active_minigame, sprint(it, msgstr)); - } - else if(teamsay > 0) // team message, only sent to team mates - { - sprint(source, sourcemsgstr); - dedicated_print(msgstr); // send to server console too - if(sourcecmsgstr != "") - centerprint(source, sourcecmsgstr); - FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it) && it != source && it.team == source.team, { - sprint(it, msgstr); - if(cmsgstr != "") - centerprint(it, cmsgstr); - }); - } - else if(teamsay < 0) // spectator message, only sent to spectators - { - sprint(source, sourcemsgstr); - dedicated_print(msgstr); // send to server console too - FOREACH_CLIENT(!IS_PLAYER(it) && IS_REAL_CLIENT(it) && it != source, sprint(it, msgstr)); - } - else - { - if (source) { - sprint(source, sourcemsgstr); - dedicated_print(msgstr); // send to server console too - MX_Say(strcat(playername(source), "^7: ", msgin)); - } - FOREACH_CLIENT(IS_REAL_CLIENT(it) && it != source, sprint(it, msgstr)); - } - } - - return ret; -} diff --git a/qcsrc/server/cl_player.qh b/qcsrc/server/cl_player.qh deleted file mode 100644 index b5f8ca07c8..0000000000 --- a/qcsrc/server/cl_player.qh +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once - -.entity pusher; -.float pushltime; -.float istypefrag; - -.float CopyBody_nextthink; -.void(entity this) CopyBody_think; -void CopyBody_Think(entity this); -void CopyBody(entity this, float keepvelocity); - -void player_setupanimsformodel(entity this); - -void player_anim(entity this); - -void PlayerCorpseDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force); - -// g__str: -// If 0, default is used. -// If <0, 0 is used. -// Otherwise, g_str (default value) is used. -// For consistency, negative values there are mapped to zero too. -#define GAMETYPE_DEFAULTED_SETTING(str) \ - ((gametype_setting_tmp = cvar(strcat("g_", GetGametype(), "_" #str))), \ - (gametype_setting_tmp < 0) ? 0 \ - : (gametype_setting_tmp == 0 || autocvar_g_respawn_delay_forced) ? max(0, autocvar_g_##str) \ - : gametype_setting_tmp) - -void calculate_player_respawn_time(entity this); - -void ClientKill_Now_TeamChange(entity this); - -void MoveToTeam(entity client, float team_colour, float type); - -void PlayerDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force); - -/** to be used by `prvm_edictset server playernumber muted 1` */ -.float muted; -int Say(entity source, float teamsay, entity privatesay, string msgin, float floodcontrol); diff --git a/qcsrc/server/client.qc b/qcsrc/server/client.qc new file mode 100644 index 0000000000..68fa35e005 --- /dev/null +++ b/qcsrc/server/client.qc @@ -0,0 +1,2596 @@ +#include "client.qh" + +#include "anticheat.qh" +#include "impulse.qh" +#include "player.qh" +#include "ipban.qh" +#include "miscfunctions.qh" +#include "portals.qh" +#include "teamplay.qh" +#include "playerdemo.qh" +#include "spawnpoints.qh" +#include "g_damage.qh" +#include "g_hook.qh" +#include "command/common.qh" +#include "cheats.qh" +#include "g_world.qh" +#include "race.qh" +#include "antilag.qh" +#include "campaign.qh" +#include "command/common.qh" + +#include "bot/api.qh" + +#include "../common/ent_cs.qh" +#include + +#include + +#include "../common/triggers/teleporters.qh" + +#include "../common/vehicles/all.qh" + +#include "weapons/hitplot.qh" +#include "weapons/weaponsystem.qh" + +#include "../common/net_notice.qh" +#include "../common/physics/player.qh" + +#include "../common/items/all.qc" + +#include "../common/mutators/mutator/waypoints/all.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/items/inventory.qh" + +#include "../common/monsters/sv_monsters.qh" + +#include "../lib/warpzone/server.qh" + +STATIC_METHOD(Client, Add, void(Client this, int _team)) +{ + ClientConnect(this); + TRANSMUTE(Player, this); + this.frame = 12; // 7 + this.team = _team; + PutClientInServer(this); +} + +void PutObserverInServer(entity this); + +STATIC_METHOD(Client, Remove, void(Client this)) +{ + TRANSMUTE(Observer, this); + PutClientInServer(this); + ClientDisconnect(this); +} + +void send_CSQC_teamnagger() { + WriteHeader(MSG_BROADCAST, TE_CSQC_TEAMNAGGER); +} + +int CountSpectators(entity player, entity to) +{ + if(!player) { return 0; } // not sure how, but best to be safe + + int spec_count = 0; + + FOREACH_CLIENT(IS_REAL_CLIENT(it) && IS_SPEC(it) && it != to && it.enemy == player, + { + spec_count++; + }); + + return spec_count; +} + +void WriteSpectators(entity player, entity to) +{ + if(!player) { return; } // not sure how, but best to be safe + + FOREACH_CLIENT(IS_REAL_CLIENT(it) && IS_SPEC(it) && it != to && it.enemy == player, + { + WriteByte(MSG_ENTITY, num_for_edict(it)); + }); +} + +bool ClientData_Send(entity this, entity to, int sf) +{ + assert(to == this.owner, return false); + + entity e = to; + if (IS_SPEC(e)) e = e.enemy; + + sf = 0; + if (e.race_completed) sf |= 1; // forced scoreboard + if (to.spectatee_status) sf |= 2; // spectator ent number follows + if (e.zoomstate) sf |= 4; // zoomed + if (e.porto_v_angle_held) sf |= 8; // angles held + if (autocvar_sv_showspectators) sf |= 16; // show spectators + + WriteHeader(MSG_ENTITY, ENT_CLIENT_CLIENTDATA); + WriteByte(MSG_ENTITY, sf); + + if (sf & 2) + { + WriteByte(MSG_ENTITY, to.spectatee_status); + } + if (sf & 8) + { + WriteAngle(MSG_ENTITY, e.v_angle.x); + WriteAngle(MSG_ENTITY, e.v_angle.y); + } + + if(sf & 16) + { + float specs = CountSpectators(e, to); + WriteByte(MSG_ENTITY, specs); + WriteSpectators(e, to); + } + + return true; +} + +void ClientData_Attach(entity this) +{ + Net_LinkEntity(this.clientdata = new_pure(clientdata), false, 0, ClientData_Send); + this.clientdata.drawonlytoclient = this; + this.clientdata.owner = this; +} + +void ClientData_Detach(entity this) +{ + delete(this.clientdata); + this.clientdata = NULL; +} + +void ClientData_Touch(entity e) +{ + e.clientdata.SendFlags = 1; + + // make it spectatable + FOREACH_CLIENT(IS_REAL_CLIENT(it) && it != e && IS_SPEC(it) && it.enemy == e, LAMBDA(it.clientdata.SendFlags = 1)); +} + +.string netname_previous; + +void SetSpectatee(entity player, entity spectatee); + + +/* +============= +CheckPlayerModel + +Checks if the argument string can be a valid playermodel. +Returns a valid one in doubt. +============= +*/ +string FallbackPlayerModel; +string CheckPlayerModel(string plyermodel) { + if(FallbackPlayerModel != cvar_defstring("_cl_playermodel")) + { + // note: we cannot summon Don Strunzone here, some player may + // still have the model string set. In case anyone manages how + // to change a cvar default, we'll have a small leak here. + FallbackPlayerModel = strzone(cvar_defstring("_cl_playermodel")); + } + // only in right path + if( substring(plyermodel,0,14) != "models/player/") + return FallbackPlayerModel; + // only good file extensions + if(substring(plyermodel,-4,4) != ".zym") + if(substring(plyermodel,-4,4) != ".dpm") + if(substring(plyermodel,-4,4) != ".iqm") + if(substring(plyermodel,-4,4) != ".md3") + if(substring(plyermodel,-4,4) != ".psk") + return FallbackPlayerModel; + // forbid the LOD models + if(substring(plyermodel, -9,5) == "_lod1") + return FallbackPlayerModel; + if(substring(plyermodel, -9,5) == "_lod2") + return FallbackPlayerModel; + if(plyermodel != strtolower(plyermodel)) + return FallbackPlayerModel; + // also, restrict to server models + if(autocvar_sv_servermodelsonly) + { + if(!fexists(plyermodel)) + return FallbackPlayerModel; + } + return plyermodel; +} + +void setplayermodel(entity e, string modelname) +{ + precache_model(modelname); + _setmodel(e, modelname); + player_setupanimsformodel(e); + if(!autocvar_g_debug_globalsounds) + UpdatePlayerSounds(e); +} + +void FixPlayermodel(entity player); +/** putting a client as observer in the server */ +void PutObserverInServer(entity this) +{ + bool mutator_returnvalue = MUTATOR_CALLHOOK(MakePlayerObserver, this); + PlayerState_detach(this); + + if (IS_PLAYER(this) && this.health >= 1) { + // despawn effect + Send_Effect(EFFECT_SPAWN_NEUTRAL, this.origin, '0 0 0', 1); + } + + { + entity spot = SelectSpawnPoint(this, true); + if (!spot) LOG_FATAL("No spawnpoints for observers?!?"); + this.angles = spot.angles; + this.angles_z = 0; + this.fixangle = true; + // offset it so that the spectator spawns higher off the ground, looks better this way + setorigin(this, spot.origin + STAT(PL_VIEW_OFS, NULL)); + this.prevorigin = this.origin; + if (IS_REAL_CLIENT(this)) + { + msg_entity = this; + WriteByte(MSG_ONE, SVC_SETVIEW); + WriteEntity(MSG_ONE, this); + } + // give the spectator some space between walls for MOVETYPE_FLY_WORLDONLY + // so that your view doesn't go into the ceiling with MOVETYPE_FLY_WORLDONLY, previously "PL_VIEW_OFS" + if(!autocvar_g_debug_globalsounds) + { + // needed for player sounds + this.model = ""; + FixPlayermodel(this); + } + setmodel(this, MDL_Null); + setsize(this, STAT(PL_CROUCH_MIN, NULL), STAT(PL_CROUCH_MAX, NULL)); + this.view_ofs = '0 0 0'; + } + + RemoveGrapplingHook(this); + Portal_ClearAll(this); + Unfreeze(this); + SetSpectatee(this, NULL); + + if (this.alivetime) + { + if (!warmup_stage) + PS_GR_P_ADDVAL(this, PLAYERSTATS_ALIVETIME, time - this.alivetime); + this.alivetime = 0; + } + + if (this.vehicle) vehicles_exit(this.vehicle, VHEF_RELEASE); + + WaypointSprite_PlayerDead(this); + + if (mutator_returnvalue) { + // mutator prevents resetting teams+score + } else { + this.team = -1; // move this as it is needed to log the player spectating in eventlog + this.frags = FRAGS_SPECTATOR; + PlayerScore_Clear(this); // clear scores when needed + } + + if (this.killcount != FRAGS_SPECTATOR) + { + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_QUIT_SPECTATE, this.netname); + if(!intermission_running) + if(autocvar_g_chat_nospectators == 1 || (!(warmup_stage || gameover) && autocvar_g_chat_nospectators == 2)) + Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_CHAT_NOSPECTATORS); + + if(this.just_joined == false) { + LogTeamchange(this.playerid, -1, 4); + } else + this.just_joined = false; + } + + accuracy_resend(this); + + this.spectatortime = time; + this.bot_attack = false; + this.hud = HUD_NORMAL; + TRANSMUTE(Observer, this); + this.iscreature = false; + this.teleportable = TELEPORT_SIMPLE; + this.damagedbycontents = false; + this.health = FRAGS_SPECTATOR; + this.takedamage = DAMAGE_NO; + this.solid = SOLID_NOT; + set_movetype(this, MOVETYPE_FLY_WORLDONLY); // user preference is controlled by playerprethink + this.flags = FL_CLIENT | FL_NOTARGET; + this.armorvalue = 666; + this.effects = 0; + this.armorvalue = autocvar_g_balance_armor_start; + this.pauserotarmor_finished = 0; + this.pauserothealth_finished = 0; + this.pauseregen_finished = 0; + this.damageforcescale = 0; + this.death_time = 0; + this.respawn_flags = 0; + this.respawn_time = 0; + this.stat_respawn_time = 0; + this.alpha = 0; + this.scale = 0; + this.fade_time = 0; + this.pain_frame = 0; + this.pain_finished = 0; + this.strength_finished = 0; + this.invincible_finished = 0; + this.superweapons_finished = 0; + this.pushltime = 0; + this.istypefrag = 0; + setthink(this, func_null); + this.nextthink = 0; + this.hook_time = 0; + this.deadflag = DEAD_NO; + this.crouch = false; + this.revival_time = 0; + + this.items = 0; + this.weapons = '0 0 0'; + this.drawonlytoclient = this; + + this.weaponname = ""; + this.weaponmodel = ""; + for (int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) + { + this.weaponentities[slot] = NULL; + } + this.exteriorweaponentity = NULL; + this.killcount = FRAGS_SPECTATOR; + this.velocity = '0 0 0'; + this.avelocity = '0 0 0'; + this.punchangle = '0 0 0'; + this.punchvector = '0 0 0'; + this.oldvelocity = this.velocity; + this.fire_endtime = -1; + this.event_damage = func_null; + + STAT(ACTIVEWEAPON, this) = WEP_Null.m_id; + STAT(SWITCHINGWEAPON, this) = WEP_Null.m_id; + STAT(SWITCHWEAPON, this) = WEP_Null.m_id; +} + +int player_getspecies(entity this) +{ + get_model_parameters(this.model, this.skin); + int s = get_model_parameters_species; + get_model_parameters(string_null, 0); + if (s < 0) return SPECIES_HUMAN; + return s; +} + +.float model_randomizer; +void FixPlayermodel(entity player) +{ + string defaultmodel = ""; + int defaultskin = 0; + if(autocvar_sv_defaultcharacter) + { + if(teamplay) + { + string s = Static_Team_ColorName_Lower(player.team); + if (s != "neutral") + { + defaultmodel = cvar_string(strcat("sv_defaultplayermodel_", s)); + defaultskin = cvar(strcat("sv_defaultplayerskin_", s)); + } + } + + if(defaultmodel == "") + { + defaultmodel = autocvar_sv_defaultplayermodel; + defaultskin = autocvar_sv_defaultplayerskin; + } + + int n = tokenize_console(defaultmodel); + if(n > 0) + { + defaultmodel = argv(floor(n * player.model_randomizer)); + // However, do NOT randomize if the player-selected model is in the list. + for (int i = 0; i < n; ++i) + if ((argv(i) == player.playermodel && defaultskin == stof(player.playerskin)) || argv(i) == strcat(player.playermodel, ":", player.playerskin)) + defaultmodel = argv(i); + } + + int i = strstrofs(defaultmodel, ":", 0); + if(i >= 0) + { + defaultskin = stof(substring(defaultmodel, i+1, -1)); + defaultmodel = substring(defaultmodel, 0, i); + } + } + if(autocvar_sv_defaultcharacterskin && !defaultskin) + { + if(teamplay) + { + string s = Static_Team_ColorName_Lower(player.team); + if (s != "neutral") + defaultskin = cvar(strcat("sv_defaultplayerskin_", s)); + } + + if(!defaultskin) + defaultskin = autocvar_sv_defaultplayerskin; + } + + MUTATOR_CALLHOOK(FixPlayermodel, defaultmodel, defaultskin, player); + defaultmodel = M_ARGV(0, string); + defaultskin = M_ARGV(1, int); + + bool chmdl = false; + int oldskin; + if(defaultmodel != "") + { + if (defaultmodel != player.model) + { + vector m1 = player.mins; + vector m2 = player.maxs; + setplayermodel (player, defaultmodel); + setsize (player, m1, m2); + chmdl = true; + } + + oldskin = player.skin; + player.skin = defaultskin; + } else { + if (player.playermodel != player.model || player.playermodel == "") + { + player.playermodel = CheckPlayerModel(player.playermodel); // this is never "", so no endless loop + vector m1 = player.mins; + vector m2 = player.maxs; + setplayermodel (player, player.playermodel); + setsize (player, m1, m2); + chmdl = true; + } + + if(!autocvar_sv_defaultcharacterskin) + { + oldskin = player.skin; + player.skin = stof(player.playerskin); + } + else + { + oldskin = player.skin; + player.skin = defaultskin; + } + } + + if(chmdl || oldskin != player.skin) // model or skin has changed + { + player.species = player_getspecies(player); // update species + if(!autocvar_g_debug_globalsounds) + UpdatePlayerSounds(player); // update skin sounds + } + + if(!teamplay) + if(strlen(autocvar_sv_defaultplayercolors)) + if(player.clientcolors != stof(autocvar_sv_defaultplayercolors)) + setcolor(player, stof(autocvar_sv_defaultplayercolors)); +} + + +/** Called when a client spawns in the server */ +void PutClientInServer(entity this) +{ + if (IS_BOT_CLIENT(this)) { + TRANSMUTE(Player, this); + } else if (IS_REAL_CLIENT(this)) { + msg_entity = this; + WriteByte(MSG_ONE, SVC_SETVIEW); + WriteEntity(MSG_ONE, this); + } + if (gameover) { + TRANSMUTE(Observer, this); + } + + SetSpectatee(this, NULL); + + // reset player keys + this.itemkeys = 0; + + MUTATOR_CALLHOOK(PutClientInServer, this); + + if (IS_OBSERVER(this)) { + PutObserverInServer(this); + } else if (IS_PLAYER(this)) { + if (this.vehicle) vehicles_exit(this.vehicle, VHEF_RELEASE); + + PlayerState_attach(this); + accuracy_resend(this); + + if (this.team < 0) + JoinBestTeam(this, false, true); + + entity spot = SelectSpawnPoint(this, false); + if (!spot) { + Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_JOIN_NOSPAWNS); + return; // spawn failed + } + + TRANSMUTE(Player, this); + this.wasplayer = true; + this.iscreature = true; + this.teleportable = TELEPORT_NORMAL; + this.damagedbycontents = true; + set_movetype(this, MOVETYPE_WALK); + this.solid = SOLID_SLIDEBOX; + this.dphitcontentsmask = DPCONTENTS_BODY | DPCONTENTS_SOLID; + if (autocvar_g_playerclip_collisions) + this.dphitcontentsmask |= DPCONTENTS_PLAYERCLIP; + if (IS_BOT_CLIENT(this) && autocvar_g_botclip_collisions) + this.dphitcontentsmask |= DPCONTENTS_BOTCLIP; + this.frags = FRAGS_PLAYER; + if (INDEPENDENT_PLAYERS) MAKE_INDEPENDENT_PLAYER(this); + this.flags = FL_CLIENT | FL_PICKUPITEMS; + if (autocvar__notarget) + this.flags |= FL_NOTARGET; + this.takedamage = DAMAGE_AIM; + this.effects = EF_TELEPORT_BIT | EF_RESTARTANIM_BIT; + this.dmg = 2; // WTF + + if (warmup_stage) { + this.ammo_shells = warmup_start_ammo_shells; + this.ammo_nails = warmup_start_ammo_nails; + this.ammo_rockets = warmup_start_ammo_rockets; + this.ammo_cells = warmup_start_ammo_cells; + this.ammo_plasma = warmup_start_ammo_plasma; + this.ammo_fuel = warmup_start_ammo_fuel; + this.health = warmup_start_health; + this.armorvalue = warmup_start_armorvalue; + this.weapons = WARMUP_START_WEAPONS; + } else { + this.ammo_shells = start_ammo_shells; + this.ammo_nails = start_ammo_nails; + this.ammo_rockets = start_ammo_rockets; + this.ammo_cells = start_ammo_cells; + this.ammo_plasma = start_ammo_plasma; + this.ammo_fuel = start_ammo_fuel; + this.health = start_health; + this.armorvalue = start_armorvalue; + this.weapons = start_weapons; + } + + this.superweapons_finished = (this.weapons & WEPSET_SUPERWEAPONS) ? time + autocvar_g_balance_superweapons_time : 0; + + this.items = start_items; + + this.spawnshieldtime = time + autocvar_g_spawnshieldtime; + this.pauserotarmor_finished = time + autocvar_g_balance_pause_armor_rot_spawn; + this.pauserothealth_finished = time + autocvar_g_balance_pause_health_rot_spawn; + this.pauserotfuel_finished = time + autocvar_g_balance_pause_fuel_rot_spawn; + this.pauseregen_finished = time + autocvar_g_balance_pause_health_regen_spawn; + // extend the pause of rotting if client was reset at the beginning of the countdown + if (!autocvar_sv_ready_restart_after_countdown && time < game_starttime) { // TODO why is this cvar NOTted? + float f = game_starttime - time; + this.spawnshieldtime += f; + this.pauserotarmor_finished += f; + this.pauserothealth_finished += f; + this.pauseregen_finished += f; + } + this.damageforcescale = 2; + this.death_time = 0; + this.respawn_flags = 0; + this.respawn_time = 0; + this.stat_respawn_time = 0; + this.scale = autocvar_sv_player_scale; + this.fade_time = 0; + this.pain_frame = 0; + this.pain_finished = 0; + this.pushltime = 0; + setthink(this, func_null); // players have no think function + this.nextthink = 0; + this.dmg_team = 0; + this.ballistics_density = autocvar_g_ballistics_density_player; + + this.deadflag = DEAD_NO; + + this.angles = spot.angles; + this.angles_z = 0; // never spawn tilted even if the spot says to + if (IS_BOT_CLIENT(this)) + this.v_angle = this.angles; + this.fixangle = true; // turn this way immediately + this.oldvelocity = this.velocity = '0 0 0'; + this.avelocity = '0 0 0'; + this.punchangle = '0 0 0'; + this.punchvector = '0 0 0'; + + this.strength_finished = 0; + this.invincible_finished = 0; + this.fire_endtime = -1; + this.revival_time = 0; + this.air_finished = time + 12; + + entity spawnevent = new_pure(spawnevent); + spawnevent.owner = this; + Net_LinkEntity(spawnevent, false, 0.5, SpawnEvent_Send); + + // Cut off any still running player sounds. + stopsound(this, CH_PLAYER_SINGLE); + + this.model = ""; + FixPlayermodel(this); + this.drawonlytoclient = NULL; + + this.crouch = false; + this.view_ofs = STAT(PL_VIEW_OFS, NULL); + setsize(this, STAT(PL_MIN, NULL), STAT(PL_MAX, NULL)); + this.spawnorigin = spot.origin; + setorigin(this, spot.origin + '0 0 1' * (1 - this.mins.z - 24)); + // don't reset back to last position, even if new position is stuck in solid + this.oldorigin = this.origin; + this.prevorigin = this.origin; + this.lastteleporttime = time; // prevent insane speeds due to changing origin + this.conveyor = NULL; // prevent conveyors at the previous location from moving a freshly spawned player + this.hud = HUD_NORMAL; + + this.event_damage = PlayerDamage; + + this.bot_attack = true; + this.monster_attack = true; + + PHYS_INPUT_BUTTON_ATCK(this) = PHYS_INPUT_BUTTON_JUMP(this) = PHYS_INPUT_BUTTON_ATCK2(this) = false; + + if (this.killcount == FRAGS_SPECTATOR) { + PlayerScore_Clear(this); + this.killcount = 0; + } + + for (int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) + { + CL_SpawnWeaponentity(this, weaponentities[slot]); + } + this.alpha = default_player_alpha; + this.colormod = '1 1 1' * autocvar_g_player_brightness; + this.exteriorweaponentity.alpha = default_weapon_alpha; + + this.speedrunning = false; + + target_voicescript_clear(this); + + // reset fields the weapons may use + FOREACH(Weapons, true, LAMBDA( + it.wr_resetplayer(it, this); + // reload all reloadable weapons + if (it.spawnflags & WEP_FLAG_RELOADABLE) { + this.weapon_load[it.m_id] = it.reloading_ammo; + } + )); + + { + string s = spot.target; + spot.target = string_null; + SUB_UseTargets(spot, this, NULL); + spot.target = s; + } + + Unfreeze(this); + + MUTATOR_CALLHOOK(PlayerSpawn, this, spot); + + if (autocvar_spawn_debug) + { + sprint(this, strcat("spawnpoint origin: ", vtos(spot.origin), "\n")); + delete(spot); // usefull for checking if there are spawnpoints, that let drop through the floor + } + + PS(this).m_switchweapon = w_getbestweapon(this); + this.cnt = -1; // W_LastWeapon will not complain + PS(this).m_weapon = WEP_Null; + this.weaponname = ""; + PS(this).m_switchingweapon = WEP_Null; + + if (!warmup_stage && !this.alivetime) + this.alivetime = time; + + antilag_clear(this, CS(this)); + } +} + +void ClientInit_misc(entity this); + +.float ebouncefactor, ebouncestop; // electro's values +// TODO do we need all these fields, or should we stop autodetecting runtime +// changes and just have a console command to update this? +bool ClientInit_SendEntity(entity this, entity to, int sf) +{ + WriteHeader(MSG_ENTITY, _ENT_CLIENT_INIT); + return = true; + msg_entity = to; + // MSG_INIT replacement + // TODO: make easier to use + Registry_send_all(); + W_PROP_reload(MSG_ONE, to); + ClientInit_misc(this); + MUTATOR_CALLHOOK(Ent_Init); +} +void ClientInit_misc(entity this) +{ + int channel = MSG_ONE; + WriteHeader(channel, ENT_CLIENT_INIT); + WriteByte(channel, g_nexball_meter_period * 32); + WriteInt24_t(channel, compressShotOrigin(hook_shotorigin[0])); + WriteInt24_t(channel, compressShotOrigin(hook_shotorigin[1])); + WriteInt24_t(channel, compressShotOrigin(hook_shotorigin[2])); + WriteInt24_t(channel, compressShotOrigin(hook_shotorigin[3])); + WriteInt24_t(channel, compressShotOrigin(arc_shotorigin[0])); + WriteInt24_t(channel, compressShotOrigin(arc_shotorigin[1])); + WriteInt24_t(channel, compressShotOrigin(arc_shotorigin[2])); + WriteInt24_t(channel, compressShotOrigin(arc_shotorigin[3])); + + if(sv_foginterval && world.fog != "") + WriteString(channel, world.fog); + else + WriteString(channel, ""); + WriteByte(channel, this.count * 255.0); // g_balance_armor_blockpercent + WriteByte(channel, serverflags); // client has to know if it should zoom or not + WriteCoord(channel, autocvar_g_trueaim_minrange); +} + +void ClientInit_CheckUpdate(entity this) +{ + this.nextthink = time; + if(this.count != autocvar_g_balance_armor_blockpercent) + { + this.count = autocvar_g_balance_armor_blockpercent; + this.SendFlags |= 1; + } +} + +void ClientInit_Spawn() +{ + entity e = new_pure(clientinit); + setthink(e, ClientInit_CheckUpdate); + Net_LinkEntity(e, false, 0, ClientInit_SendEntity); + + ClientInit_CheckUpdate(e); +} + +/* +============= +SetNewParms +============= +*/ +void SetNewParms () +{ + // initialize parms for a new player + parm1 = -(86400 * 366); + + MUTATOR_CALLHOOK(SetNewParms); +} + +/* +============= +SetChangeParms +============= +*/ +void SetChangeParms (entity this) +{ + // save parms for level change + parm1 = this.parm_idlesince - time; + + MUTATOR_CALLHOOK(SetChangeParms); +} + +/* +============= +DecodeLevelParms +============= +*/ +void DecodeLevelParms(entity this) +{ + // load parms + this.parm_idlesince = parm1; + if (this.parm_idlesince == -(86400 * 366)) + this.parm_idlesince = time; + + // whatever happens, allow 60 seconds of idling directly after connect for map loading + this.parm_idlesince = max(this.parm_idlesince, time - sv_maxidle + 60); + + MUTATOR_CALLHOOK(DecodeLevelParms); +} + +/* +============= +ClientKill + +Called when a client types 'kill' in the console +============= +*/ + +.float clientkill_nexttime; +void ClientKill_Now_TeamChange(entity this) +{ + if(this.killindicator_teamchange == -1) + { + JoinBestTeam( this, false, true ); + } + else if(this.killindicator_teamchange == -2) + { + if(blockSpectators) + Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_SPECTATE_WARNING, autocvar_g_maxplayers_spectator_blocktime); + PutObserverInServer(this); + } + else + SV_ChangeTeam(this, this.killindicator_teamchange - 1); + this.killindicator_teamchange = 0; +} + +void ClientKill_Now(entity this) +{ + if(this.vehicle) + { + vehicles_exit(this.vehicle, VHEF_RELEASE); + if(!this.killindicator_teamchange) + { + this.vehicle_health = -1; + Damage(this, this, this, 1 , DEATH_KILL.m_id, this.origin, '0 0 0'); + } + } + + if(this.killindicator && !wasfreed(this.killindicator)) + delete(this.killindicator); + + this.killindicator = NULL; + + if(this.killindicator_teamchange) + ClientKill_Now_TeamChange(this); + + if(!IS_SPEC(this) && !IS_OBSERVER(this)) + Damage(this, this, this, 100000, DEATH_KILL.m_id, this.origin, '0 0 0'); + + // now I am sure the player IS dead +} +void KillIndicator_Think(entity this) +{ + if (gameover) + { + this.owner.killindicator = NULL; + delete(this); + return; + } + + if (this.owner.alpha < 0 && !this.owner.vehicle) + { + this.owner.killindicator = NULL; + delete(this); + return; + } + + if(this.cnt <= 0) + { + ClientKill_Now(this.owner); + return; + } + else if(g_cts && this.health == 1) // health == 1 means that it's silent + { + this.nextthink = time + 1; + this.cnt -= 1; + } + else + { + if(this.cnt <= 10) + setmodel(this, MDL_NUM(this.cnt)); + if(IS_REAL_CLIENT(this.owner)) + { + if(this.cnt <= 10) + { Send_Notification(NOTIF_ONE, this.owner, MSG_ANNCE, Announcer_PickNumber(CNT_KILL, this.cnt)); } + } + this.nextthink = time + 1; + this.cnt -= 1; + } +} + +float clientkilltime; +void ClientKill_TeamChange (entity this, float targetteam) // 0 = don't change, -1 = auto, -2 = spec +{ + float killtime; + float starttime; + + if (gameover) + return; + + killtime = autocvar_g_balance_kill_delay; + + if(g_race_qualifying || g_cts) + killtime = 0; + + if(MUTATOR_CALLHOOK(ClientKill, this, killtime)) + return; + + this.killindicator_teamchange = targetteam; + + if(!this.killindicator) + { + if(!IS_DEAD(this)) + { + killtime = max(killtime, this.clientkill_nexttime - time); + this.clientkill_nexttime = time + killtime + autocvar_g_balance_kill_antispam; + } + + if(killtime <= 0 || !IS_PLAYER(this) || IS_DEAD(this)) + { + ClientKill_Now(this); + } + else + { + starttime = max(time, clientkilltime); + + this.killindicator = spawn(); + this.killindicator.owner = this; + this.killindicator.scale = 0.5; + setattachment(this.killindicator, this, ""); + setorigin(this.killindicator, '0 0 52'); + setthink(this.killindicator, KillIndicator_Think); + this.killindicator.nextthink = starttime + (this.lip) * 0.05; + clientkilltime = max(clientkilltime, this.killindicator.nextthink + 0.05); + this.killindicator.cnt = ceil(killtime); + this.killindicator.count = bound(0, ceil(killtime), 10); + //sprint(this, strcat("^1You'll be dead in ", ftos(this.killindicator.cnt), " seconds\n")); + + FOREACH_ENTITY_ENT(enemy, this, + { + if(it.classname != "body") + continue; + it.killindicator = spawn(); + it.killindicator.owner = it; + it.killindicator.scale = 0.5; + setattachment(it.killindicator, it, ""); + setorigin(it.killindicator, '0 0 52'); + setthink(it.killindicator, KillIndicator_Think); + it.killindicator.nextthink = starttime + (it.lip) * 0.05; + //clientkilltime = max(clientkilltime, it.killindicator.nextthink + 0.05); + it.killindicator.cnt = ceil(killtime); + }); + this.lip = 0; + } + } + if(this.killindicator) + { + if(targetteam == 0) // just die + { + this.killindicator.colormod = '0 0 0'; + if(IS_REAL_CLIENT(this)) + if(this.killindicator.cnt > 0) + Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_TEAMCHANGE_SUICIDE, this.killindicator.cnt); + } + else if(targetteam == -1) // auto + { + this.killindicator.colormod = '0 1 0'; + if(IS_REAL_CLIENT(this)) + if(this.killindicator.cnt > 0) + Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_TEAMCHANGE_AUTO, this.killindicator.cnt); + } + else if(targetteam == -2) // spectate + { + this.killindicator.colormod = '0.5 0.5 0.5'; + if(IS_REAL_CLIENT(this)) + if(this.killindicator.cnt > 0) + Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_TEAMCHANGE_SPECTATE, this.killindicator.cnt); + } + else + { + this.killindicator.colormod = Team_ColorRGB(targetteam); + if(IS_REAL_CLIENT(this)) + if(this.killindicator.cnt > 0) + Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, APP_TEAM_NUM(targetteam, CENTER_TEAMCHANGE), this.killindicator.cnt); + } + } + +} + +void ClientKill (entity this) +{ + if(gameover) return; + if(this.player_blocked) return; + if(STAT(FROZEN, this)) return; + + ClientKill_TeamChange(this, 0); +} + +void FixClientCvars(entity e) +{ + // send prediction settings to the client + stuffcmd(e, "\nin_bindmap 0 0\n"); + if(autocvar_g_antilag == 3) // client side hitscan + stuffcmd(e, "cl_cmd settemp cl_prydoncursor_notrace 0\n"); + if(autocvar_sv_gentle) + stuffcmd(e, "cl_cmd settemp cl_gentle 1\n"); + + MUTATOR_CALLHOOK(FixClientCvars, e); +} + +float PlayerInIDList(entity p, string idlist) +{ + float n, i; + string s; + + // NOTE: we do NOT check crypto_idfp_signed here, an unsigned ID is fine too for this + if (!p.crypto_idfp) + return 0; + + // this function allows abbreviated player IDs too! + n = tokenize_console(idlist); + for(i = 0; i < n; ++i) + { + s = argv(i); + if(s == substring(p.crypto_idfp, 0, strlen(s))) + return 1; + } + + return 0; +} + +#ifdef DP_EXT_PRECONNECT +/* +============= +ClientPreConnect + +Called once (not at each match start) when a client begins a connection to the server +============= +*/ +void ClientPreConnect () +{ENGINE_EVENT(); + if(autocvar_sv_eventlog) + { + GameLogEcho(sprintf(":connect:%d:%d:%s", + this.playerid, + etof(this), + ((IS_REAL_CLIENT(this)) ? this.netaddress : "bot") + )); + } +} +#endif + +/** +============= +ClientConnect + +Called when a client connects to the server +============= +*/ +void ClientConnect(entity this) +{ + if (Ban_MaybeEnforceBanOnce(this)) return; + assert(!IS_CLIENT(this), return); + this.flags |= FL_CLIENT; + assert(player_count >= 0, player_count = 0); + +#ifdef WATERMARK + Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_WATERMARK, WATERMARK); +#endif + this.version_nagtime = time + 10 + random() * 10; + TRANSMUTE(Client, this); + + // identify the right forced team + if (autocvar_g_campaign) + { + if (IS_REAL_CLIENT(this)) // only players, not bots + { + switch (autocvar_g_campaign_forceteam) + { + case 1: this.team_forced = NUM_TEAM_1; break; + case 2: this.team_forced = NUM_TEAM_2; break; + case 3: this.team_forced = NUM_TEAM_3; break; + case 4: this.team_forced = NUM_TEAM_4; break; + default: this.team_forced = 0; + } + } + } + else if (PlayerInIDList(this, autocvar_g_forced_team_red)) this.team_forced = NUM_TEAM_1; + else if (PlayerInIDList(this, autocvar_g_forced_team_blue)) this.team_forced = NUM_TEAM_2; + else if (PlayerInIDList(this, autocvar_g_forced_team_yellow)) this.team_forced = NUM_TEAM_3; + else if (PlayerInIDList(this, autocvar_g_forced_team_pink)) this.team_forced = NUM_TEAM_4; + else switch (autocvar_g_forced_team_otherwise) + { + default: this.team_forced = 0; break; + case "red": this.team_forced = NUM_TEAM_1; break; + case "blue": this.team_forced = NUM_TEAM_2; break; + case "yellow": this.team_forced = NUM_TEAM_3; break; + case "pink": this.team_forced = NUM_TEAM_4; break; + case "spectate": + case "spectator": + this.team_forced = -1; + break; + } + if (!teamplay && this.team_forced > 0) this.team_forced = 0; + + { + int id = this.playerid; + this.playerid = 0; // silent + JoinBestTeam(this, false, false); // if the team number is valid, keep it + this.playerid = id; + } + + if (autocvar_sv_spectate || autocvar_g_campaign || this.team_forced < 0) { + TRANSMUTE(Observer, this); + } else { + if (!teamplay || autocvar_g_balance_teams) { + TRANSMUTE(Player, this); + campaign_bots_may_start = true; + } else { + TRANSMUTE(Observer, this); // do it anyway + } + } + + PlayerStats_GameReport_AddEvent(sprintf("kills-%d", this.playerid)); + + // always track bots, don't ask for cl_allow_uidtracking + if (IS_BOT_CLIENT(this)) PlayerStats_GameReport_AddPlayer(this); + + if (autocvar_sv_eventlog) + GameLogEcho(strcat(":join:", ftos(this.playerid), ":", ftos(etof(this)), ":", ((IS_REAL_CLIENT(this)) ? this.netaddress : "bot"), ":", this.netname)); + + LogTeamchange(this.playerid, this.team, 1); + + this.just_joined = true; // stop spamming the eventlog with additional lines when the client connects + + this.netname_previous = strzone(this.netname); + + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, ((teamplay && IS_PLAYER(this)) ? APP_TEAM_ENT(this, INFO_JOIN_CONNECT_TEAM) : INFO_JOIN_CONNECT), this.netname); + + stuffcmd(this, clientstuff, "\n"); + stuffcmd(this, "cl_particles_reloadeffects\n"); // TODO do we still need this? + + FixClientCvars(this); + + // get version info from player + stuffcmd(this, "cmd clientversion $gameversion\n"); + + // notify about available teams + if (teamplay) + { + CheckAllowedTeams(this); + int t = 0; + if (c1 >= 0) t |= BIT(0); + if (c2 >= 0) t |= BIT(1); + if (c3 >= 0) t |= BIT(2); + if (c4 >= 0) t |= BIT(3); + stuffcmd(this, sprintf("set _teams_available %d\n", t)); + } + else + { + stuffcmd(this, "set _teams_available 0\n"); + } + + bot_relinkplayerlist(); + + this.spectatortime = time; + if (blockSpectators) + { + Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_SPECTATE_WARNING, autocvar_g_maxplayers_spectator_blocktime); + } + + this.jointime = time; + this.allowed_timeouts = autocvar_sv_timeout_number; + + if (IS_REAL_CLIENT(this)) + { + if (!autocvar_g_campaign) + { + this.motd_actived_time = -1; + Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_MOTD, getwelcomemessage(this)); + } + + if (g_weaponarena_weapons == WEPSET(TUBA)) + stuffcmd(this, "cl_cmd settemp chase_active 1\n"); + } + + if (!sv_foginterval && world.fog != "") + stuffcmd(this, strcat("\nfog ", world.fog, "\nr_fog_exp2 0\nr_drawfog 1\n")); + + if (autocvar_sv_teamnagger && !(autocvar_bot_vs_human && AvailableTeams() == 2)) + if (!g_ca && !g_cts && !g_race) // teamnagger is currently bad for ca, race & cts + send_CSQC_teamnagger(); + + CSQCMODEL_AUTOINIT(this); + + this.model_randomizer = random(); + + if (IS_REAL_CLIENT(this)) + sv_notice_join(this); + + FOREACH_ENTITY_FLOAT(init_for_player_needed, true, { + it.init_for_player(it, this); + }); + + MUTATOR_CALLHOOK(ClientConnect, this); +} +/* +============= +ClientDisconnect + +Called when a client disconnects from the server +============= +*/ +.entity chatbubbleentity; +void ReadyCount(); +void ClientDisconnect(entity this) +{ + assert(IS_CLIENT(this), return); + + PlayerStats_GameReport_FinalizePlayer(this); + if (this.vehicle) vehicles_exit(this.vehicle, VHEF_RELEASE); + if (this.active_minigame) part_minigame(this); + if (IS_PLAYER(this)) Send_Effect(EFFECT_SPAWN_NEUTRAL, this.origin, '0 0 0', 1); + + if (autocvar_sv_eventlog) + GameLogEcho(strcat(":part:", ftos(this.playerid))); + + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_QUIT_DISCONNECT, this.netname); + + SetSpectatee(this, NULL); + + MUTATOR_CALLHOOK(ClientDisconnect, this); + + ClientState_detach(this); + + Portal_ClearAll(this); + + Unfreeze(this); + + RemoveGrapplingHook(this); + + // Here, everything has been done that requires this player to be a client. + + this.flags &= ~FL_CLIENT; + + if (this.chatbubbleentity) delete(this.chatbubbleentity); + if (this.killindicator) delete(this.killindicator); + + WaypointSprite_PlayerGone(this); + + bot_relinkplayerlist(); + + if (this.netname_previous) strunzone(this.netname_previous); + if (this.clientstatus) strunzone(this.clientstatus); + if (this.weaponorder_byimpulse) strunzone(this.weaponorder_byimpulse); + if (this.personal) delete(this.personal); + + this.playerid = 0; + ReadyCount(); + if (vote_called && IS_REAL_CLIENT(this)) VoteCount(false); +} + +void ChatBubbleThink(entity this) +{ + this.nextthink = time; + if ((this.owner.alpha < 0) || this.owner.chatbubbleentity != this) + { + if(this.owner) // but why can that ever be NULL? + this.owner.chatbubbleentity = NULL; + delete(this); + return; + } + + this.mdl = ""; + + if ( !IS_DEAD(this.owner) && IS_PLAYER(this.owner) ) + { + if ( this.owner.active_minigame ) + this.mdl = "models/sprites/minigame_busy.iqm"; + else if (PHYS_INPUT_BUTTON_CHAT(this.owner)) + this.mdl = "models/misc/chatbubble.spr"; + } + + if ( this.model != this.mdl ) + _setmodel(this, this.mdl); + +} + +void UpdateChatBubble(entity this) +{ + if (this.alpha < 0) + return; + // spawn a chatbubble entity if needed + if (!this.chatbubbleentity) + { + this.chatbubbleentity = new(chatbubbleentity); + this.chatbubbleentity.owner = this; + this.chatbubbleentity.exteriormodeltoclient = this; + setthink(this.chatbubbleentity, ChatBubbleThink); + this.chatbubbleentity.nextthink = time; + setmodel(this.chatbubbleentity, MDL_CHAT); // precision set below + //setorigin(this.chatbubbleentity, this.origin + '0 0 15' + this.maxs_z * '0 0 1'); + setorigin(this.chatbubbleentity, '0 0 15' + this.maxs_z * '0 0 1'); + setattachment(this.chatbubbleentity, this, ""); // sticks to moving player better, also conserves bandwidth + this.chatbubbleentity.mdl = this.chatbubbleentity.model; + //this.chatbubbleentity.model = ""; + this.chatbubbleentity.effects = EF_LOWPRECISION; + } +} + + +// LordHavoc: this hack will be removed when proper _pants/_shirt layers are +// added to the model skins +/*void UpdateColorModHack() +{ + float c; + c = this.clientcolors & 15; + // LordHavoc: only bothering to support white, green, red, yellow, blue + if (!teamplay) this.colormod = '0 0 0'; + else if (c == 0) this.colormod = '1.00 1.00 1.00'; + else if (c == 3) this.colormod = '0.10 1.73 0.10'; + else if (c == 4) this.colormod = '1.73 0.10 0.10'; + else if (c == 12) this.colormod = '1.22 1.22 0.10'; + else if (c == 13) this.colormod = '0.10 0.10 1.73'; + else this.colormod = '1 1 1'; +}*/ + +void respawn(entity this) +{ + if(this.alpha >= 0 && autocvar_g_respawn_ghosts) + { + this.solid = SOLID_NOT; + this.takedamage = DAMAGE_NO; + set_movetype(this, MOVETYPE_FLY); + this.velocity = '0 0 1' * autocvar_g_respawn_ghosts_speed; + this.avelocity = randomvec() * autocvar_g_respawn_ghosts_speed * 3 - randomvec() * autocvar_g_respawn_ghosts_speed * 3; + this.effects |= CSQCMODEL_EF_RESPAWNGHOST; + Send_Effect(EFFECT_RESPAWN_GHOST, this.origin, '0 0 0', 1); + if(autocvar_g_respawn_ghosts_maxtime) + SUB_SetFade (this, time + autocvar_g_respawn_ghosts_maxtime / 2 + random () * (autocvar_g_respawn_ghosts_maxtime - autocvar_g_respawn_ghosts_maxtime / 2), 1.5); + } + + CopyBody(this, 1); + + this.effects |= EF_NODRAW; // prevent another CopyBody + PutClientInServer(this); +} + +void play_countdown(entity this, float finished, Sound samp) +{ + TC(Sound, samp); + if(IS_REAL_CLIENT(this)) + if(floor(finished - time - frametime) != floor(finished - time)) + if(finished - time < 6) + sound (this, CH_INFO, samp, VOL_BASE, ATTEN_NORM); +} + +void player_powerups(entity this) +{ + // add a way to see what the items were BEFORE all of these checks for the mutator hook + int items_prev = this.items; + + if((this.items & IT_USING_JETPACK) && !IS_DEAD(this) && !gameover) + this.modelflags |= MF_ROCKET; + else + this.modelflags &= ~MF_ROCKET; + + this.effects &= ~(EF_RED | EF_BLUE | EF_ADDITIVE | EF_FULLBRIGHT | EF_FLAME | EF_NODEPTHTEST); + + if((this.alpha < 0 || IS_DEAD(this)) && !this.vehicle) // don't apply the flags if the player is gibbed + return; + + Fire_ApplyDamage(this); + Fire_ApplyEffect(this); + + if (!g_instagib) + { + if (this.items & ITEM_Strength.m_itemid) + { + play_countdown(this, this.strength_finished, SND_POWEROFF); + this.effects = this.effects | (EF_BLUE | EF_ADDITIVE | EF_FULLBRIGHT); + if (time > this.strength_finished) + { + this.items = this.items - (this.items & ITEM_Strength.m_itemid); + //Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_POWERDOWN_STRENGTH, this.netname); + Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_POWERDOWN_STRENGTH); + } + } + else + { + if (time < this.strength_finished) + { + this.items = this.items | ITEM_Strength.m_itemid; + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_POWERUP_STRENGTH, this.netname); + Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_POWERUP_STRENGTH); + } + } + if (this.items & ITEM_Shield.m_itemid) + { + play_countdown(this, this.invincible_finished, SND_POWEROFF); + this.effects = this.effects | (EF_RED | EF_ADDITIVE | EF_FULLBRIGHT); + if (time > this.invincible_finished) + { + this.items = this.items - (this.items & ITEM_Shield.m_itemid); + //Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_POWERDOWN_SHIELD, this.netname); + Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_POWERDOWN_SHIELD); + } + } + else + { + if (time < this.invincible_finished) + { + this.items = this.items | ITEM_Shield.m_itemid; + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_POWERUP_SHIELD, this.netname); + Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_POWERUP_SHIELD); + } + } + if (this.items & IT_SUPERWEAPON) + { + if (!(this.weapons & WEPSET_SUPERWEAPONS)) + { + this.superweapons_finished = 0; + this.items = this.items - (this.items & IT_SUPERWEAPON); + //Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SUPERWEAPON_LOST, this.netname); + Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_SUPERWEAPON_LOST); + } + else if (this.items & IT_UNLIMITED_SUPERWEAPONS) + { + // don't let them run out + } + else + { + play_countdown(this, this.superweapons_finished, SND_POWEROFF); + if (time > this.superweapons_finished) + { + this.items = this.items - (this.items & IT_SUPERWEAPON); + this.weapons &= ~WEPSET_SUPERWEAPONS; + //Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SUPERWEAPON_BROKEN, this.netname); + Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_SUPERWEAPON_BROKEN); + } + } + } + else if(this.weapons & WEPSET_SUPERWEAPONS) + { + if (time < this.superweapons_finished || (this.items & IT_UNLIMITED_SUPERWEAPONS)) + { + this.items = this.items | IT_SUPERWEAPON; + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SUPERWEAPON_PICKUP, this.netname); + Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_SUPERWEAPON_PICKUP); + } + else + { + this.superweapons_finished = 0; + this.weapons &= ~WEPSET_SUPERWEAPONS; + } + } + else + { + this.superweapons_finished = 0; + } + } + + if(autocvar_g_nodepthtestplayers) + this.effects = this.effects | EF_NODEPTHTEST; + + if(autocvar_g_fullbrightplayers) + this.effects = this.effects | EF_FULLBRIGHT; + + if (time >= game_starttime) + if (time < this.spawnshieldtime) + this.effects = this.effects | (EF_ADDITIVE | EF_FULLBRIGHT); + + MUTATOR_CALLHOOK(PlayerPowerups, this, items_prev); +} + +float CalcRegen(float current, float stable, float regenfactor, float regenframetime) +{ + if(current > stable) + return current; + else if(current > stable - 0.25) // when close enough, "snap" + return stable; + else + return min(stable, current + (stable - current) * regenfactor * regenframetime); +} + +float CalcRot(float current, float stable, float rotfactor, float rotframetime) +{ + if(current < stable) + return current; + else if(current < stable + 0.25) // when close enough, "snap" + return stable; + else + return max(stable, current + (stable - current) * rotfactor * rotframetime); +} + +float CalcRotRegen(float current, float regenstable, float regenfactor, float regenlinear, float regenframetime, float rotstable, float rotfactor, float rotlinear, float rotframetime, float limit) +{ + if(current > rotstable) + { + if(rotframetime > 0) + { + current = CalcRot(current, rotstable, rotfactor, rotframetime); + current = max(rotstable, current - rotlinear * rotframetime); + } + } + else if(current < regenstable) + { + if(regenframetime > 0) + { + current = CalcRegen(current, regenstable, regenfactor, regenframetime); + current = min(regenstable, current + regenlinear * regenframetime); + } + } + + if(current > limit) + current = limit; + + return current; +} + +void player_regen(entity this) +{ + float max_mod, regen_mod, rot_mod, limit_mod; + max_mod = regen_mod = rot_mod = limit_mod = 1; + + float regen_health = autocvar_g_balance_health_regen; + float regen_health_linear = autocvar_g_balance_health_regenlinear; + float regen_health_rot = autocvar_g_balance_health_rot; + float regen_health_rotlinear = autocvar_g_balance_health_rotlinear; + float regen_health_stable = autocvar_g_balance_health_regenstable; + float regen_health_rotstable = autocvar_g_balance_health_rotstable; + bool mutator_returnvalue = MUTATOR_CALLHOOK(PlayerRegen, this, max_mod, regen_mod, rot_mod, limit_mod, regen_health, regen_health_linear, regen_health_rot, + regen_health_rotlinear, regen_health_stable, regen_health_rotstable); + max_mod = M_ARGV(1, float); + regen_mod = M_ARGV(2, float); + rot_mod = M_ARGV(3, float); + limit_mod = M_ARGV(4, float); + regen_health = M_ARGV(5, float); + regen_health_linear = M_ARGV(6, float); + regen_health_rot = M_ARGV(7, float); + regen_health_rotlinear = M_ARGV(8, float); + regen_health_stable = M_ARGV(9, float); + regen_health_rotstable = M_ARGV(10, float); + + + if(!mutator_returnvalue) + if(!STAT(FROZEN, this)) + { + float mina, maxa, limith, limita; + maxa = autocvar_g_balance_armor_rotstable; + mina = autocvar_g_balance_armor_regenstable; + limith = autocvar_g_balance_health_limit; + limita = autocvar_g_balance_armor_limit; + + regen_health_rotstable = regen_health_rotstable * max_mod; + regen_health_stable = regen_health_stable * max_mod; + limith = limith * limit_mod; + limita = limita * limit_mod; + + this.armorvalue = CalcRotRegen(this.armorvalue, mina, autocvar_g_balance_armor_regen, autocvar_g_balance_armor_regenlinear, regen_mod * frametime * (time > this.pauseregen_finished), maxa, autocvar_g_balance_armor_rot, autocvar_g_balance_armor_rotlinear, rot_mod * frametime * (time > this.pauserotarmor_finished), limita); + this.health = CalcRotRegen(this.health, regen_health_stable, regen_health, regen_health_linear, regen_mod * frametime * (time > this.pauseregen_finished), regen_health_rotstable, regen_health_rot, regen_health_rotlinear, rot_mod * frametime * (time > this.pauserothealth_finished), limith); + } + + // if player rotted to death... die! + // check this outside above checks, as player may still be able to rot to death + if(this.health < 1) + { + if(this.vehicle) + vehicles_exit(this.vehicle, VHEF_RELEASE); + if(this.event_damage) + this.event_damage(this, this, this, 1, DEATH_ROT.m_id, this.origin, '0 0 0'); + } + + if (!(this.items & IT_UNLIMITED_WEAPON_AMMO)) + { + float minf, maxf, limitf; + + maxf = autocvar_g_balance_fuel_rotstable; + minf = autocvar_g_balance_fuel_regenstable; + limitf = autocvar_g_balance_fuel_limit; + + this.ammo_fuel = CalcRotRegen(this.ammo_fuel, minf, autocvar_g_balance_fuel_regen, autocvar_g_balance_fuel_regenlinear, frametime * (time > this.pauseregen_finished) * ((this.items & ITEM_JetpackRegen.m_itemid) != 0), maxf, autocvar_g_balance_fuel_rot, autocvar_g_balance_fuel_rotlinear, frametime * (time > this.pauserotfuel_finished), limitf); + } +} + +bool zoomstate_set; +void SetZoomState(entity this, float z) +{ + if(z != this.zoomstate) + { + this.zoomstate = z; + ClientData_Touch(this); + } + zoomstate_set = true; +} + +void GetPressedKeys(entity this) +{ + MUTATOR_CALLHOOK(GetPressedKeys, this); + int keys = this.pressedkeys; + keys = BITSET(keys, KEY_FORWARD, this.movement.x > 0); + keys = BITSET(keys, KEY_BACKWARD, this.movement.x < 0); + keys = BITSET(keys, KEY_RIGHT, this.movement.y > 0); + keys = BITSET(keys, KEY_LEFT, this.movement.y < 0); + + keys = BITSET(keys, KEY_JUMP, PHYS_INPUT_BUTTON_JUMP(this)); + keys = BITSET(keys, KEY_CROUCH, PHYS_INPUT_BUTTON_CROUCH(this)); + keys = BITSET(keys, KEY_ATCK, PHYS_INPUT_BUTTON_ATCK(this)); + keys = BITSET(keys, KEY_ATCK2, PHYS_INPUT_BUTTON_ATCK2(this)); + this.pressedkeys = keys; +} + +/* +====================== +spectate mode routines +====================== +*/ + +void SpectateCopy(entity this, entity spectatee) +{ + TC(Client, this); TC(Client, spectatee); + + MUTATOR_CALLHOOK(SpectateCopy, spectatee, this); + PS(this) = PS(spectatee); + this.armortype = spectatee.armortype; + this.armorvalue = spectatee.armorvalue; + this.ammo_cells = spectatee.ammo_cells; + this.ammo_plasma = spectatee.ammo_plasma; + this.ammo_shells = spectatee.ammo_shells; + this.ammo_nails = spectatee.ammo_nails; + this.ammo_rockets = spectatee.ammo_rockets; + this.ammo_fuel = spectatee.ammo_fuel; + this.clip_load = spectatee.clip_load; + this.clip_size = spectatee.clip_size; + this.effects = spectatee.effects & EFMASK_CHEAP; // eat performance + this.health = spectatee.health; + this.impulse = 0; + this.items = spectatee.items; + this.last_pickup = spectatee.last_pickup; + this.hit_time = spectatee.hit_time; + this.strength_finished = spectatee.strength_finished; + this.invincible_finished = spectatee.invincible_finished; + this.pressedkeys = spectatee.pressedkeys; + this.weapons = spectatee.weapons; + this.vortex_charge = spectatee.vortex_charge; + this.vortex_chargepool_ammo = spectatee.vortex_chargepool_ammo; + this.hagar_load = spectatee.hagar_load; + this.arc_heat_percent = spectatee.arc_heat_percent; + this.minelayer_mines = spectatee.minelayer_mines; + this.punchangle = spectatee.punchangle; + this.view_ofs = spectatee.view_ofs; + this.velocity = spectatee.velocity; + this.dmg_take = spectatee.dmg_take; + this.dmg_save = spectatee.dmg_save; + this.dmg_inflictor = spectatee.dmg_inflictor; + this.v_angle = spectatee.v_angle; + this.angles = spectatee.v_angle; + STAT(FROZEN, this) = STAT(FROZEN, spectatee); + this.revive_progress = spectatee.revive_progress; + if(!PHYS_INPUT_BUTTON_USE(this) && STAT(CAMERA_SPECTATOR, this) != 2) + this.fixangle = true; + setorigin(this, spectatee.origin); + setsize(this, spectatee.mins, spectatee.maxs); + SetZoomState(this, spectatee.zoomstate); + + anticheat_spectatecopy(this, spectatee); + this.hud = spectatee.hud; + if(spectatee.vehicle) + { + this.angles = spectatee.v_angle; + + //this.fixangle = false; + //this.velocity = spectatee.vehicle.velocity; + this.vehicle_health = spectatee.vehicle_health; + this.vehicle_shield = spectatee.vehicle_shield; + this.vehicle_energy = spectatee.vehicle_energy; + this.vehicle_ammo1 = spectatee.vehicle_ammo1; + this.vehicle_ammo2 = spectatee.vehicle_ammo2; + this.vehicle_reload1 = spectatee.vehicle_reload1; + this.vehicle_reload2 = spectatee.vehicle_reload2; + + //msg_entity = this; + + // WriteByte (MSG_ONE, SVC_SETVIEWANGLES); + //WriteAngle(MSG_ONE, spectatee.v_angle.x); + // WriteAngle(MSG_ONE, spectatee.v_angle.y); + // WriteAngle(MSG_ONE, spectatee.v_angle.z); + + //WriteByte (MSG_ONE, SVC_SETVIEW); + // WriteEntity(MSG_ONE, this); + //makevectors(spectatee.v_angle); + //setorigin(this, spectatee.origin - v_forward * 400 + v_up * 300);*/ + } +} + +bool SpectateUpdate(entity this) +{ + if(!this.enemy) + return false; + + if(!IS_PLAYER(this.enemy) || this == this.enemy) + { + SetSpectatee(this, NULL); + return false; + } + + SpectateCopy(this, this.enemy); + + return true; +} + +bool SpectateSet(entity this) +{ + if(!IS_PLAYER(this.enemy)) + return false; + + ClientData_Touch(this.enemy); + + msg_entity = this; + WriteByte(MSG_ONE, SVC_SETVIEW); + WriteEntity(MSG_ONE, this.enemy); + set_movetype(this, MOVETYPE_NONE); + accuracy_resend(this); + + if(!SpectateUpdate(this)) + PutObserverInServer(this); + + return true; +} + +void SetSpectatee(entity this, entity spectatee) +{ + entity old_spectatee = this.enemy; + + this.enemy = spectatee; + + // WEAPONTODO + // these are required to fix the spectator bug with arc + if(old_spectatee) + { + for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) + { + .entity weaponentity = weaponentities[slot]; + if(old_spectatee.(weaponentity).arc_beam) + old_spectatee.(weaponentity).arc_beam.SendFlags |= ARC_SF_SETTINGS; + } + } + if(this.enemy) + { + for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) + { + .entity weaponentity = weaponentities[slot]; + if(this.enemy.(weaponentity).arc_beam) + this.enemy.(weaponentity).arc_beam.SendFlags |= ARC_SF_SETTINGS; + } + } + + // needed to update spectator list + if(old_spectatee) { ClientData_Touch(old_spectatee); } +} + +bool Spectate(entity this, entity pl) +{ + if(MUTATOR_CALLHOOK(SpectateSet, this, pl)) + return false; + pl = M_ARGV(1, entity); + + SetSpectatee(this, pl); + return SpectateSet(this); +} + +bool SpectateNext(entity this) +{ + entity ent = find(this.enemy, classname, STR_PLAYER); + + if (MUTATOR_CALLHOOK(SpectateNext, this, ent)) + ent = M_ARGV(1, entity); + else if (!ent) + ent = find(ent, classname, STR_PLAYER); + + if(ent) { SetSpectatee(this, ent); } + + return SpectateSet(this); +} + +bool SpectatePrev(entity this) +{ + // NOTE: chain order is from the highest to the lower entnum (unlike find) + entity ent = findchain(classname, STR_PLAYER); + if (!ent) // no player + return false; + + entity first = ent; + // skip players until current spectated player + if(this.enemy) + while(ent && ent != this.enemy) + ent = ent.chain; + + switch (MUTATOR_CALLHOOK(SpectatePrev, this, ent, first)) + { + case MUT_SPECPREV_FOUND: + ent = M_ARGV(1, entity); + break; + case MUT_SPECPREV_RETURN: + return true; + case MUT_SPECPREV_CONTINUE: + default: + { + if(ent.chain) + ent = ent.chain; + else + ent = first; + break; + } + } + + SetSpectatee(this, ent); + return SpectateSet(this); +} + +/* +============= +ShowRespawnCountdown() + +Update a respawn countdown display. +============= +*/ +void ShowRespawnCountdown(entity this) +{ + float number; + if(!IS_DEAD(this)) // just respawned? + return; + else + { + number = ceil(this.respawn_time - time); + if(number <= 0) + return; + if(number <= this.respawn_countdown) + { + this.respawn_countdown = number - 1; + if(ceil(this.respawn_time - (time + 0.5)) == number) // only say it if it is the same number even in 0.5s; to prevent overlapping sounds + { Send_Notification(NOTIF_ONE, this, MSG_ANNCE, Announcer_PickNumber(CNT_RESPAWN, number)); } + } + } +} + +void LeaveSpectatorMode(entity this) +{ + if(this.caplayer) + return; + if(nJoinAllowed(this, this)) + { + if(!teamplay || autocvar_g_campaign || autocvar_g_balance_teams || (this.wasplayer && autocvar_g_changeteam_banned) || this.team_forced > 0) + { + TRANSMUTE(Player, this); + + SetSpectatee(this, NULL); + + if(autocvar_g_campaign || autocvar_g_balance_teams) + { JoinBestTeam(this, false, true); } + + if(autocvar_g_campaign) + { campaign_bots_may_start = true; } + + Kill_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CPID_PREVENT_JOIN); + + PutClientInServer(this); + + if(IS_PLAYER(this)) { Send_Notification(NOTIF_ALL, NULL, MSG_INFO, ((teamplay && this.team != -1) ? APP_TEAM_ENT(this, INFO_JOIN_PLAY_TEAM) : INFO_JOIN_PLAY), this.netname); } + } + else + stuffcmd(this, "menu_showteamselect\n"); + } + else + { + // Player may not join because g_maxplayers is set + Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_JOIN_PREVENT); + } +} + +/** + * Determines whether the player is allowed to join. This depends on cvar + * g_maxplayers, if it isn't used this function always return true, otherwise + * it checks whether the number of currently playing players exceeds g_maxplayers. + * @return int number of free slots for players, 0 if none + */ +bool nJoinAllowed(entity this, entity ignore) +{ + if(!ignore) + // this is called that way when checking if anyone may be able to join (to build qcstatus) + // so report 0 free slots if restricted + { + if(autocvar_g_forced_team_otherwise == "spectate") + return false; + if(autocvar_g_forced_team_otherwise == "spectator") + return false; + } + + if(this.team_forced < 0) + return false; // forced spectators can never join + + // TODO simplify this + int totalClients = 0; + int currentlyPlaying = 0; + FOREACH_CLIENT(true, LAMBDA( + if(it != ignore) + ++totalClients; + if(IS_REAL_CLIENT(it)) + if(IS_PLAYER(it) || it.caplayer) + ++currentlyPlaying; + )); + + if (!autocvar_g_maxplayers) + return maxclients - totalClients; + + if(currentlyPlaying < autocvar_g_maxplayers) + return min(maxclients - totalClients, autocvar_g_maxplayers - currentlyPlaying); + + return false; +} + +/** + * Checks whether the client is an observer or spectator, if so, he will get kicked after + * g_maxplayers_spectator_blocktime seconds + */ +void checkSpectatorBlock(entity this) +{ + if(IS_SPEC(this) || IS_OBSERVER(this)) + if(!this.caplayer) + if(IS_REAL_CLIENT(this)) + { + if( time > (this.spectatortime + autocvar_g_maxplayers_spectator_blocktime) ) { + Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_QUIT_KICK_SPECTATING); + dropclient(this); + } + } +} + +void PrintWelcomeMessage(entity this) +{ + if(this.motd_actived_time == 0) + { + if (autocvar_g_campaign) { + if ((IS_PLAYER(this) && PHYS_INPUT_BUTTON_INFO(this)) || (!IS_PLAYER(this))) { + this.motd_actived_time = time; + Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_MOTD, campaign_message); + } + } else { + if (PHYS_INPUT_BUTTON_INFO(this)) { + this.motd_actived_time = time; + Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_MOTD, getwelcomemessage(this)); + } + } + } + else if(this.motd_actived_time > 0) // showing MOTD or campaign message + { + if (autocvar_g_campaign) { + if (PHYS_INPUT_BUTTON_INFO(this)) + this.motd_actived_time = time; + else if ((time - this.motd_actived_time > 2) && IS_PLAYER(this)) { // hide it some seconds after BUTTON_INFO has been released + this.motd_actived_time = 0; + Kill_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CPID_MOTD); + } + } else { + if (PHYS_INPUT_BUTTON_INFO(this)) + this.motd_actived_time = time; + else if (time - this.motd_actived_time > 2) { // hide it some seconds after BUTTON_INFO has been released + this.motd_actived_time = 0; + Kill_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CPID_MOTD); + } + } + } + else //if(this.motd_actived_time < 0) // just connected, motd is active + { + if(PHYS_INPUT_BUTTON_INFO(this)) // BUTTON_INFO hides initial MOTD + this.motd_actived_time = -2; // wait until BUTTON_INFO gets released + else if(this.motd_actived_time == -2 || IS_PLAYER(this) || IS_SPEC(this)) + { + // instanctly hide MOTD + this.motd_actived_time = 0; + Kill_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CPID_MOTD); + } + } +} + +void ObserverThink(entity this) +{ + if ( this.impulse ) + { + MinigameImpulse(this, this.impulse); + this.impulse = 0; + } + if (this.flags & FL_JUMPRELEASED) { + if (PHYS_INPUT_BUTTON_JUMP(this) && !this.version_mismatch) { + this.flags &= ~FL_JUMPRELEASED; + this.flags |= FL_SPAWNING; + } else if(PHYS_INPUT_BUTTON_ATCK(this) && !this.version_mismatch) { + this.flags &= ~FL_JUMPRELEASED; + if(SpectateNext(this)) { + TRANSMUTE(Spectator, this); + } + } else { + int preferred_movetype = ((!PHYS_INPUT_BUTTON_USE(this) ? this.cvar_cl_clippedspectating : !this.cvar_cl_clippedspectating) ? MOVETYPE_FLY_WORLDONLY : MOVETYPE_NOCLIP); + set_movetype(this, preferred_movetype); + } + } else { + if (!(PHYS_INPUT_BUTTON_ATCK(this) || PHYS_INPUT_BUTTON_JUMP(this))) { + this.flags |= FL_JUMPRELEASED; + if(this.flags & FL_SPAWNING) + { + this.flags &= ~FL_SPAWNING; + LeaveSpectatorMode(this); + return; + } + } + } +} + +void SpectatorThink(entity this) +{ + if ( this.impulse ) + { + if(MinigameImpulse(this, this.impulse)) + this.impulse = 0; + + if (this.impulse == IMP_weapon_drop.impulse) + { + STAT(CAMERA_SPECTATOR, this) = (STAT(CAMERA_SPECTATOR, this) + 1) % 3; + this.impulse = 0; + return; + } + } + if (this.flags & FL_JUMPRELEASED) { + if (PHYS_INPUT_BUTTON_JUMP(this) && !this.version_mismatch) { + this.flags &= ~FL_JUMPRELEASED; + this.flags |= FL_SPAWNING; + } else if(PHYS_INPUT_BUTTON_ATCK(this) || this.impulse == 10 || this.impulse == 15 || this.impulse == 18 || (this.impulse >= 200 && this.impulse <= 209)) { + this.flags &= ~FL_JUMPRELEASED; + if(SpectateNext(this)) { + TRANSMUTE(Spectator, this); + } else { + TRANSMUTE(Observer, this); + PutClientInServer(this); + } + this.impulse = 0; + } else if(this.impulse == 12 || this.impulse == 16 || this.impulse == 19 || (this.impulse >= 220 && this.impulse <= 229)) { + this.flags &= ~FL_JUMPRELEASED; + if(SpectatePrev(this)) { + TRANSMUTE(Spectator, this); + } else { + TRANSMUTE(Observer, this); + PutClientInServer(this); + } + this.impulse = 0; + } else if (PHYS_INPUT_BUTTON_ATCK2(this)) { + this.flags &= ~FL_JUMPRELEASED; + TRANSMUTE(Observer, this); + PutClientInServer(this); + } else { + if(!SpectateUpdate(this)) + PutObserverInServer(this); + } + } else { + if (!(PHYS_INPUT_BUTTON_ATCK(this) || PHYS_INPUT_BUTTON_ATCK2(this))) { + this.flags |= FL_JUMPRELEASED; + if(this.flags & FL_SPAWNING) + { + this.flags &= ~FL_SPAWNING; + LeaveSpectatorMode(this); + return; + } + } + if(!SpectateUpdate(this)) + PutObserverInServer(this); + } + + this.flags |= FL_CLIENT | FL_NOTARGET; +} + +void vehicles_enter (entity pl, entity veh); +void PlayerUseKey(entity this) +{ + if (!IS_PLAYER(this)) + return; + + if(this.vehicle) + { + if(!gameover) + { + vehicles_exit(this.vehicle, VHEF_NORMAL); + return; + } + } + else if(autocvar_g_vehicles_enter) + { + if(!STAT(FROZEN, this)) + if(!IS_DEAD(this)) + if(!gameover) + { + entity head, closest_target = NULL; + head = WarpZone_FindRadius(this.origin, autocvar_g_vehicles_enter_radius, true); + + while(head) // find the closest acceptable target to enter + { + if(IS_VEHICLE(head)) + if(!IS_DEAD(head)) + if(!head.owner || ((head.vehicle_flags & VHF_MULTISLOT) && SAME_TEAM(head.owner, this))) + if(head.takedamage != DAMAGE_NO) + { + if(closest_target) + { + if(vlen2(this.origin - head.origin) < vlen2(this.origin - closest_target.origin)) + { closest_target = head; } + } + else { closest_target = head; } + } + + head = head.chain; + } + + if(closest_target) { vehicles_enter(this, closest_target); return; } + } + } + + // a use key was pressed; call handlers + MUTATOR_CALLHOOK(PlayerUseKey, this); +} + + +/* +============= +PlayerPreThink + +Called every frame for each client before the physics are run +============= +*/ +.float usekeypressed; +.float last_vehiclecheck; +.int items_added; +void PlayerPreThink (entity this) +{ + WarpZone_PlayerPhysics_FixVAngle(this); + + STAT(GAMESTARTTIME, this) = game_starttime; + STAT(ROUNDSTARTTIME, this) = round_starttime; + STAT(ALLOW_OLDVORTEXBEAM, this) = autocvar_g_allow_oldvortexbeam; + STAT(LEADLIMIT, this) = autocvar_leadlimit; + + STAT(WEAPONSINMAP, this) = weaponsInMap; + + if (frametime) { + // physics frames: update anticheat stuff + anticheat_prethink(this); + } + + if (blockSpectators && frametime) { + // WORKAROUND: only use dropclient in server frames (frametime set). + // Never use it in cl_movement frames (frametime zero). + checkSpectatorBlock(this); + } + + zoomstate_set = false; + + // Check for nameless players + if (isInvisibleString(this.netname)) { + this.netname = strzone(sprintf("Player#%d", this.playerid)); + // stuffcmd(this, strcat("name ", this.netname, "\n")); // maybe? + } + if (this.netname != this.netname_previous) { + if (autocvar_sv_eventlog) { + GameLogEcho(strcat(":name:", ftos(this.playerid), ":", this.netname)); + } + if (this.netname_previous) strunzone(this.netname_previous); + this.netname_previous = strzone(this.netname); + } + + // version nagging + if (this.version_nagtime && this.cvar_g_xonoticversion && time > this.version_nagtime) { + this.version_nagtime = 0; + if (strstrofs(this.cvar_g_xonoticversion, "git", 0) >= 0 || strstrofs(this.cvar_g_xonoticversion, "autobuild", 0) >= 0) { + // git client + } else if (strstrofs(autocvar_g_xonoticversion, "git", 0) >= 0 || strstrofs(autocvar_g_xonoticversion, "autobuild", 0) >= 0) { + // git server + Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_VERSION_BETA, autocvar_g_xonoticversion, this.cvar_g_xonoticversion); + } else { + int r = vercmp(this.cvar_g_xonoticversion, autocvar_g_xonoticversion); + if (r < 0) { // old client + Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_VERSION_OUTDATED, autocvar_g_xonoticversion, this.cvar_g_xonoticversion); + } else if (r > 0) { // old server + Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_VERSION_OLD, autocvar_g_xonoticversion, this.cvar_g_xonoticversion); + } + } + } + + // GOD MODE info + if (!(this.flags & FL_GODMODE) && this.max_armorvalue) + { + Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_GODMODE_OFF, this.max_armorvalue); + this.max_armorvalue = 0; + } + + if (STAT(FROZEN, this) == 2) + { + this.revive_progress = bound(0, this.revive_progress + frametime * this.revive_speed, 1); + this.health = max(1, this.revive_progress * start_health); + this.iceblock.alpha = bound(0.2, 1 - this.revive_progress, 1); + + if (this.revive_progress >= 1) + Unfreeze(this); + } + else if (STAT(FROZEN, this) == 3) + { + this.revive_progress = bound(0, this.revive_progress - frametime * this.revive_speed, 1); + this.health = max(0, autocvar_g_nades_ice_health + (start_health-autocvar_g_nades_ice_health) * this.revive_progress ); + + if (this.health < 1) + { + if (this.vehicle) + vehicles_exit(this.vehicle, VHEF_RELEASE); + if(this.event_damage) + this.event_damage(this, this, this.frozen_by, 1, DEATH_NADE_ICE_FREEZE.m_id, this.origin, '0 0 0'); + } + else if (this.revive_progress <= 0) + Unfreeze(this); + } + + MUTATOR_CALLHOOK(PlayerPreThink, this); + + if(autocvar_g_vehicles_enter && (time > this.last_vehiclecheck) && !gameover && !this.vehicle) + if(IS_PLAYER(this) && !STAT(FROZEN, this) && !IS_DEAD(this)) + { + FOREACH_ENTITY_RADIUS(this.origin, autocvar_g_vehicles_enter_radius, IS_VEHICLE(it), + { + if(!IS_DEAD(it) && it.takedamage != DAMAGE_NO) + if((it.vehicle_flags & VHF_MULTISLOT) && SAME_TEAM(it.owner, this)) + { + Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_VEHICLE_ENTER_GUNNER); + } + else if(!it.owner) + { + if(!it.team || SAME_TEAM(this, it)) + Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_VEHICLE_ENTER); + else if(autocvar_g_vehicles_steal) + Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_VEHICLE_ENTER_STEAL); + } + }); + + this.last_vehiclecheck = time + 1; + } + + if(!this.cvar_cl_newusekeysupported) // FIXME remove this - it was a stupid idea to begin with, we can JUST use the button + { + if(PHYS_INPUT_BUTTON_USE(this) && !this.usekeypressed) + PlayerUseKey(this); + this.usekeypressed = PHYS_INPUT_BUTTON_USE(this); + } + + if (IS_REAL_CLIENT(this)) + PrintWelcomeMessage(this); + + if (IS_PLAYER(this)) { + CheckRules_Player(this); + + if (intermission_running) { + IntermissionThink(this); + return; + } + + if (timeout_status == TIMEOUT_ACTIVE) { + // don't allow the player to turn around while game is paused + // FIXME turn this into CSQC stuff + this.v_angle = this.lastV_angle; + this.angles = this.lastV_angle; + this.fixangle = true; + } + + if (frametime) player_powerups(this); + + if (IS_DEAD(this)) { + if (this.personal && g_race_qualifying) { + if (time > this.respawn_time) { + STAT(RESPAWN_TIME, this) = this.respawn_time = time + 1; // only retry once a second + respawn(this); + this.impulse = CHIMPULSE_SPEEDRUN.impulse; + } + } else { + if (frametime) player_anim(this); + bool button_pressed = (PHYS_INPUT_BUTTON_ATCK(this) || PHYS_INPUT_BUTTON_JUMP(this) || PHYS_INPUT_BUTTON_ATCK2(this) || PHYS_INPUT_BUTTON_HOOK(this) || PHYS_INPUT_BUTTON_USE(this)); + + switch(this.deadflag) + { + case DEAD_DYING: + { + if ((this.respawn_flags & RESPAWN_FORCE) && !(this.respawn_time < this.respawn_time_max)) + this.deadflag = DEAD_RESPAWNING; + else if (!button_pressed || (this.respawn_flags & RESPAWN_FORCE)) + this.deadflag = DEAD_DEAD; + break; + } + case DEAD_DEAD: + { + if (button_pressed) + this.deadflag = DEAD_RESPAWNABLE; + else if (time >= this.respawn_time_max && (this.respawn_flags & RESPAWN_FORCE)) + this.deadflag = DEAD_RESPAWNING; + break; + } + case DEAD_RESPAWNABLE: + { + if (!button_pressed || (this.respawn_flags & RESPAWN_FORCE)) + this.deadflag = DEAD_RESPAWNING; + break; + } + case DEAD_RESPAWNING: + { + if (time > this.respawn_time) + { + this.respawn_time = time + 1; // only retry once a second + this.respawn_time_max = this.respawn_time; + respawn(this); + } + break; + } + } + + ShowRespawnCountdown(this); + + if (this.respawn_flags & RESPAWN_SILENT) + STAT(RESPAWN_TIME, this) = 0; + else if ((this.respawn_flags & RESPAWN_FORCE) && this.respawn_time < this.respawn_time_max) + { + if (time < this.respawn_time) + STAT(RESPAWN_TIME, this) = this.respawn_time; + else if (this.deadflag != DEAD_RESPAWNING) + STAT(RESPAWN_TIME, this) = -this.respawn_time_max; + } + else + STAT(RESPAWN_TIME, this) = this.respawn_time; + } + + // if respawning, invert stat_respawn_time to indicate this, the client translates it + if (this.deadflag == DEAD_RESPAWNING && STAT(RESPAWN_TIME, this) > 0) + STAT(RESPAWN_TIME, this) *= -1; + + return; + } + + this.prevorigin = this.origin; + + bool do_crouch = PHYS_INPUT_BUTTON_CROUCH(this); + if (this.hook.state) { + do_crouch = false; + } else if (this.waterlevel >= WATERLEVEL_SWIMMING) { + do_crouch = false; + } else if (this.vehicle) { + do_crouch = false; + } else if (STAT(FROZEN, this)) { + do_crouch = false; + } + + if (do_crouch) { + if (!this.crouch) { + this.crouch = true; + this.view_ofs = STAT(PL_CROUCH_VIEW_OFS, this); + setsize(this, STAT(PL_CROUCH_MIN, this), STAT(PL_CROUCH_MAX, this)); + // setanim(this, this.anim_duck, false, true, true); // this anim is BROKEN anyway + } + } else if (this.crouch) { + tracebox(this.origin, STAT(PL_MIN, this), STAT(PL_MAX, this), this.origin, false, this); + if (!trace_startsolid) { + this.crouch = false; + this.view_ofs = STAT(PL_VIEW_OFS, this); + setsize(this, STAT(PL_MIN, this), STAT(PL_MAX, this)); + } + } + + FixPlayermodel(this); + + // LordHavoc: allow firing on move frames (sub-ticrate), this gives better timing on slow servers + //if(frametime) + { + this.items &= ~this.items_added; + + //for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) + //{ + //.entity weaponentity = weaponentities[slot]; + //W_WeaponFrame(this, weaponentity); + //} + .entity weaponentity = weaponentities[0]; // TODO + W_WeaponFrame(this, weaponentity); + + this.items_added = 0; + if (this.items & ITEM_Jetpack.m_itemid && (this.items & ITEM_JetpackRegen.m_itemid || this.ammo_fuel >= 0.01)) + this.items_added |= IT_FUEL; + + this.items |= this.items_added; + } + + player_regen(this); + + // WEAPONTODO: Add a weapon request for this + // rot vortex charge to the charge limit + if (WEP_CVAR(vortex, charge_rot_rate) && this.vortex_charge > WEP_CVAR(vortex, charge_limit) && this.vortex_charge_rottime < time) + this.vortex_charge = bound(WEP_CVAR(vortex, charge_limit), this.vortex_charge - WEP_CVAR(vortex, charge_rot_rate) * frametime / W_TICSPERFRAME, 1); + + if (frametime) player_anim(this); + + // secret status + secrets_setstatus(this); + + // monsters status + monsters_setstatus(this); + + this.dmg_team = max(0, this.dmg_team - autocvar_g_teamdamage_resetspeed * frametime); + } + else if (gameover) { + if (intermission_running) IntermissionThink(this); + return; + } + else if (IS_OBSERVER(this)) { + ObserverThink(this); + } + else if (IS_SPEC(this)) { + SpectatorThink(this); + } + + // WEAPONTODO: Add weapon request for this + if (!zoomstate_set) { + SetZoomState(this, + PHYS_INPUT_BUTTON_ZOOM(this) || PHYS_INPUT_BUTTON_ZOOMSCRIPT(this) + || (PHYS_INPUT_BUTTON_ATCK2(this) && PS(this).m_weapon == WEP_VORTEX) + || (PHYS_INPUT_BUTTON_ATCK2(this) && PS(this).m_weapon == WEP_RIFLE && WEP_CVAR(rifle, secondary) == 0) + ); + } + + int oldspectatee_status = this.spectatee_status; + if (IS_SPEC(this)) { + this.spectatee_status = etof(this.enemy); + } else if (IS_OBSERVER(this)) { + this.spectatee_status = etof(this); + } else { + this.spectatee_status = 0; + } + if (this.spectatee_status != oldspectatee_status) { + ClientData_Touch(this); + if (g_race || g_cts) race_InitSpectator(); + } + + if (this.teamkill_soundtime && time > this.teamkill_soundtime) + { + this.teamkill_soundtime = 0; + + entity e = this.teamkill_soundsource; + entity oldpusher = e.pusher; + e.pusher = this; + PlayerSound(e, playersound_teamshoot, CH_VOICE, VOL_BASEVOICE, VOICETYPE_LASTATTACKER_ONLY); + e.pusher = oldpusher; + } + + if (this.taunt_soundtime && time > this.taunt_soundtime) { + this.taunt_soundtime = 0; + PlayerSound(this, playersound_taunt, CH_VOICE, VOL_BASEVOICE, VOICETYPE_AUTOTAUNT); + } + + target_voicescript_next(this); + + // WEAPONTODO: Move into weaponsystem somehow + // if a player goes unarmed after holding a loaded weapon, empty his clip size and remove the crosshair ammo ring + if (PS(this).m_weapon == WEP_Null) + this.clip_load = this.clip_size = 0; +} + +void DrownPlayer(entity this) +{ + if(IS_DEAD(this)) + return; + + if (this.waterlevel != WATERLEVEL_SUBMERGED || this.vehicle) + { + if(this.air_finished < time) + PlayerSound(this, playersound_gasp, CH_PLAYER, VOL_BASE, VOICETYPE_PLAYERSOUND); + this.air_finished = time + autocvar_g_balance_contents_drowndelay; + this.dmg = 2; + } + else if (this.air_finished < time) + { // drown! + if (this.pain_finished < time) + { + Damage (this, NULL, NULL, autocvar_g_balance_contents_playerdamage_drowning * autocvar_g_balance_contents_damagerate, DEATH_DROWN.m_id, this.origin, '0 0 0'); + this.pain_finished = time + 0.5; + } + } +} + +.bool move_qcphysics; + +void Player_Physics(entity this) +{ + set_movetype(this, ((this.move_qcphysics) ? MOVETYPE_NONE : this.move_movetype)); + + if(!this.move_qcphysics) + return; + + int mt = this.move_movetype; + + if(mt == MOVETYPE_PUSH || mt == MOVETYPE_FAKEPUSH || mt == MOVETYPE_PHYSICS) + { + this.move_qcphysics = false; + set_movetype(this, mt); + return; + } + + if(!frametime && !this.pm_frametime) + return; + + Movetype_Physics_NoMatchTicrate(this, this.pm_frametime, true); + + this.pm_frametime = 0; +} + +/* +============= +PlayerPostThink + +Called every frame for each client after the physics are run +============= +*/ +.float idlekick_lasttimeleft; +void PlayerPostThink (entity this) +{ + Player_Physics(this); + + if (sv_maxidle > 0) + if (frametime) // WORKAROUND: only use dropclient in server frames (frametime set). Never use it in cl_movement frames (frametime zero). + if (IS_REAL_CLIENT(this)) + if (IS_PLAYER(this) || sv_maxidle_spectatorsareidle) + { + int totalClients = 0; + if(sv_maxidle_slots > 0) + { + FOREACH_CLIENT(IS_REAL_CLIENT(it) || sv_maxidle_slots_countbots, + { + ++totalClients; + }); + } + + if (sv_maxidle_slots > 0 && (maxclients - totalClients) > sv_maxidle_slots) + { /* do nothing */ } + else if (time - this.parm_idlesince < 1) // instead of (time == this.parm_idlesince) to support sv_maxidle <= 10 + { + if (this.idlekick_lasttimeleft) + { + this.idlekick_lasttimeleft = 0; + Kill_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CPID_IDLING); + } + } + else + { + float timeleft = ceil(sv_maxidle - (time - this.parm_idlesince)); + if (timeleft == min(10, sv_maxidle - 1)) { // - 1 to support sv_maxidle <= 10 + if (!this.idlekick_lasttimeleft) + Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_DISCONNECT_IDLING, timeleft); + } + if (timeleft <= 0) { + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_QUIT_KICK_IDLING, this.netname); + dropclient(this); + return; + } + else if (timeleft <= 10) { + if (timeleft != this.idlekick_lasttimeleft) { + Send_Notification(NOTIF_ONE, this, MSG_ANNCE, Announcer_PickNumber(CNT_IDLE, timeleft)); + } + this.idlekick_lasttimeleft = timeleft; + } + } + } + + CheatFrame(this); + + //CheckPlayerJump(); + + if (IS_PLAYER(this)) { + DrownPlayer(this); + CheckRules_Player(this); + UpdateChatBubble(this); + if (this.impulse) ImpulseCommands(this); + if (intermission_running) return; // intermission or finale + GetPressedKeys(this); + } + + if (this.waypointsprite_attachedforcarrier) { + vector v = healtharmor_maxdamage(this.health, this.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id); + WaypointSprite_UpdateHealth(this.waypointsprite_attachedforcarrier, '1 0 0' * v); + } + + playerdemo_write(this); + + CSQCMODEL_AUTOUPDATE(this); +} diff --git a/qcsrc/server/client.qh b/qcsrc/server/client.qh new file mode 100644 index 0000000000..35ff6e961c --- /dev/null +++ b/qcsrc/server/client.qh @@ -0,0 +1,109 @@ +#pragma once + +void ClientState_attach(entity this); + +CLASS(Client, Object) + /** Client name */ + ATTRIB(Client, netname, string, this.netname); + ATTRIB(Client, colormap, int, this.colormap); + ATTRIB(Client, team, int, this.team); + ATTRIB(Client, clientcolors, int, this.clientcolors); + /** Client IP */ + ATTRIB(Client, netaddress, string, this.netaddress); + ATTRIB(Client, playermodel, string, this.playermodel); + ATTRIB(Client, playerskin, int, this.playerskin); + + /** fingerprint of CA key the player used to authenticate */ + ATTRIB(Client, crypto_keyfp, string, this.crypto_keyfp); + /** fingerprint of CA key the server used to authenticate to the player */ + ATTRIB(Client, crypto_mykeyfp, string, this.crypto_mykeyfp); + /** fingerprint of ID used by the player entity, or string_null if not identified */ + ATTRIB(Client, crypto_idfp, string, this.crypto_idfp); + /** set if the player's ID has been signed */ + ATTRIB(Client, crypto_idfp_signed, bool, this.crypto_idfp_signed); + /** the string "AES128" if encrypting, and string_null if plaintext */ + ATTRIB(Client, crypto_encryptmethod, string, this.crypto_encryptmethod); + /** the string "HMAC-SHA256" if signing, and string_null if plaintext */ + ATTRIB(Client, crypto_signmethod, string, this.crypto_signmethod); + + // custom + + ATTRIB(Client, playerid, int, this.playerid); + + METHOD(Client, m_unwind, bool(Client this)); + + STATIC_METHOD(Client, Add, void(Client this, int _team)); + STATIC_METHOD(Client, Remove, void(Client this)); + + INIT(Client) { + if (this.m_unwind(this)) return this; + make_impure(this); + this.classname = "player_joining"; + static int playerid_last; + this.playerid = ++playerid_last; + ClientState_attach(this); + } + DESTRUCTOR(Client) { + Client_Remove(this); + } + CONSTRUCTOR(Client, string name) { + CONSTRUCT(Client); + this.netname = name; + this.netaddress = "local"; + this.playermodel = "models/player/megaerebus.iqm"; + } +ENDCLASS(Client) + +CLASS(Observer, Client) + INIT(Observer) { + this.classname = STR_OBSERVER; + } + DESTRUCTOR(Observer) { } +ENDCLASS(Observer) + +CLASS(Spectator, Client) + INIT(Spectator) { + this.classname = STR_SPECTATOR; + } + DESTRUCTOR(Spectator) { } +ENDCLASS(Spectator) + +CLASS(Player, Client) + INIT(Player) { + this.classname = STR_PLAYER; + } + DESTRUCTOR(Player) { } +ENDCLASS(Player) + +METHOD(Client, m_unwind, bool(Client this)) +{ + TC(Client, this); + #define UNWIND(class) MACRO_BEGIN if (this.instanceOf##class) { METHOD_REFERENCE(class, dtorimpl)(this); } MACRO_END + switch (this.classname) { + case "Observer": + UNWIND(Spectator); + UNWIND(Player); + return true; + case "Spectator": + UNWIND(Observer); + UNWIND(Player); + return true; + case "Player": + UNWIND(Observer); + UNWIND(Spectator); + return true; + } + #undef UNWIND + return false; +} + +float c1, c2, c3, c4; + +void play_countdown(entity this, float finished, Sound samp); + +float CalcRotRegen(float current, float regenstable, float regenfactor, float regenlinear, float regenframetime, float rotstable, float rotfactor, float rotlinear, float rotframetime, float limit); + +bool Spectate(entity this, entity pl); + +#define SPECTATE_COPY() [[accumulate]] void SpectateCopy(entity this, entity spectatee) +#define SPECTATE_COPYFIELD(fld) SPECTATE_COPY() { this.(fld) = spectatee.(fld); } diff --git a/qcsrc/server/command/_mod.qh b/qcsrc/server/command/_mod.qh index b95b1e0a6e..6a3b175a4b 100644 --- a/qcsrc/server/command/_mod.qh +++ b/qcsrc/server/command/_mod.qh @@ -1,6 +1,9 @@ // generated file; do not modify #include #include +#ifdef SVQC + #include +#endif #include #include #include diff --git a/qcsrc/server/command/banning.qc b/qcsrc/server/command/banning.qc index d450ca4c85..6a9b2b3614 100644 --- a/qcsrc/server/command/banning.qc +++ b/qcsrc/server/command/banning.qc @@ -4,7 +4,7 @@ #include "common.qh" -#include "../cl_player.qh" +#include "../player.qh" #include "../ipban.qh" #include diff --git a/qcsrc/server/command/cmd.qc b/qcsrc/server/command/cmd.qc index 19ee99a1ae..48ac3bff56 100644 --- a/qcsrc/server/command/cmd.qc +++ b/qcsrc/server/command/cmd.qc @@ -6,7 +6,7 @@ #include "../campaign.qh" #include "../cheats.qh" -#include "../cl_player.qh" +#include "../player.qh" #include "../ipban.qh" #include "../mapvoting.qh" #include "../scores.qh" diff --git a/qcsrc/server/command/sv_cmd.qc b/qcsrc/server/command/sv_cmd.qc index 18a424a1da..f0efcba467 100644 --- a/qcsrc/server/command/sv_cmd.qc +++ b/qcsrc/server/command/sv_cmd.qc @@ -9,8 +9,8 @@ #include "../anticheat.qh" #include "../campaign.qh" -#include "../cl_client.qh" -#include "../cl_player.qh" +#include "../client.qh" +#include "../player.qh" #include "../g_world.qh" #include "../ipban.qh" #include "../playerdemo.qh" diff --git a/qcsrc/server/g_hook.qc b/qcsrc/server/g_hook.qc index 1b2662d6e4..1684573281 100644 --- a/qcsrc/server/g_hook.qc +++ b/qcsrc/server/g_hook.qc @@ -5,7 +5,7 @@ #include "weapons/weaponsystem.qh" #include "weapons/selection.qh" #include "weapons/tracing.qh" -#include "cl_player.qh" +#include "player.qh" #include "command/common.qh" #include "round_handler.qh" #include "../common/state.qh" diff --git a/qcsrc/server/g_world.qc b/qcsrc/server/g_world.qc index 8f10ea9dbb..a04d6d440b 100644 --- a/qcsrc/server/g_world.qc +++ b/qcsrc/server/g_world.qc @@ -5,7 +5,7 @@ #include "bot/api.qh" #include "campaign.qh" #include "cheats.qh" -#include "cl_client.qh" +#include "client.qh" #include "command/common.qh" #include "command/getreplies.qh" #include "command/sv_cmd.qh" diff --git a/qcsrc/server/impulse.qc b/qcsrc/server/impulse.qc new file mode 100644 index 0000000000..c797c57710 --- /dev/null +++ b/qcsrc/server/impulse.qc @@ -0,0 +1,606 @@ +#include "impulse.qh" +#include "round_handler.qh" + +#include "bot/api.qh" + +#include "weapons/throwing.qh" +#include "command/common.qh" +#include "cheats.qh" +#include "weapons/selection.qh" +#include "weapons/tracing.qh" +#include "weapons/weaponsystem.qh" + +#include + +#include "../common/minigames/sv_minigames.qh" + +#include "../common/weapons/all.qh" +#include "../common/vehicles/sv_vehicles.qh" + +#include "../common/mutators/mutator/waypoints/waypointsprites.qh" + +.entity vehicle; + +#define IMPULSE(id) _IMPULSE(IMP_##id) +#define _IMPULSE(id) \ + void id##_handle(entity this); \ + STATIC_INIT_LATE(id) \ + { \ + id.impulse_handle = id##_handle; \ + } \ + void id##_handle(entity this) + +/** + * Impulse map: + * + * 0 reserved (no input) + * + * 99: loaded + * + * 140: moving clone + * 141: ctf speedrun + * 142: fixed clone + * 143: emergency teleport + * 148: unfairly eliminate + * + * TODO: + * 200 to 209: prev weapon shortcuts + * 210 to 219: best weapon shortcuts + * 220 to 229: next weapon shortcuts + * 230 to 253: individual weapons (up to 24) + */ + +// weapon switching impulses + +#define X(slot) \ + IMPULSE(weapon_group_##slot) \ + { \ + if (IS_DEAD(this)) \ + { \ + this.impulse = IMP_weapon_group_##slot.impulse; \ + return; \ + } \ + W_NextWeaponOnImpulse(this, slot); \ + } +X(1) +X(2) +X(3) +X(4) +X(5) +X(6) +X(7) +X(8) +X(9) +X(0) +#undef X + +// custom order weapon cycling + +#define X(slot, dir) \ + IMPULSE(weapon_priority_##slot##_##dir) \ + { \ + if (this.vehicle) return; \ + if (IS_DEAD(this)) \ + { \ + this.impulse = IMP_weapon_priority_##slot##_##dir.impulse; \ + return; \ + } \ + noref int prev = -1; \ + noref int best = 0; \ + noref int next = +1; \ + W_CycleWeapon(this, this.cvar_cl_weaponpriorities[slot], dir); \ + } +X(0, prev) +X(1, prev) +X(2, prev) +X(3, prev) +X(4, prev) +X(5, prev) +X(6, prev) +X(7, prev) +X(8, prev) +X(9, prev) + +X(0, best) +X(1, best) +X(2, best) +X(3, best) +X(4, best) +X(5, best) +X(6, best) +X(7, best) +X(8, best) +X(9, best) + +X(0, next) +X(1, next) +X(2, next) +X(3, next) +X(4, next) +X(5, next) +X(6, next) +X(7, next) +X(8, next) +X(9, next) +#undef X + +// direct weapons + +#define X(i) \ + IMPULSE(weapon_byid_##i) \ + { \ + if (this.vehicle) return; \ + if (IS_DEAD(this)) \ + { \ + this.impulse = IMP_weapon_byid_##i.impulse; \ + return; \ + } \ + W_SwitchWeapon(this, Weapons_from(WEP_FIRST + i)); \ + } +X(0) +X(1) +X(2) +X(3) +X(4) +X(5) +X(6) +X(7) +X(8) +X(9) +X(10) +X(11) +X(12) +X(13) +X(14) +X(15) +X(16) +X(17) +X(18) +X(19) +X(20) +X(21) +X(22) +X(23) +#undef X + +IMPULSE(weapon_next_byid) +{ + if (this.vehicle) return; + if (IS_DEAD(this)) + { + this.impulse = IMP_weapon_next_byid.impulse; + return; + } + W_NextWeapon(this, 0); +} + +IMPULSE(weapon_prev_byid) +{ + if (this.vehicle) return; + if (IS_DEAD(this)) + { + this.impulse = IMP_weapon_prev_byid.impulse; + return; + } + W_PreviousWeapon(this, 0); +} + +IMPULSE(weapon_next_bygroup) +{ + if (this.vehicle) return; + if (IS_DEAD(this)) + { + this.impulse = IMP_weapon_next_bygroup.impulse; + return; + } + W_NextWeapon(this, 1); +} + +IMPULSE(weapon_prev_bygroup) +{ + if (this.vehicle) return; + if (IS_DEAD(this)) + { + this.impulse = IMP_weapon_prev_bygroup.impulse; + return; + } + W_PreviousWeapon(this, 1); +} + +IMPULSE(weapon_next_bypriority) +{ + if (this.vehicle) return; + if (IS_DEAD(this)) + { + this.impulse = IMP_weapon_next_bypriority.impulse; + return; + } + W_NextWeapon(this, 2); +} + +IMPULSE(weapon_prev_bypriority) +{ + if (this.vehicle) return; + if (IS_DEAD(this)) + { + this.impulse = IMP_weapon_prev_bypriority.impulse; + return; + } + W_PreviousWeapon(this, 2); +} + +IMPULSE(weapon_last) +{ + if (this.vehicle) return; + if (IS_DEAD(this)) return; + W_LastWeapon(this); +} + +IMPULSE(weapon_best) +{ + if (this.vehicle) return; + if (IS_DEAD(this)) return; + W_SwitchWeapon(this, w_getbestweapon(this)); +} + +IMPULSE(weapon_drop) +{ + if (this.vehicle) return; + if (IS_DEAD(this)) return; + W_ThrowWeapon(this, weaponentities[0], W_CalculateProjectileVelocity(this, this.velocity, v_forward * 750, false), '0 0 0', true); +} + +IMPULSE(weapon_reload) +{ + if (this.vehicle) return; + if (IS_DEAD(this)) return; + if (forbidWeaponUse(this)) return; + Weapon w = PS(this).m_weapon; + entity actor = this; + for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) + { + .entity weaponentity = weaponentities[slot]; + w.wr_reload(w, actor, weaponentity); + } +} + +void ImpulseCommands(entity this) +{ + if (gameover) return; + + int imp = this.impulse; + if (!imp) return; + this.impulse = 0; + + if (MinigameImpulse(this, imp)) return; + + if (timeout_status == TIMEOUT_ACTIVE) return; // don't allow any impulses while the game is paused + + // allow only weapon change impulses when not in round time + if (round_handler_IsActive() && !round_handler_IsRoundStarted()) + { + #define X(id) case IMP_##id.impulse: + switch (imp) + { + X(weapon_group_0) + X(weapon_group_1) + X(weapon_group_2) + X(weapon_group_3) + X(weapon_group_4) + X(weapon_group_5) + X(weapon_group_6) + X(weapon_group_7) + X(weapon_group_8) + X(weapon_group_9) + X(weapon_next_byid) + X(weapon_prev_byid) + X(weapon_next_bygroup) + X(weapon_prev_bygroup) + X(weapon_next_bypriority) + X(weapon_prev_bypriority) + X(weapon_last) + X(weapon_best) + X(weapon_reload) + X(weapon_priority_0_prev) + X(weapon_priority_1_prev) + X(weapon_priority_2_prev) + X(weapon_priority_3_prev) + X(weapon_priority_4_prev) + X(weapon_priority_5_prev) + X(weapon_priority_6_prev) + X(weapon_priority_7_prev) + X(weapon_priority_8_prev) + X(weapon_priority_9_prev) + X(weapon_priority_0_next) + X(weapon_priority_1_next) + X(weapon_priority_2_next) + X(weapon_priority_3_next) + X(weapon_priority_4_next) + X(weapon_priority_5_next) + X(weapon_priority_6_next) + X(weapon_priority_7_next) + X(weapon_priority_8_next) + X(weapon_priority_9_next) + X(weapon_priority_0_best) + X(weapon_priority_1_best) + X(weapon_priority_2_best) + X(weapon_priority_3_best) + X(weapon_priority_4_best) + X(weapon_priority_5_best) + X(weapon_priority_6_best) + X(weapon_priority_7_best) + X(weapon_priority_8_best) + X(weapon_priority_9_best) + X(weapon_byid_0) + X(weapon_byid_1) + X(weapon_byid_2) + X(weapon_byid_3) + X(weapon_byid_4) + X(weapon_byid_5) + X(weapon_byid_6) + X(weapon_byid_7) + X(weapon_byid_8) + X(weapon_byid_9) + X(weapon_byid_10) + X(weapon_byid_11) + X(weapon_byid_12) + X(weapon_byid_13) + X(weapon_byid_14) + X(weapon_byid_15) + X(weapon_byid_16) + X(weapon_byid_17) + X(weapon_byid_18) + X(weapon_byid_19) + X(weapon_byid_20) + X(weapon_byid_21) + X(weapon_byid_22) + X(weapon_byid_23) + break; + default: return; + } +#undef X + } + + if (vehicle_impulse(this, imp)) return; + + if (CheatImpulse(this, imp)) return; + + FOREACH(IMPULSES, it.impulse == imp, { + void(entity) f = it.impulse_handle; + if (!f) continue; + f(this); + return; + }); +} + +IMPULSE(use) +{ + PlayerUseKey(this); +} + +IMPULSE(waypoint_personal_here) +{ + entity wp = WaypointSprite_DeployPersonal(WP_Waypoint, this, this.origin, RADARICON_WAYPOINT); + if (wp) WaypointSprite_Ping(wp); + sprint(this, "personal waypoint spawned at location\n"); +} + +IMPULSE(waypoint_personal_crosshair) +{ + WarpZone_crosshair_trace(this); + entity wp = WaypointSprite_DeployPersonal(WP_Waypoint, this, trace_endpos, RADARICON_WAYPOINT); + if (wp) WaypointSprite_Ping(wp); + sprint(this, "personal waypoint spawned at crosshair\n"); +} + +IMPULSE(waypoint_personal_death) +{ + if (!this.death_origin) return; + entity wp = WaypointSprite_DeployPersonal(WP_Waypoint, this, this.death_origin, RADARICON_WAYPOINT); + if (wp) WaypointSprite_Ping(wp); + sprint(this, "personal waypoint spawned at death location\n"); +} + +IMPULSE(waypoint_here_follow) +{ + if (!teamplay) return; + if (IS_DEAD(this)) return; + if (!MUTATOR_CALLHOOK(HelpMePing, this)) + { + entity wp = WaypointSprite_Attach(WP_Helpme, this, true, RADARICON_HELPME); + if (!wp) WaypointSprite_HelpMePing(this.waypointsprite_attachedforcarrier); + else WaypointSprite_Ping(wp); + } + sprint(this, "HELP ME attached\n"); +} + +IMPULSE(waypoint_here_here) +{ + entity wp = WaypointSprite_DeployFixed(WP_Here, false, this, this.origin, RADARICON_HERE); + if (wp) WaypointSprite_Ping(wp); + sprint(this, "HERE spawned at location\n"); +} + +IMPULSE(waypoint_here_crosshair) +{ + WarpZone_crosshair_trace(this); + entity wp = WaypointSprite_DeployFixed(WP_Here, false, this, trace_endpos, RADARICON_HERE); + if (wp) WaypointSprite_Ping(wp); + sprint(this, "HERE spawned at crosshair\n"); +} + +IMPULSE(waypoint_here_death) +{ + if (!this.death_origin) return; + entity wp = WaypointSprite_DeployFixed(WP_Here, false, this, this.death_origin, RADARICON_HERE); + if (wp) WaypointSprite_Ping(wp); + sprint(this, "HERE spawned at death location\n"); +} + +IMPULSE(waypoint_danger_here) +{ + entity wp = WaypointSprite_DeployFixed(WP_Danger, false, this, this.origin, RADARICON_DANGER); + if (wp) WaypointSprite_Ping(wp); + sprint(this, "DANGER spawned at location\n"); +} + +IMPULSE(waypoint_danger_crosshair) +{ + WarpZone_crosshair_trace(this); + entity wp = WaypointSprite_DeployFixed(WP_Danger, false, this, trace_endpos, RADARICON_DANGER); + if (wp) WaypointSprite_Ping(wp); + sprint(this, "DANGER spawned at crosshair\n"); +} + +IMPULSE(waypoint_danger_death) +{ + if (!this.death_origin) return; + entity wp = WaypointSprite_DeployFixed(WP_Danger, false, this, this.death_origin, RADARICON_DANGER); + if (wp) WaypointSprite_Ping(wp); + sprint(this, "DANGER spawned at death location\n"); +} + +IMPULSE(waypoint_clear_personal) +{ + WaypointSprite_ClearPersonal(this); + if (this.personal) + { + delete(this.personal); + this.personal = NULL; + } + sprint(this, "personal waypoint cleared\n"); +} + +IMPULSE(waypoint_clear) +{ + WaypointSprite_ClearOwned(this); + if (this.personal) + { + delete(this.personal); + this.personal = NULL; + } + sprint(this, "all waypoints cleared\n"); +} + +IMPULSE(navwaypoint_spawn) +{ + if (!autocvar_g_waypointeditor) return; + waypoint_schedulerelink(waypoint_spawn(this.origin, this.origin, 0)); + bprint(strcat("Waypoint spawned at ", vtos(this.origin), "\n")); +} + +IMPULSE(navwaypoint_remove) +{ + if (!autocvar_g_waypointeditor) return; + entity e = navigation_findnearestwaypoint(this, false); + if (!e) return; + if (e.wpflags & WAYPOINTFLAG_GENERATED) return; + bprint(strcat("Waypoint removed at ", vtos(e.origin), "\n")); + waypoint_remove(e); +} + +IMPULSE(navwaypoint_relink) +{ + if (!autocvar_g_waypointeditor) return; + waypoint_schedulerelinkall(); +} + +IMPULSE(navwaypoint_save) +{ + if (!autocvar_g_waypointeditor) return; + waypoint_saveall(); +} + +IMPULSE(navwaypoint_unreachable) +{ + if (!autocvar_g_waypointeditor) return; + IL_EACH(g_waypoints, true, + { + it.colormod = '0.5 0.5 0.5'; + it.effects &= ~(EF_NODEPTHTEST | EF_RED | EF_BLUE); + }); + entity e2 = navigation_findnearestwaypoint(this, false); + navigation_markroutes(this, e2); + + int j, m; + + j = 0; + m = 0; + IL_EACH(g_waypoints, it.wpcost >= 10000000, + { + LOG_INFO("unreachable: ", etos(it), " ", vtos(it.origin), "\n"); + it.colormod_z = 8; + it.effects |= EF_NODEPTHTEST | EF_BLUE; + ++j; + ++m; + }); + if (j) LOG_INFOF("%d waypoints cannot be reached from here in any way (marked with blue light)\n", j); + navigation_markroutes_inverted(e2); + + j = 0; + IL_EACH(g_waypoints, it.wpcost >= 10000000, + { + LOG_INFO("cannot reach me: ", etos(it), " ", vtos(it.origin), "\n"); + it.colormod_x = 8; + if (!(it.effects & EF_NODEPTHTEST)) // not already reported before + ++m; + it.effects |= EF_NODEPTHTEST | EF_RED; + ++j; + }); + if (j) LOG_INFOF("%d waypoints cannot walk to here in any way (marked with red light)\n", j); + if (m) LOG_INFOF("%d waypoints have been marked total\n", m); + + j = 0; + FOREACH_ENTITY_CLASS("info_player_deathmatch", true, + { + vector org = it.origin; + tracebox(it.origin, STAT(PL_MIN, NULL), STAT(PL_MAX, NULL), it.origin - '0 0 512', MOVE_NOMONSTERS, NULL); + setorigin(it, trace_endpos); + if (navigation_findnearestwaypoint(it, false)) + { + setorigin(it, org); + it.effects &= ~EF_NODEPTHTEST; + it.model = ""; + } + else + { + setorigin(it, org); + LOG_INFO("spawn without waypoint: ", etos(it), " ", vtos(it.origin), "\n"); + it.effects |= EF_NODEPTHTEST; + _setmodel(it, this.model); + it.frame = this.frame; + it.skin = this.skin; + it.colormod = '8 0.5 8'; + setsize(it, '0 0 0', '0 0 0'); + ++j; + } + }); + if (j) LOG_INFOF("%d spawnpoints have no nearest waypoint (marked by player model)\n", j); + + j = 0; + FOREACH_ENTITY_FLAGS(flags, FL_ITEM, + { + it.effects &= ~(EF_NODEPTHTEST | EF_RED | EF_BLUE); + it.colormod = '0.5 0.5 0.5'; + }); + FOREACH_ENTITY_FLAGS(flags, FL_ITEM, + { + if (navigation_findnearestwaypoint(it, false)) continue; + LOG_INFO("item without waypoint: ", etos(it), " ", vtos(it.origin), "\n"); + it.effects |= EF_NODEPTHTEST | EF_RED; + it.colormod_x = 8; + ++j; + }); + if (j) LOG_INFOF("%d items have no nearest waypoint and cannot be walked away from (marked with red light)\n", j); + + j = 0; + FOREACH_ENTITY_FLAGS(flags, FL_ITEM, + { + if (navigation_findnearestwaypoint(it, true)) continue; + LOG_INFO("item without waypoint: ", etos(it), " ", vtos(it.origin), "\n"); + it.effects |= EF_NODEPTHTEST | EF_BLUE; + it.colormod_z = 8; + ++j; + }); + if (j) LOG_INFOF("%d items have no nearest waypoint and cannot be walked to (marked with blue light)\n", j); +} diff --git a/qcsrc/server/impulse.qh b/qcsrc/server/impulse.qh new file mode 100644 index 0000000000..50edc2c9c5 --- /dev/null +++ b/qcsrc/server/impulse.qh @@ -0,0 +1,3 @@ +#pragma once + +void ImpulseCommands(entity this); diff --git a/qcsrc/server/matrix.qc b/qcsrc/server/matrix.qc index b7d26decf5..4d235da694 100644 --- a/qcsrc/server/matrix.qc +++ b/qcsrc/server/matrix.qc @@ -1,6 +1,6 @@ #include "matrix.qh" -#include "cl_player.qh" +#include "player.qh" var void MX_Handle(int buf, string ancestor) { diff --git a/qcsrc/server/mutators/events.qh b/qcsrc/server/mutators/events.qh index 0d53e054f4..89021e3237 100644 --- a/qcsrc/server/mutators/events.qh +++ b/qcsrc/server/mutators/events.qh @@ -350,7 +350,7 @@ MUTATOR_HOOKABLE(W_DecreaseAmmo, EV_W_DecreaseAmmo); /**/ MUTATOR_HOOKABLE(W_Reload, EV_W_Reload); -/** called at the end of player_powerups() in cl_client.qc, used for manipulating the values which are set by powerup items. */ +/** called at the end of player_powerups() in client.qc, used for manipulating the values which are set by powerup items. */ #define EV_PlayerPowerups(i, o) \ /** player */ i(entity, MUTATOR_ARGV_0_entity) \ /** old items */ i(int, MUTATOR_ARGV_1_int) \ @@ -499,7 +499,7 @@ MUTATOR_HOOKABLE(BotShouldAttack, EV_BotShouldAttack); MUTATOR_HOOKABLE(PortalTeleport, EV_PortalTeleport); /** - * called whenever a player uses impulse 33 (help me) in cl_impulse.qc + * called whenever a player uses impulse 33 (help me) in impulse.qc * normally help me ping uses .waypointsprite_attachedforcarrier, * but if your mutator uses something different then you can handle it * in a special manner using this hook diff --git a/qcsrc/server/mutators/gamemode.qh b/qcsrc/server/mutators/gamemode.qh index abd29740a5..64577fa6bf 100644 --- a/qcsrc/server/mutators/gamemode.qh +++ b/qcsrc/server/mutators/gamemode.qh @@ -71,9 +71,9 @@ #include #include -#include -#include -#include +#include +#include +#include #include #include diff --git a/qcsrc/server/mutators/mutator.qh b/qcsrc/server/mutators/mutator.qh index 5344a91bb1..d00a20b0fc 100644 --- a/qcsrc/server/mutators/mutator.qh +++ b/qcsrc/server/mutators/mutator.qh @@ -2,9 +2,9 @@ #include -#include -#include -#include +#include +#include +#include #include #include #include diff --git a/qcsrc/server/player.qc b/qcsrc/server/player.qc new file mode 100644 index 0000000000..5316a85b58 --- /dev/null +++ b/qcsrc/server/player.qc @@ -0,0 +1,962 @@ +#include "player.qh" + +#include "bot/api.qh" +#include "cheats.qh" +#include "g_damage.qh" +#include "g_subs.qh" +#include "miscfunctions.qh" +#include "portals.qh" +#include "teamplay.qh" +#include "weapons/throwing.qh" +#include "command/common.qh" +#include "../common/state.qh" +#include "../common/anim.qh" +#include "../common/animdecide.qh" +#include "../common/csqcmodel_settings.qh" +#include "../common/deathtypes/all.qh" +#include "../common/triggers/subs.qh" +#include "../common/playerstats.qh" +#include "../lib/csqcmodel/sv_model.qh" + +#include "../common/minigames/sv_minigames.qh" + +#include "../common/physics/player.qh" +#include "../common/effects/qc/all.qh" +#include "../common/mutators/mutator/waypoints/waypointsprites.qh" +#include "../common/triggers/include.qh" + +#include "weapons/weaponstats.qh" + +#include "../common/animdecide.qh" + +void Drop_Special_Items(entity player) +{ + // called when the player has become stuck or frozen + // so objective items aren't stuck with the player + + MUTATOR_CALLHOOK(DropSpecialItems, player); +} + +void CopyBody_Think(entity this) +{ + if(this.CopyBody_nextthink && time > this.CopyBody_nextthink) + { + this.CopyBody_think(this); + if(wasfreed(this)) + return; + this.CopyBody_nextthink = this.nextthink; + this.CopyBody_think = getthink(this); + setthink(this, CopyBody_Think); + } + CSQCMODEL_AUTOUPDATE(this); + this.nextthink = time; +} +void CopyBody(entity this, float keepvelocity) +{ + if (this.effects & EF_NODRAW) + return; + entity clone = new(body); + clone.enemy = this; + clone.lip = this.lip; + clone.colormap = this.colormap; + clone.iscreature = this.iscreature; + clone.teleportable = this.teleportable; + clone.damagedbycontents = this.damagedbycontents; + clone.angles = this.angles; + clone.v_angle = this.v_angle; + clone.avelocity = this.avelocity; + clone.damageforcescale = this.damageforcescale; + clone.effects = this.effects; + clone.glowmod = this.glowmod; + clone.event_damage = this.event_damage; + clone.anim_state = this.anim_state; + clone.anim_time = this.anim_time; + clone.anim_lower_action = this.anim_lower_action; + clone.anim_lower_time = this.anim_lower_time; + clone.anim_upper_action = this.anim_upper_action; + clone.anim_upper_time = this.anim_upper_time; + clone.anim_implicit_state = this.anim_implicit_state; + clone.anim_implicit_time = this.anim_implicit_time; + clone.anim_lower_implicit_action = this.anim_lower_implicit_action; + clone.anim_lower_implicit_time = this.anim_lower_implicit_time; + clone.anim_upper_implicit_action = this.anim_upper_implicit_action; + clone.anim_upper_implicit_time = this.anim_upper_implicit_time; + clone.dphitcontentsmask = this.dphitcontentsmask; + clone.death_time = this.death_time; + clone.pain_finished = this.pain_finished; + clone.health = this.health; + clone.armorvalue = this.armorvalue; + clone.armortype = this.armortype; + clone.model = this.model; + clone.modelindex = this.modelindex; + clone.skin = this.skin; + clone.species = this.species; + clone.move_qcphysics = false; // don't run gamecode logic on clones, too many + set_movetype(clone, this.move_movetype); + clone.solid = this.solid; + clone.ballistics_density = this.ballistics_density; + clone.takedamage = this.takedamage; + setcefc(clone, getcefc(this)); + clone.uncustomizeentityforclient = this.uncustomizeentityforclient; + clone.uncustomizeentityforclient_set = this.uncustomizeentityforclient_set; + if (keepvelocity == 1) + clone.velocity = this.velocity; + clone.oldvelocity = clone.velocity; + clone.alpha = this.alpha; + clone.fade_time = this.fade_time; + clone.fade_rate = this.fade_rate; + //clone.weapon = this.weapon; + setorigin(clone, this.origin); + setsize(clone, this.mins, this.maxs); + clone.prevorigin = this.origin; + clone.reset = SUB_Remove; + clone._ps = this._ps; + + Drag_MoveDrag(this, clone); + + if(clone.colormap <= maxclients && clone.colormap > 0) + clone.colormap = 1024 + this.clientcolors; + + CSQCMODEL_AUTOINIT(clone); + clone.CopyBody_nextthink = this.nextthink; + clone.CopyBody_think = getthink(this); + clone.nextthink = time; + setthink(clone, CopyBody_Think); + // "bake" the current animation frame for clones (they don't get clientside animation) + animdecide_load_if_needed(clone); + animdecide_setframes(clone, false, frame, frame1time, frame2, frame2time); +} + +void player_setupanimsformodel(entity this) +{ + // load animation info + animdecide_load_if_needed(this); + animdecide_setstate(this, 0, false); +} + +void player_anim(entity this) +{ + int deadbits = (this.anim_state & (ANIMSTATE_DEAD1 | ANIMSTATE_DEAD2)); + if(IS_DEAD(this)) { + if (!deadbits) { + // Decide on which death animation to use. + if(random() < 0.5) + deadbits = ANIMSTATE_DEAD1; + else + deadbits = ANIMSTATE_DEAD2; + } + } else { + // Clear a previous death animation. + deadbits = 0; + } + int animbits = deadbits; + if(STAT(FROZEN, this)) + animbits |= ANIMSTATE_FROZEN; + if(this.move_movetype == MOVETYPE_FOLLOW) + animbits |= ANIMSTATE_FOLLOW; + if(this.crouch) + animbits |= ANIMSTATE_DUCK; + animdecide_setstate(this, animbits, false); + animdecide_setimplicitstate(this, IS_ONGROUND(this)); +} + +void PlayerCorpseDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force) +{ + float take, save; + vector v; + Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, this, attacker); + + // damage resistance (ignore most of the damage from a bullet or similar) + damage = max(damage - 5, 1); + + v = healtharmor_applydamage(this.armorvalue, autocvar_g_balance_armor_blockpercent, deathtype, damage); + take = v.x; + save = v.y; + + if(sound_allowed(MSG_BROADCAST, attacker)) + { + if (save > 10) + sound (this, CH_SHOTS, SND_ARMORIMPACT, VOL_BASE, ATTEN_NORM); + else if (take > 30) + sound (this, CH_SHOTS, SND_BODYIMPACT2, VOL_BASE, ATTEN_NORM); + else if (take > 10) + sound (this, CH_SHOTS, SND_BODYIMPACT1, VOL_BASE, ATTEN_NORM); + } + + if (take > 50) + Violence_GibSplash_At(hitloc, force * -0.1, 3, 1, this, attacker); + if (take > 100) + Violence_GibSplash_At(hitloc, force * -0.2, 3, 1, this, attacker); + + this.armorvalue = this.armorvalue - save; + this.health = this.health - take; + // pause regeneration for 5 seconds + this.pauseregen_finished = max(this.pauseregen_finished, time + autocvar_g_balance_pause_health_regen); + + this.dmg_save = this.dmg_save + save;//max(save - 10, 0); + this.dmg_take = this.dmg_take + take;//max(take - 10, 0); + this.dmg_inflictor = inflictor; + + if (this.health <= -autocvar_sv_gibhealth && this.alpha >= 0) + { + // don't use any animations as a gib + this.frame = 0; + // view just above the floor + this.view_ofs = '0 0 4'; + + Violence_GibSplash(this, 1, 1, attacker); + this.alpha = -1; + this.solid = SOLID_NOT; // restore later + this.takedamage = DAMAGE_NO; // restore later + this.damagedbycontents = false; + } +} + +void calculate_player_respawn_time(entity this) +{ + if(g_ca) + return; + + float gametype_setting_tmp; + float sdelay_max = GAMETYPE_DEFAULTED_SETTING(respawn_delay_max); + float sdelay_small = GAMETYPE_DEFAULTED_SETTING(respawn_delay_small); + float sdelay_large = GAMETYPE_DEFAULTED_SETTING(respawn_delay_large); + float sdelay_small_count = GAMETYPE_DEFAULTED_SETTING(respawn_delay_small_count); + float sdelay_large_count = GAMETYPE_DEFAULTED_SETTING(respawn_delay_large_count); + float waves = GAMETYPE_DEFAULTED_SETTING(respawn_waves); + + float pcount = 1; // Include myself whether or not team is already set right and I'm a "player". + if (teamplay) + { + FOREACH_CLIENT(IS_PLAYER(it) && it != this, LAMBDA( + if(it.team == this.team) + ++pcount; + )); + if (sdelay_small_count == 0) + sdelay_small_count = 1; + if (sdelay_large_count == 0) + sdelay_large_count = 1; + } + else + { + FOREACH_CLIENT(IS_PLAYER(it) && it != this, LAMBDA( + ++pcount; + )); + if (sdelay_small_count == 0) + { + if (g_cts) + { + // Players play independently. No point in requiring enemies. + sdelay_small_count = 1; + } + else + { + // Players play AGAINST each other. Enemies required. + sdelay_small_count = 2; + } + } + if (sdelay_large_count == 0) + { + if (g_cts) + { + // Players play independently. No point in requiring enemies. + sdelay_large_count = 1; + } + else + { + // Players play AGAINST each other. Enemies required. + sdelay_large_count = 2; + } + } + } + + float sdelay; + + if (pcount <= sdelay_small_count) + sdelay = sdelay_small; + else if (pcount >= sdelay_large_count) + sdelay = sdelay_large; + else // NOTE: this case implies sdelay_large_count > sdelay_small_count. + sdelay = sdelay_small + (sdelay_large - sdelay_small) * (pcount - sdelay_small_count) / (sdelay_large_count - sdelay_small_count); + + if(waves) + this.respawn_time = ceil((time + sdelay) / waves) * waves; + else + this.respawn_time = time + sdelay; + + if(sdelay < sdelay_max) + this.respawn_time_max = time + sdelay_max; + else + this.respawn_time_max = this.respawn_time; + + if((sdelay + waves >= 5.0) && (this.respawn_time - time > 1.75)) + this.respawn_countdown = 10; // first number to count down from is 10 + else + this.respawn_countdown = -1; // do not count down + + if(autocvar_g_forced_respawn) + this.respawn_flags = this.respawn_flags | RESPAWN_FORCE; +} + +void PlayerDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force) +{ + float take, save, dh, da; + vector v; + float valid_damage_for_weaponstats; + float excess; + + dh = max(this.health, 0); + da = max(this.armorvalue, 0); + + if(!DEATH_ISSPECIAL(deathtype)) + { + damage *= sqrt(bound(1.0, this.cvar_cl_handicap, 100.0)); + if(this != attacker) + damage /= sqrt(bound(1.0, attacker.cvar_cl_handicap, 100.0)); + } + + if(DEATH_ISWEAPON(deathtype, WEP_TUBA)) + { + // tuba causes blood to come out of the ears + vector ear1, ear2; + vector d; + float f; + ear1 = this.origin; + ear1_z += 0.125 * this.view_ofs.z + 0.875 * this.maxs.z; // 7/8 + ear2 = ear1; + makevectors(this.angles); + ear1 += v_right * -10; + ear2 += v_right * +10; + d = inflictor.origin - this.origin; + if (d) + f = (d * v_right) / vlen(d); // this is cos of angle of d and v_right! + else + f = 0; // Assum ecenter. + force = v_right * vlen(force); + Violence_GibSplash_At(ear1, force * -1, 2, bound(0, damage, 25) / 2 * (0.5 - 0.5 * f), this, attacker); + Violence_GibSplash_At(ear2, force, 2, bound(0, damage, 25) / 2 * (0.5 + 0.5 * f), this, attacker); + if(f > 0) + { + hitloc = ear1; + force = force * -1; + } + else + { + hitloc = ear2; + // force is already good + } + } + else + Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, this, attacker); + + + v = healtharmor_applydamage(this.armorvalue, autocvar_g_balance_armor_blockpercent, deathtype, damage); + take = v.x; + save = v.y; + + if(attacker == this) + { + // don't reset pushltime for this damage as it may be an attempt to + // escape a lava pit or similar + //this.pushltime = 0; + this.istypefrag = 0; + } + else if(IS_PLAYER(attacker)) + { + this.pusher = attacker; + this.pushltime = time + autocvar_g_maxpushtime; + this.istypefrag = PHYS_INPUT_BUTTON_CHAT(this); + } + else if(time < this.pushltime) + { + attacker = this.pusher; + this.pushltime = max(this.pushltime, time + 0.6); + } + else + { + this.pushltime = 0; + this.istypefrag = 0; + } + + if (time < this.spawnshieldtime && autocvar_g_spawnshield_blockdamage < 1) + { + vector v = healtharmor_applydamage(this.armorvalue, max(0, autocvar_g_spawnshield_blockdamage), deathtype, damage); + take = v.x; + save = v.y; + } + + MUTATOR_CALLHOOK(PlayerDamage_SplitHealthArmor, inflictor, attacker, this, force, take, save, deathtype, damage); + take = bound(0, M_ARGV(4, float), this.health); + save = bound(0, M_ARGV(5, float), this.armorvalue); + excess = max(0, damage - take - save); + + if(sound_allowed(MSG_BROADCAST, attacker)) + { + if (save > 10) + sound (this, CH_SHOTS, SND_ARMORIMPACT, VOL_BASE, ATTEN_NORM); + else if (take > 30) + sound (this, CH_SHOTS, SND_BODYIMPACT2, VOL_BASE, ATTEN_NORM); + else if (take > 10) + sound (this, CH_SHOTS, SND_BODYIMPACT1, VOL_BASE, ATTEN_NORM); // FIXME possibly remove them? + } + + if (take > 50) + Violence_GibSplash_At(hitloc, force * -0.1, 3, 1, this, attacker); + if (take > 100) + Violence_GibSplash_At(hitloc, force * -0.2, 3, 1, this, attacker); + + if (time >= this.spawnshieldtime || autocvar_g_spawnshield_blockdamage < 1) + { + if (!(this.flags & FL_GODMODE)) + { + this.armorvalue = this.armorvalue - save; + this.health = this.health - take; + // pause regeneration for 5 seconds + if(take) + this.pauseregen_finished = max(this.pauseregen_finished, time + autocvar_g_balance_pause_health_regen); + + if (time > this.pain_finished) //Don't switch pain sequences like crazy + { + this.pain_finished = time + 0.5; //Supajoe + + if(autocvar_sv_gentle < 1) { + if(this.classname != "body") // pain anim is BORKED on our ZYMs, FIXME remove this once we have good models + { + if (!this.animstate_override) + { + if (random() > 0.5) + animdecide_setaction(this, ANIMACTION_PAIN1, true); + else + animdecide_setaction(this, ANIMACTION_PAIN2, true); + } + } + + if(sound_allowed(MSG_BROADCAST, attacker)) + if((this.health < 2 * WEP_CVAR_PRI(blaster, damage) * autocvar_g_balance_selfdamagepercent + 1) || !(DEATH_WEAPONOF(deathtype).spawnflags & WEP_FLAG_CANCLIMB) || attacker != this) // WEAPONTODO: create separate limit for pain notification with laser + if(this.health > 1) + // exclude pain sounds for laserjumps as long as you aren't REALLY low on health and would die of the next two + { + if(deathtype == DEATH_FALL.m_id) + PlayerSound(this, playersound_fall, CH_PAIN, VOL_BASE, VOICETYPE_PLAYERSOUND); + else if(this.health > 75) + PlayerSound(this, playersound_pain100, CH_PAIN, VOL_BASE, VOICETYPE_PLAYERSOUND); + else if(this.health > 50) + PlayerSound(this, playersound_pain75, CH_PAIN, VOL_BASE, VOICETYPE_PLAYERSOUND); + else if(this.health > 25) + PlayerSound(this, playersound_pain50, CH_PAIN, VOL_BASE, VOICETYPE_PLAYERSOUND); + else + PlayerSound(this, playersound_pain25, CH_PAIN, VOL_BASE, VOICETYPE_PLAYERSOUND); + } + } + } + + // throw off bot aim temporarily + float shake; + if(IS_BOT_CLIENT(this) && this.health >= 1) + { + shake = damage * 5 / (bound(0,skill,100) + 1); + this.v_angle_x = this.v_angle.x + (random() * 2 - 1) * shake; + this.v_angle_y = this.v_angle.y + (random() * 2 - 1) * shake; + this.v_angle_x = bound(-90, this.v_angle.x, 90); + } + } + else + this.max_armorvalue += (save + take); + } + this.dmg_save = this.dmg_save + save;//max(save - 10, 0); + this.dmg_take = this.dmg_take + take;//max(take - 10, 0); + this.dmg_inflictor = inflictor; + + if (this != attacker) { + float realdmg = damage - excess; + if (IS_PLAYER(attacker)) { + PlayerScore_Add(attacker, SP_DMG, realdmg); + } + if (IS_PLAYER(this)) { + PlayerScore_Add(this, SP_DMGTAKEN, realdmg); + } + } + + bool abot = (IS_BOT_CLIENT(attacker)); + bool vbot = (IS_BOT_CLIENT(this)); + + valid_damage_for_weaponstats = 0; + Weapon awep = WEP_Null; + + if(vbot || IS_REAL_CLIENT(this)) + if(abot || IS_REAL_CLIENT(attacker)) + if(attacker && this != attacker) + if(DIFF_TEAM(this, attacker)) + { + if(DEATH_ISSPECIAL(deathtype)) + awep = PS(attacker).m_weapon; + else + awep = DEATH_WEAPONOF(deathtype); + valid_damage_for_weaponstats = 1; + } + + dh = dh - max(this.health, 0); + da = da - max(this.armorvalue, 0); + if(valid_damage_for_weaponstats) + { + WeaponStats_LogDamage(awep.m_id, abot, PS(this).m_weapon.m_id, vbot, dh + da); + } + if (dh + da) + { + MUTATOR_CALLHOOK(PlayerDamaged, attacker, this, dh, da, hitloc, deathtype); + } + + if (this.health < 1) + { + float defer_ClientKill_Now_TeamChange; + defer_ClientKill_Now_TeamChange = false; + + if(this.alivetime) + { + PS_GR_P_ADDVAL(this, PLAYERSTATS_ALIVETIME, time - this.alivetime); + this.alivetime = 0; + } + + if(valid_damage_for_weaponstats) + WeaponStats_LogKill(awep.m_id, abot, PS(this).m_weapon.m_id, vbot); + + if(autocvar_sv_gentle < 1) + if(sound_allowed(MSG_BROADCAST, attacker)) + { + if(deathtype == DEATH_DROWN.m_id) + PlayerSound(this, playersound_drown, CH_PAIN, VOL_BASE, VOICETYPE_PLAYERSOUND); + else + PlayerSound(this, playersound_death, CH_PAIN, VOL_BASE, VOICETYPE_PLAYERSOUND); + } + + // get rid of kill indicator + if(this.killindicator) + { + delete(this.killindicator); + this.killindicator = NULL; + if(this.killindicator_teamchange) + defer_ClientKill_Now_TeamChange = true; + + if(this.classname == "body") + if(deathtype == DEATH_KILL.m_id) + { + // for the lemmings fans, a small harmless explosion + Send_Effect(EFFECT_ROCKET_EXPLODE, this.origin, '0 0 0', 1); + } + } + + // print an obituary message + if(this.classname != "body") + Obituary (attacker, inflictor, this, deathtype); + + // increment frag counter for used weapon type + Weapon w = DEATH_WEAPONOF(deathtype); + if(w != WEP_Null) + if(accuracy_isgooddamage(attacker, this)) + attacker.accuracy.(accuracy_frags[w.m_id-1]) += 1; + + MUTATOR_CALLHOOK(PlayerDies, inflictor, attacker, this, deathtype, damage); + excess = M_ARGV(4, float); + + Weapon wep = PS(this).m_weapon; + /*for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) + { + .entity weaponentity = weaponentities[slot]; + wep.wr_playerdeath(wep, this, weaponentity); + }*/ + .entity weaponentity = weaponentities[0]; // TODO: unhardcode + wep.wr_playerdeath(wep, this, weaponentity); + + RemoveGrapplingHook(this); + + Portal_ClearAllLater(this); + + this.fixangle = true; + + if(defer_ClientKill_Now_TeamChange) + ClientKill_Now_TeamChange(this); // can turn player into spectator + + // player could have been miraculously resuscitated ;) + // e.g. players in freezetag get frozen, they don't really die + if(this.health >= 1 || !(IS_PLAYER(this) || this.classname == "body")) + return; + + // when we get here, player actually dies + + Unfreeze(this); // remove any icy remains + this.health = 0; // Unfreeze resets health, so we need to set it back + + // clear waypoints + WaypointSprite_PlayerDead(this); + // throw a weapon + SpawnThrownWeapon(this, this.origin + (this.mins + this.maxs) * 0.5, PS(this).m_switchweapon.m_id); + + // become fully visible + this.alpha = default_player_alpha; + // make the corpse upright (not tilted) + this.angles_x = 0; + this.angles_z = 0; + // don't spin + this.avelocity = '0 0 0'; + // view from the floor + this.view_ofs = '0 0 -8'; + // toss the corpse + set_movetype(this, MOVETYPE_TOSS); + // shootable corpse + this.solid = SOLID_CORPSE; + this.ballistics_density = autocvar_g_ballistics_density_corpse; + // don't stick to the floor + UNSET_ONGROUND(this); + // dying animation + this.deadflag = DEAD_DYING; + + // when to allow respawn + calculate_player_respawn_time(this); + + this.death_time = time; + if (random() < 0.5) + animdecide_setstate(this, this.anim_state | ANIMSTATE_DEAD1, true); + else + animdecide_setstate(this, this.anim_state | ANIMSTATE_DEAD2, true); + if (this.maxs.z > 5) + { + this.maxs_z = 5; + setsize(this, this.mins, this.maxs); + } + // set damage function to corpse damage + this.event_damage = PlayerCorpseDamage; + // call the corpse damage function just in case it wants to gib + this.event_damage(this, inflictor, attacker, excess, deathtype, hitloc, force); + + // set up to fade out later + SUB_SetFade (this, time + 6 + random (), 1); + // reset body think wrapper broken by SUB_SetFade + if(this.classname == "body" && getthink(this) != CopyBody_Think) { + this.CopyBody_think = getthink(this); + this.CopyBody_nextthink = this.nextthink; + setthink(this, CopyBody_Think); + this.nextthink = time; + } + + if(autocvar_sv_gentle > 0 || autocvar_ekg || this.classname == "body") { + // remove corpse + // clones don't run any animation code any more, so we must gib them when they die :( + PlayerCorpseDamage(this, inflictor, attacker, autocvar_sv_gibhealth+1.0, deathtype, hitloc, force); + } + + // reset fields the weapons may use just in case + FOREACH(Weapons, it != WEP_Null, LAMBDA( + it.wr_resetplayer(it, this); + for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) + { + ATTACK_FINISHED_FOR(this, it.m_id, slot) = 0; + } + )); + } +} + +void MoveToTeam(entity client, int team_colour, int type) +{ + int lockteams_backup = lockteams; // backup any team lock + lockteams = 0; // disable locked teams + TeamchangeFrags(client); // move the players frags + SetPlayerColors(client, team_colour - 1); // set the players colour + Damage(client, client, client, 100000, DEATH_AUTOTEAMCHANGE.m_id, client.origin, '0 0 0'); // kill the player + lockteams = lockteams_backup; // restore the team lock + LogTeamchange(client.playerid, client.team, type); +} + +/** print(), but only print if the server is not local */ +void dedicated_print(string input) +{ + if (server_is_dedicated) print(input); +} + +/** + * message "": do not say, just test flood control + * return value: + * 1 = accept + * 0 = reject + * -1 = fake accept + */ +int Say(entity source, int teamsay, entity privatesay, string msgin, bool floodcontrol) +{ + if (!teamsay && !privatesay) if (substring(msgin, 0, 1) == " ") + msgin = substring(msgin, 1, -1); // work around DP say bug (say_team does not have this!) + + msgin = formatmessage(source, msgin); + + string colorstr; + if (!IS_PLAYER(source)) + colorstr = "^0"; // black for spectators + else if(teamplay) + colorstr = Team_ColorCode(source.team); + else + { + colorstr = ""; + teamsay = false; + } + + if(intermission_running) + teamsay = false; + + if (!source) { + colorstr = ""; + teamsay = false; + } + + if(msgin != "") + msgin = trigger_magicear_processmessage_forallears(source, teamsay, privatesay, msgin); + + /* + * using bprint solves this... me stupid + // how can we prevent the message from appearing in a listen server? + // for now, just give "say" back and only handle say_team + if(!teamsay) + { + clientcommand(source, strcat("say ", msgin)); + return; + } + */ + + string namestr = ""; + if (source) + namestr = autocvar_g_chat_teamcolors ? playername(source) : source.netname; + + string colorprefix = (strdecolorize(namestr) == namestr) ? "^3" : "^7"; + + string msgstr, cmsgstr; + string privatemsgprefix = string_null; + int privatemsgprefixlen = 0; + if (msgin == "") { + msgstr = cmsgstr = ""; + } else { + if(privatesay) + { + msgstr = strcat("\{1}\{13}* ", colorprefix, namestr, "^3 tells you: ^7"); + privatemsgprefixlen = strlen(msgstr); + msgstr = strcat(msgstr, msgin); + cmsgstr = strcat(colorstr, colorprefix, namestr, "^3 tells you:\n^7", msgin); + if(autocvar_g_chat_teamcolors) + privatemsgprefix = strcat("\{1}\{13}* ^3You tell ", playername(privatesay), ": ^7"); + else + privatemsgprefix = strcat("\{1}\{13}* ^3You tell ", privatesay.netname, ": ^7"); + } + else if(teamsay) + { + if(strstrofs(msgin, "/me", 0) >= 0) + { + //msgin = strreplace("/me", "", msgin); + //msgin = substring(msgin, 3, strlen(msgin)); + msgin = strreplace("/me", strcat(colorstr, "(", colorprefix, namestr, colorstr, ")^7"), msgin); + msgstr = strcat("\{1}\{13}^4* ", "^7", msgin); + } + else + msgstr = strcat("\{1}\{13}", colorstr, "(", colorprefix, namestr, colorstr, ") ^7", msgin); + cmsgstr = strcat(colorstr, "(", colorprefix, namestr, colorstr, ")\n^7", msgin); + } + else + { + if(strstrofs(msgin, "/me", 0) >= 0) + { + //msgin = strreplace("/me", "", msgin); + //msgin = substring(msgin, 3, strlen(msgin)); + msgin = strreplace("/me", strcat(colorprefix, namestr), msgin); + msgstr = strcat("\{1}^4* ", "^7", msgin); + } + else { + msgstr = "\{1}"; + msgstr = strcat(msgstr, (namestr != "") ? strcat(colorprefix, namestr, "^7: ") : "^7"); + msgstr = strcat(msgstr, msgin); + } + cmsgstr = ""; + } + msgstr = strcat(strreplace("\n", " ", msgstr), "\n"); // newlines only are good for centerprint + } + + string fullmsgstr = msgstr; + string fullcmsgstr = cmsgstr; + + // FLOOD CONTROL + int flood = 0; + var .float flood_field = floodcontrol_chat; + if(floodcontrol) + { + float flood_spl; + float flood_burst; + float flood_lmax; + float lines; + if(privatesay) + { + flood_spl = autocvar_g_chat_flood_spl_tell; + flood_burst = autocvar_g_chat_flood_burst_tell; + flood_lmax = autocvar_g_chat_flood_lmax_tell; + flood_field = floodcontrol_chattell; + } + else if(teamsay) + { + flood_spl = autocvar_g_chat_flood_spl_team; + flood_burst = autocvar_g_chat_flood_burst_team; + flood_lmax = autocvar_g_chat_flood_lmax_team; + flood_field = floodcontrol_chatteam; + } + else + { + flood_spl = autocvar_g_chat_flood_spl; + flood_burst = autocvar_g_chat_flood_burst; + flood_lmax = autocvar_g_chat_flood_lmax; + flood_field = floodcontrol_chat; + } + flood_burst = max(0, flood_burst - 1); + // to match explanation in default.cfg, a value of 3 must allow three-line bursts and not four! + + // do flood control for the default line size + if(msgstr != "") + { + getWrappedLine_remaining = msgstr; + msgstr = ""; + lines = 0; + while(getWrappedLine_remaining && (!flood_lmax || lines <= flood_lmax)) + { + msgstr = strcat(msgstr, " ", getWrappedLineLen(82.4289758859709, strlennocol)); // perl averagewidth.pl < gfx/vera-sans.width + ++lines; + } + msgstr = substring(msgstr, 1, strlen(msgstr) - 1); + + if(getWrappedLine_remaining != "") + { + msgstr = strcat(msgstr, "\n"); + flood = 2; + } + + if (time >= source.(flood_field)) + { + source.(flood_field) = max(time - flood_burst * flood_spl, source.(flood_field)) + lines * flood_spl; + } + else + { + flood = 1; + msgstr = fullmsgstr; + } + } + else + { + if (time >= source.(flood_field)) + source.(flood_field) = max(time - flood_burst * flood_spl, source.(flood_field)) + flood_spl; + else + flood = 1; + } + + if (timeout_status == TIMEOUT_ACTIVE) // when game is paused, no flood protection + source.(flood_field) = flood = 0; + } + + string sourcemsgstr, sourcecmsgstr; + if(flood == 2) // cannot happen for empty msgstr + { + if(autocvar_g_chat_flood_notify_flooder) + { + sourcemsgstr = strcat(msgstr, "\n^3FLOOD CONTROL: ^7message too long, trimmed\n"); + sourcecmsgstr = ""; + } + else + { + sourcemsgstr = fullmsgstr; + sourcecmsgstr = fullcmsgstr; + } + cmsgstr = ""; + } + else + { + sourcemsgstr = msgstr; + sourcecmsgstr = cmsgstr; + } + + if (!privatesay && source && !IS_PLAYER(source)) + { + if (!intermission_running) + if(teamsay || (autocvar_g_chat_nospectators == 1) || (autocvar_g_chat_nospectators == 2 && !(warmup_stage || gameover))) + teamsay = -1; // spectators + } + + if(flood) + LOG_INFO("NOTE: ", playername(source), "^7 is flooding.\n"); + + // build sourcemsgstr by cutting off a prefix and replacing it by the other one + if(privatesay) + sourcemsgstr = strcat(privatemsgprefix, substring(sourcemsgstr, privatemsgprefixlen, -1)); + + int ret; + if(source.muted) + { + // always fake the message + ret = -1; + } + else if(flood == 1) + { + if (autocvar_g_chat_flood_notify_flooder) + { + sprint(source, strcat("^3FLOOD CONTROL: ^7wait ^1", ftos(source.(flood_field) - time), "^3 seconds\n")); + ret = 0; + } + else + ret = -1; + } + else + { + ret = 1; + } + + if(sourcemsgstr != "" && ret != 0) + { + if(ret < 0) // faked message, because the player is muted + { + sprint(source, sourcemsgstr); + if(sourcecmsgstr != "" && !privatesay) + centerprint(source, sourcecmsgstr); + } + else if(privatesay) // private message, between 2 people only + { + sprint(source, sourcemsgstr); + sprint(privatesay, msgstr); + if (!autocvar_g_chat_tellprivacy) { dedicated_print(msgstr); } // send to server console too if "tellprivacy" is disabled + if(cmsgstr != "") + centerprint(privatesay, cmsgstr); + } + else if ( teamsay && source.active_minigame ) + { + sprint(source, sourcemsgstr); + dedicated_print(msgstr); // send to server console too + FOREACH_CLIENT(IS_REAL_CLIENT(it) && it != source && it.active_minigame == source.active_minigame, sprint(it, msgstr)); + } + else if(teamsay > 0) // team message, only sent to team mates + { + sprint(source, sourcemsgstr); + dedicated_print(msgstr); // send to server console too + if(sourcecmsgstr != "") + centerprint(source, sourcecmsgstr); + FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it) && it != source && it.team == source.team, { + sprint(it, msgstr); + if(cmsgstr != "") + centerprint(it, cmsgstr); + }); + } + else if(teamsay < 0) // spectator message, only sent to spectators + { + sprint(source, sourcemsgstr); + dedicated_print(msgstr); // send to server console too + FOREACH_CLIENT(!IS_PLAYER(it) && IS_REAL_CLIENT(it) && it != source, sprint(it, msgstr)); + } + else + { + if (source) { + sprint(source, sourcemsgstr); + dedicated_print(msgstr); // send to server console too + MX_Say(strcat(playername(source), "^7: ", msgin)); + } + FOREACH_CLIENT(IS_REAL_CLIENT(it) && it != source, sprint(it, msgstr)); + } + } + + return ret; +} diff --git a/qcsrc/server/player.qh b/qcsrc/server/player.qh new file mode 100644 index 0000000000..b5f8ca07c8 --- /dev/null +++ b/qcsrc/server/player.qh @@ -0,0 +1,39 @@ +#pragma once + +.entity pusher; +.float pushltime; +.float istypefrag; + +.float CopyBody_nextthink; +.void(entity this) CopyBody_think; +void CopyBody_Think(entity this); +void CopyBody(entity this, float keepvelocity); + +void player_setupanimsformodel(entity this); + +void player_anim(entity this); + +void PlayerCorpseDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force); + +// g__str: +// If 0, default is used. +// If <0, 0 is used. +// Otherwise, g_str (default value) is used. +// For consistency, negative values there are mapped to zero too. +#define GAMETYPE_DEFAULTED_SETTING(str) \ + ((gametype_setting_tmp = cvar(strcat("g_", GetGametype(), "_" #str))), \ + (gametype_setting_tmp < 0) ? 0 \ + : (gametype_setting_tmp == 0 || autocvar_g_respawn_delay_forced) ? max(0, autocvar_g_##str) \ + : gametype_setting_tmp) + +void calculate_player_respawn_time(entity this); + +void ClientKill_Now_TeamChange(entity this); + +void MoveToTeam(entity client, float team_colour, float type); + +void PlayerDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force); + +/** to be used by `prvm_edictset server playernumber muted 1` */ +.float muted; +int Say(entity source, float teamsay, entity privatesay, string msgin, float floodcontrol); diff --git a/qcsrc/server/race.qc b/qcsrc/server/race.qc index b315d6cad4..fc5ba4d61b 100644 --- a/qcsrc/server/race.qc +++ b/qcsrc/server/race.qc @@ -1,6 +1,6 @@ #include "race.qh" -#include "cl_client.qh" +#include "client.qh" #include "portals.qh" #include "scores.qh" #include "spawnpoints.qh" diff --git a/qcsrc/server/scores_rules.qc b/qcsrc/server/scores_rules.qc index d3aceac50b..718ebd2658 100644 --- a/qcsrc/server/scores_rules.qc +++ b/qcsrc/server/scores_rules.qc @@ -1,6 +1,6 @@ #include "scores_rules.qh" -#include "cl_client.qh" +#include "client.qh" #include "scores.qh" int ScoreRules_teams; diff --git a/qcsrc/server/teamplay.qc b/qcsrc/server/teamplay.qc index 1d742375d1..974495bb66 100644 --- a/qcsrc/server/teamplay.qc +++ b/qcsrc/server/teamplay.qc @@ -1,6 +1,6 @@ #include "teamplay.qh" -#include "cl_client.qh" +#include "client.qh" #include "race.qh" #include "scores.qh" #include "scores_rules.qh" @@ -12,7 +12,7 @@ #include "mutators/_all.qh" #include "../common/deathtypes/all.qh" -#include "../common/gamemodes/all.qh" +#include "../common/gamemodes/_all.qh" #include "../common/teams.qh" void TeamchangeFrags(entity e) @@ -540,7 +540,7 @@ int JoinBestTeam(entity this, bool only_return_best, bool forcebestteam) SetPlayerColors(this, selectedteam - 1); // when JoinBestTeam is called by client.qc/ClientKill_Now_TeamChange the players team is -1 and thus skipped - // when JoinBestTeam is called by cl_client.qc/ClientConnect the player_id is 0 the log attempt is rejected + // when JoinBestTeam is called by client.qc/ClientConnect the player_id is 0 the log attempt is rejected LogTeamchange(this.playerid, this.team, 99); } return selectedteam; diff --git a/qcsrc/server/tests.qh b/qcsrc/server/tests.qh index ec56918c79..865d3f70b7 100644 --- a/qcsrc/server/tests.qh +++ b/qcsrc/server/tests.qh @@ -1,7 +1,7 @@ #pragma once #include "autocvars.qh" -#include "cl_client.qh" +#include "client.qh" #include "command/_all.qh" #include "weapons/common.qh" #include "weapons/selection.qh" diff --git a/qcsrc/tools/genmod.sh b/qcsrc/tools/genmod.sh index 2c34e67134..5998184bff 100755 --- a/qcsrc/tools/genmod.sh +++ b/qcsrc/tools/genmod.sh @@ -12,15 +12,19 @@ function genmod() { echo '// generated file; do not modify' > ${MOD}.inc echo '// generated file; do not modify' > ${MOD}.qh for f in $(ls | sort -k 1,1 -t .); do - if [[ "$f" == cl_* ]]; then if [[ -f "${f#cl_}" ]]; then continue; fi; fi - if [[ "$f" == sv_* ]]; then if [[ -f "${f#sv_}" ]]; then continue; fi; fi - if [[ "$f" == ui_* ]]; then if [[ -f "${f#ui_}" ]]; then continue; fi; fi + if [[ "$f" == cl_* ]]; then f="${f#cl_}"; if [[ -f "$f" ]]; then continue; fi + elif [[ "$f" == sv_* ]]; then f="${f#sv_}"; if [[ -f "$f" ]]; then continue; fi + elif [[ "$f" == ui_* ]]; then f="${f#ui_}"; if [[ -f "$f" ]]; then continue; fi + fi if [[ "$f" == *.qc ]]; then - echo "#include <${CTX}$f>" >> ${MOD}.inc - echo "#include <${CTX}${f%.qc}.qh>" >> ${MOD}.qh + if [[ -f "$f" ]]; then echo -e "#include <${CTX}$f>" >> ${MOD}.inc; fi + if [[ -f "${f%.qc}.qh" ]]; then echo -e "#include <${CTX}${f%.qc}.qh>" >> ${MOD}.qh; fi if [[ -f "cl_$f" ]]; then echo -e "#ifdef CSQC\n #include <${CTX}cl_$f>\n#endif" >> ${MOD}.inc; fi + if [[ -f "cl_${f%.qc}.qh" ]]; then echo -e "#ifdef CSQC\n #include <${CTX}cl_${f%.qc}.qh>\n#endif" >> ${MOD}.qh; fi if [[ -f "sv_$f" ]]; then echo -e "#ifdef SVQC\n #include <${CTX}sv_$f>\n#endif" >> ${MOD}.inc; fi + if [[ -f "sv_${f%.qc}.qh" ]]; then echo -e "#ifdef SVQC\n #include <${CTX}sv_${f%.qc}.qh>\n#endif" >> ${MOD}.qh; fi if [[ -f "ui_$f" ]]; then echo -e "#ifdef MENUQC\n #include <${CTX}ui_$f>\n#endif" >> ${MOD}.inc; fi + if [[ -f "ui_${f%.qc}.qh" ]]; then echo -e "#ifdef MENUQC\n #include <${CTX}ui_${f%.qc}.qh>\n#endif" >> ${MOD}.qh; fi fi done # echo >> ${MOD}