// generated file; do not modify
-#include <client/commands/cl_cmd.qc>
+#ifdef CSQC
+ #include <client/commands/cl_cmd.qc>
+#endif
// generated file; do not modify
-#include <client/commands/cl_cmd.qh>
+#ifdef CSQC
+ #include <client/commands/cl_cmd.qh>
+#endif
#include <common/constants.qh>
#include <common/debug.qh>
#include <common/mapinfo.qh>
-#include <common/gamemodes/all.qh>
+#include <common/gamemodes/_all.qh>
#include <common/physics/player.qh>
#include <common/stats.qh>
#include <common/triggers/target/music.qh>
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
#include <common/animdecide.qh>
#ifdef SVQC
- #include <server/cl_player.qh>
+ #include <server/player.qh>
#endif
REGISTER_NET_TEMP(globalsound)
#include "_all.qh"
#include "_mod.inc"
+
+#include "gamemode/_all.inc"
#pragma once
#include "_mod.qh"
+
+#include "gamemode/_all.qh"
// generated file; do not modify
-#include <common/gamemodes/all.qc>
// generated file; do not modify
-#include <common/gamemodes/all.qh>
+++ /dev/null
-#include "gamemode/nexball/module.inc"
-#include "gamemode/onslaught/module.inc"
+++ /dev/null
-#include "all.qh"
-
-#define IMPLEMENTATION
-#include "all.inc"
-#undef IMPLEMENTATION
+++ /dev/null
-#ifndef GAMEMODES_ALL_H
-#define GAMEMODES_ALL_H
-
-#include "all.inc"
-
-#endif
--- /dev/null
+#include "_all.qh"
+#include "_mod.inc"
+
+#include "nexball/_mod.inc"
+#include "onslaught/_mod.inc"
--- /dev/null
+#pragma once
+#include "_mod.qh"
+
+#include "nexball/_mod.qh"
+#include "onslaught/_mod.qh"
+++ /dev/null
-#include "nexball.qc"
-#include "weapon.qc"
#include "nexball.qh"
-#ifdef IMPLEMENTATION
#ifdef CSQC
int autocvar_cl_eventchase_nexball = 1;
}
#endif
-#endif
-#ifndef GAMEMODE_NEXBALL_H
-#define GAMEMODE_NEXBALL_H
+#pragma once
#ifdef SVQC
//EF_BRIGHTFIELD|EF_BRIGHTLIGHT|EF_DIMLIGHT|EF_BLUE|EF_RED|EF_FLAME
.float teamtime;
#endif
-#endif
-#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"
--- /dev/null
+#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));
// generated file; do not modify
-#include <common/gamemodes/gamemode/onslaught/cl_controlpoint.qc>
-#include <common/gamemodes/gamemode/onslaught/cl_generator.qc>
+#include <common/gamemodes/gamemode/onslaught/controlpoint.qc>
+#ifdef CSQC
+ #include <common/gamemodes/gamemode/onslaught/cl_controlpoint.qc>
+#endif
+#ifdef SVQC
+ #include <common/gamemodes/gamemode/onslaught/sv_controlpoint.qc>
+#endif
+#include <common/gamemodes/gamemode/onslaught/generator.qc>
+#ifdef CSQC
+ #include <common/gamemodes/gamemode/onslaught/cl_generator.qc>
+#endif
+#ifdef SVQC
+ #include <common/gamemodes/gamemode/onslaught/sv_generator.qc>
+#endif
#include <common/gamemodes/gamemode/onslaught/onslaught.qc>
-#include <common/gamemodes/gamemode/onslaught/sv_controlpoint.qc>
-#include <common/gamemodes/gamemode/onslaught/sv_generator.qc>
+#ifdef SVQC
+ #include <common/gamemodes/gamemode/onslaught/sv_onslaught.qc>
+#endif
// generated file; do not modify
-#include <common/gamemodes/gamemode/onslaught/cl_controlpoint.qh>
-#include <common/gamemodes/gamemode/onslaught/cl_generator.qh>
+#include <common/gamemodes/gamemode/onslaught/controlpoint.qh>
+#ifdef CSQC
+ #include <common/gamemodes/gamemode/onslaught/cl_controlpoint.qh>
+#endif
+#ifdef SVQC
+ #include <common/gamemodes/gamemode/onslaught/sv_controlpoint.qh>
+#endif
+#include <common/gamemodes/gamemode/onslaught/generator.qh>
+#ifdef CSQC
+ #include <common/gamemodes/gamemode/onslaught/cl_generator.qh>
+#endif
+#ifdef SVQC
+ #include <common/gamemodes/gamemode/onslaught/sv_generator.qh>
+#endif
#include <common/gamemodes/gamemode/onslaught/onslaught.qh>
-#include <common/gamemodes/gamemode/onslaught/sv_controlpoint.qh>
-#include <common/gamemodes/gamemode/onslaught/sv_generator.qh>
+#ifdef SVQC
+ #include <common/gamemodes/gamemode/onslaught/sv_onslaught.qh>
+#endif
-#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
-#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
--- /dev/null
+#include "controlpoint.qh"
--- /dev/null
+#pragma once
--- /dev/null
+#include "generator.qh"
--- /dev/null
+#pragma once
+++ /dev/null
-#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
-#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<bestvalue)
- {
- bestvalue = cp1.wpcost;
- cp = cp1;
- this.havocbot_ons_target = cp1;
- }
- }
-
- if (!cp)
- return;
-
- LOG_DEBUG(this.netname, " chose cp ranked ", ftos(bestvalue));
-
- if(cp.goalentity)
- {
- // Should be attacked
- // Rate waypoints near it
- found = false;
- best = NULL;
- bestvalue = 99999999999;
- for(radius=0; radius<1000 && !found; radius+=500)
- {
- for(wp=findradius(cp.origin,radius); wp; wp=wp.chain)
- {
- if(!(wp.wpflags & WAYPOINTFLAG_GENERATED))
- if(wp.classname=="waypoint")
- if(checkpvs(wp.origin,cp))
- {
- found = true;
- if(wp.cnt<bestvalue)
- {
- best = wp;
- bestvalue = wp.cnt;
- }
- }
- }
- }
-
- if(best)
- {
- navigation_routerating(this, best, ratingscale, 10000);
- best.cnt += 1;
-
- this.havocbot_attack_time = 0;
- if(checkpvs(this.view_ofs,cp))
- if(checkpvs(this.view_ofs,best))
- this.havocbot_attack_time = time + 2;
- }
- else
- {
- navigation_routerating(this, cp, ratingscale, 10000);
- }
- LOG_DEBUG(this.netname, " found an attackable controlpoint at ", vtos(cp.origin));
- }
- else
- {
- // Should be touched
- LOG_DEBUG(this.netname, " found a touchable controlpoint at ", vtos(cp.origin));
- found = false;
-
- // Look for auto generated waypoint
- if (!bot_waypoints_for_items)
- for (wp = findradius(cp.origin,100); wp; wp = wp.chain)
- {
- if(wp.classname=="waypoint")
- {
- navigation_routerating(this, wp, ratingscale, 10000);
- found = true;
- }
- }
-
- // Nothing found, rate the controlpoint itself
- if (!found)
- navigation_routerating(this, cp, ratingscale, 10000);
- }
-}
-
-bool havocbot_goalrating_ons_generator_attack(entity this, float ratingscale)
-{
- entity g, wp, bestwp;
- bool found;
- int best;
-
- for(g = ons_worldgeneratorlist; g; g = g.ons_worldgeneratornext)
- {
- if(SAME_TEAM(g, this) || g.isshielded)
- continue;
-
- // Should be attacked
- // Rate waypoints near it
- found = false;
- bestwp = NULL;
- best = 99999999999;
-
- for(wp=findradius(g.origin,400); wp; wp=wp.chain)
- {
- if(wp.classname=="waypoint")
- if(checkpvs(wp.origin,g))
- {
- found = true;
- if(wp.cnt<best)
- {
- bestwp = wp;
- best = wp.cnt;
- }
- }
- }
-
- if(bestwp)
- {
- LOG_DEBUG("waypoints found around generator");
- navigation_routerating(this, bestwp, ratingscale, 10000);
- bestwp.cnt += 1;
-
- this.havocbot_attack_time = 0;
- if(checkpvs(this.view_ofs,g))
- if(checkpvs(this.view_ofs,bestwp))
- this.havocbot_attack_time = time + 5;
-
- return true;
- }
- else
- {
- LOG_DEBUG("generator found without waypoints around");
- // if there aren't waypoints near the generator go straight to it
- navigation_routerating(this, g, ratingscale, 10000);
- this.havocbot_attack_time = 0;
- return true;
- }
- }
- return false;
-}
-
-void havocbot_role_ons_offense(entity this)
-{
- if(IS_DEAD(this))
- {
- this.havocbot_attack_time = 0;
- havocbot_ons_reset_role(this);
- return;
- }
-
- // Set the role timeout if necessary
- if (!this.havocbot_role_timeout)
- this.havocbot_role_timeout = time + 120;
-
- if (time > 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"
--- /dev/null
+#pragma once
+
+REGISTER_NET_LINKED(ENT_CLIENT_GENERATOR)
+REGISTER_NET_LINKED(ENT_CLIENT_CONTROLPOINT_ICON)
-#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
-#ifndef GENERATOR_H
-#define GENERATOR_H
+#pragma once
+
const vector GENERATOR_MIN = '-52 -52 -14';
const vector GENERATOR_MAX = '52 52 75';
const int GSF_SETUP = 8;
bool generator_send(entity this, entity to, int sf);
-#endif
--- /dev/null
+#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<bestvalue)
+ {
+ bestvalue = cp1.wpcost;
+ cp = cp1;
+ this.havocbot_ons_target = cp1;
+ }
+ }
+
+ if (!cp)
+ return;
+
+ LOG_DEBUG(this.netname, " chose cp ranked ", ftos(bestvalue));
+
+ if(cp.goalentity)
+ {
+ // Should be attacked
+ // Rate waypoints near it
+ found = false;
+ best = NULL;
+ bestvalue = 99999999999;
+ for(radius=0; radius<1000 && !found; radius+=500)
+ {
+ for(wp=findradius(cp.origin,radius); wp; wp=wp.chain)
+ {
+ if(!(wp.wpflags & WAYPOINTFLAG_GENERATED))
+ if(wp.classname=="waypoint")
+ if(checkpvs(wp.origin,cp))
+ {
+ found = true;
+ if(wp.cnt<bestvalue)
+ {
+ best = wp;
+ bestvalue = wp.cnt;
+ }
+ }
+ }
+ }
+
+ if(best)
+ {
+ navigation_routerating(this, best, ratingscale, 10000);
+ best.cnt += 1;
+
+ this.havocbot_attack_time = 0;
+ if(checkpvs(this.view_ofs,cp))
+ if(checkpvs(this.view_ofs,best))
+ this.havocbot_attack_time = time + 2;
+ }
+ else
+ {
+ navigation_routerating(this, cp, ratingscale, 10000);
+ }
+ LOG_DEBUG(this.netname, " found an attackable controlpoint at ", vtos(cp.origin));
+ }
+ else
+ {
+ // Should be touched
+ LOG_DEBUG(this.netname, " found a touchable controlpoint at ", vtos(cp.origin));
+ found = false;
+
+ // Look for auto generated waypoint
+ if (!bot_waypoints_for_items)
+ for (wp = findradius(cp.origin,100); wp; wp = wp.chain)
+ {
+ if(wp.classname=="waypoint")
+ {
+ navigation_routerating(this, wp, ratingscale, 10000);
+ found = true;
+ }
+ }
+
+ // Nothing found, rate the controlpoint itself
+ if (!found)
+ navigation_routerating(this, cp, ratingscale, 10000);
+ }
+}
+
+bool havocbot_goalrating_ons_generator_attack(entity this, float ratingscale)
+{
+ entity g, wp, bestwp;
+ bool found;
+ int best;
+
+ for(g = ons_worldgeneratorlist; g; g = g.ons_worldgeneratornext)
+ {
+ if(SAME_TEAM(g, this) || g.isshielded)
+ continue;
+
+ // Should be attacked
+ // Rate waypoints near it
+ found = false;
+ bestwp = NULL;
+ best = 99999999999;
+
+ for(wp=findradius(g.origin,400); wp; wp=wp.chain)
+ {
+ if(wp.classname=="waypoint")
+ if(checkpvs(wp.origin,g))
+ {
+ found = true;
+ if(wp.cnt<best)
+ {
+ bestwp = wp;
+ best = wp.cnt;
+ }
+ }
+ }
+
+ if(bestwp)
+ {
+ LOG_DEBUG("waypoints found around generator");
+ navigation_routerating(this, bestwp, ratingscale, 10000);
+ bestwp.cnt += 1;
+
+ this.havocbot_attack_time = 0;
+ if(checkpvs(this.view_ofs,g))
+ if(checkpvs(this.view_ofs,bestwp))
+ this.havocbot_attack_time = time + 5;
+
+ return true;
+ }
+ else
+ {
+ LOG_DEBUG("generator found without waypoints around");
+ // if there aren't waypoints near the generator go straight to it
+ navigation_routerating(this, g, ratingscale, 10000);
+ this.havocbot_attack_time = 0;
+ return true;
+ }
+ }
+ return false;
+}
+
+void havocbot_role_ons_offense(entity this)
+{
+ if(IS_DEAD(this))
+ {
+ this.havocbot_attack_time = 0;
+ havocbot_ons_reset_role(this);
+ return;
+ }
+
+ // Set the role timeout if necessary
+ if (!this.havocbot_role_timeout)
+ this.havocbot_role_timeout = time + 120;
+
+ if (time > 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);
+}
--- /dev/null
+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;
--- /dev/null
+#pragma once
// generated file; do not modify
-#include <common/minigames/cl_minigames_hud.qc>
+#ifdef CSQC
+ #include <common/minigames/cl_minigames_hud.qc>
+#endif
#include <common/minigames/minigames.qc>
#ifdef CSQC
#include <common/minigames/cl_minigames.qc>
// generated file; do not modify
-#include <common/minigames/cl_minigames_hud.qh>
+#ifdef CSQC
+ #include <common/minigames/cl_minigames_hud.qh>
+#endif
#include <common/minigames/minigames.qh>
+#ifdef CSQC
+ #include <common/minigames/cl_minigames.qh>
+#endif
+#ifdef SVQC
+ #include <common/minigames/sv_minigames.qh>
+#endif
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
// generated file; do not modify
#include <common/monsters/all.qc>
#include <common/monsters/spawn.qc>
-#include <common/monsters/sv_monsters.qc>
+#ifdef SVQC
+ #include <common/monsters/sv_monsters.qc>
+#endif
// generated file; do not modify
#include <common/monsters/all.qh>
#include <common/monsters/spawn.qh>
-#include <common/monsters/sv_monsters.qh>
+#ifdef SVQC
+ #include <common/monsters/sv_monsters.qh>
+#endif
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
#include "_all.qh"
#include "_mod.inc"
+
+#include "mutator/_all.inc"
#pragma once
#include "_mod.qh"
+
+#include "mutator/_all.qh"
// generated file; do not modify
-#include <common/mutators/all.qc>
// generated file; do not modify
-#include <common/mutators/all.qh>
+++ /dev/null
-#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"
+++ /dev/null
-#include "all.qh"
-
-#define IMPLEMENTATION
-#include "all.inc"
-#undef IMPLEMENTATION
+++ /dev/null
-#ifndef MUTATORS_ALL_H
-#define MUTATORS_ALL_H
-
-#include "all.inc"
-
-#endif
--- /dev/null
+#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"
+
--- /dev/null
+#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"
+
// generated file; do not modify
-#include <common/mutators/mutator/itemstime.qc>
// generated file; do not modify
-#include <common/mutators/mutator/itemstime.qh>
// generated file; do not modify
-#include <common/mutators/mutator/bloodloss/bloodloss.qc>
+#ifdef SVQC
+ #include <common/mutators/mutator/bloodloss/sv_bloodloss.qc>
+#endif
// generated file; do not modify
-#include <common/mutators/mutator/bloodloss/bloodloss.qh>
+#ifdef SVQC
+ #include <common/mutators/mutator/bloodloss/sv_bloodloss.qh>
+#endif
+++ /dev/null
-#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
+++ /dev/null
-#ifdef SVQC
-#include "bloodloss.qc"
-#endif
--- /dev/null
+#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");
+}
--- /dev/null
+#pragma once
// generated file; do not modify
-#include <common/mutators/mutator/breakablehook/breakablehook.qc>
+#ifdef SVQC
+ #include <common/mutators/mutator/breakablehook/sv_breakablehook.qc>
+#endif
// generated file; do not modify
-#include <common/mutators/mutator/breakablehook/breakablehook.qh>
+#ifdef SVQC
+ #include <common/mutators/mutator/breakablehook/sv_breakablehook.qh>
+#endif
+++ /dev/null
-#ifdef IMPLEMENTATION
-#include <common/deathtypes/all.qh>
-#include <server/g_hook.qh>
-
-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
+++ /dev/null
-#ifdef SVQC
-#include "breakablehook.qc"
-#endif
--- /dev/null
+#include "sv_breakablehook.qh"
+
+#include <common/deathtypes/all.qh>
+#include <server/g_hook.qh>
+
+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
+ }
+ }
+}
--- /dev/null
+#pragma once
// generated file; do not modify
-#include <common/mutators/mutator/buffs/all.qc>
#include <common/mutators/mutator/buffs/buffs.qc>
+#ifdef CSQC
+ #include <common/mutators/mutator/buffs/cl_buffs.qc>
+#endif
+#ifdef SVQC
+ #include <common/mutators/mutator/buffs/sv_buffs.qc>
+#endif
// generated file; do not modify
-#include <common/mutators/mutator/buffs/all.qh>
#include <common/mutators/mutator/buffs/buffs.qh>
+#ifdef CSQC
+ #include <common/mutators/mutator/buffs/cl_buffs.qh>
+#endif
+#ifdef SVQC
+ #include <common/mutators/mutator/buffs/sv_buffs.qh>
+#endif
+++ /dev/null
-#include "all.qh"
+++ /dev/null
-#ifndef BUFFS_ALL_H
-#define BUFFS_ALL_H
-
-#include <common/teams.qh>
-#include <common/util.qh>
-
-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 <common/items/item/pickup.qh>
-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
-#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 <common/triggers/target/music.qh>
-#include <common/gamemodes/all.qh>
-
-.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
--- /dev/null
+#pragma once
+
+#include <common/teams.qh>
+#include <common/util.qh>
+
+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 <common/items/item/pickup.qh>
+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"
--- /dev/null
+#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;
+ }
+}
--- /dev/null
+#pragma once
+
+#include "buffs.qh"
+++ /dev/null
-#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
--- /dev/null
+#include "sv_buffs.qh"
+
+#include <common/triggers/target/music.qh>
+#include <common/gamemodes/_all.qh>
+
+.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);
+ }
+ }
+}
--- /dev/null
+#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;
-#ifdef IMPLEMENTATION
+#include "bugrigs.qh"
+
+#ifndef MENUQC
+
#ifdef SVQC
#include <server/antilag.qh>
#endif
}
#endif
+
#endif
--- /dev/null
+#pragma once
+++ /dev/null
-#ifndef MENUQC
-#include "bugrigs.qc"
-#endif
// generated file; do not modify
#include <common/mutators/mutator/campcheck/campcheck.qc>
+#ifdef SVQC
+ #include <common/mutators/mutator/campcheck/sv_campcheck.qc>
+#endif
// generated file; do not modify
#include <common/mutators/mutator/campcheck/campcheck.qh>
+#ifdef SVQC
+ #include <common/mutators/mutator/campcheck/sv_campcheck.qh>
+#endif
-#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"
--- /dev/null
+#pragma once
+++ /dev/null
-#ifdef SVQC
-#include "campcheck.qc"
-#endif
--- /dev/null
+#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");
+}
--- /dev/null
+#pragma once
// generated file; do not modify
-#include <common/mutators/mutator/cloaked/cloaked.qc>
+#ifdef SVQC
+ #include <common/mutators/mutator/cloaked/sv_cloaked.qc>
+#endif
// generated file; do not modify
-#include <common/mutators/mutator/cloaked/cloaked.qh>
+#ifdef SVQC
+ #include <common/mutators/mutator/cloaked/sv_cloaked.qh>
+#endif
+++ /dev/null
-#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
+++ /dev/null
-#ifdef SVQC
-#include "cloaked.qc"
-#endif
--- /dev/null
+#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");
+}
--- /dev/null
+#pragma once
-#ifndef MUTATOR_DAMAGETEXT_H
-#define MUTATOR_DAMAGETEXT_H
+#include "damagetext.qh"
-#ifdef MENUQC
-#include <menu/xonotic/tab.qh>
-#endif
-
-#endif
-
-#ifdef IMPLEMENTATION
REGISTER_MUTATOR(damagetext, true);
#if defined(CSQC) || defined(MENUQC)
#endif
#ifdef MENUQC
+
+#include <menu/gamesettings.qh>
+
CLASS(XonoticDamageTextSettings, XonoticTab)
- #include <menu/gamesettings.qh>
REGISTER_SETTINGS(damagetext, NEW(XonoticDamageTextSettings));
ATTRIB(XonoticDamageTextSettings, title, string, _("Damage text"));
ATTRIB(XonoticDamageTextSettings, intendedWidth, float, 0.9);
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);
}
ENDCLASS(XonoticDamageTextSettings)
#endif
-#endif
--- /dev/null
+#pragma once
+
+#ifdef MENUQC
+#include <menu/xonotic/tab.qh>
+#endif
+++ /dev/null
-#include "damagetext.qc"
// generated file; do not modify
-#include <common/mutators/mutator/dodging/dodging.qc>
+#ifdef SVQC
+ #include <common/mutators/mutator/dodging/sv_dodging.qc>
+#endif
// generated file; do not modify
-#include <common/mutators/mutator/dodging/dodging.qh>
+#ifdef SVQC
+ #include <common/mutators/mutator/dodging/sv_dodging.qh>
+#endif
+++ /dev/null
-#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 <common/animdecide.qh>
-#include <common/physics/player.qh>
-
-.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
+++ /dev/null
-#ifdef SVQC
- #include "dodging.qc"
-#endif
--- /dev/null
+#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 <common/animdecide.qh>
+#include <common/physics/player.qh>
+
+.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
--- /dev/null
+#pragma once
-#ifdef IMPLEMENTATION
+#include "doublejump.qh"
+
+#ifndef MENUQC
#ifdef SVQC
#include <server/antilag.qh>
#endif
}
}
}
-
#endif
--- /dev/null
+#pragma once
+++ /dev/null
-#ifndef MENUQC
-#include "doublejump.qc"
-#endif
// generated file; do not modify
-#include <common/mutators/mutator/globalforces/globalforces.qc>
+#ifdef SVQC
+ #include <common/mutators/mutator/globalforces/sv_globalforces.qc>
+#endif
// generated file; do not modify
-#include <common/mutators/mutator/globalforces/globalforces.qh>
+#ifdef SVQC
+ #include <common/mutators/mutator/globalforces/sv_globalforces.qh>
+#endif
+++ /dev/null
-#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
+++ /dev/null
-#ifdef SVQC
- #include "globalforces.qc"
-#endif
--- /dev/null
+#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);
+ });
+}
--- /dev/null
+#pragma once
// generated file; do not modify
-#include <common/mutators/mutator/hook/hook.qc>
+#ifdef SVQC
+ #include <common/mutators/mutator/hook/sv_hook.qc>
+#endif
// generated file; do not modify
-#include <common/mutators/mutator/hook/hook.qh>
+#ifdef SVQC
+ #include <common/mutators/mutator/hook/sv_hook.qh>
+#endif
+++ /dev/null
-#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
+++ /dev/null
-#ifdef SVQC
-#include "hook.qc"
-#endif
--- /dev/null
+#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
--- /dev/null
+#pragma once
// generated file; do not modify
-#include <common/mutators/mutator/instagib/instagib.qc>
#include <common/mutators/mutator/instagib/items.qc>
+#ifdef SVQC
+ #include <common/mutators/mutator/instagib/sv_instagib.qc>
+#endif
// generated file; do not modify
-#include <common/mutators/mutator/instagib/instagib.qh>
#include <common/mutators/mutator/instagib/items.qh>
+#ifdef SVQC
+ #include <common/mutators/mutator/instagib/sv_instagib.qh>
+#endif
+++ /dev/null
-#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 <server/cl_client.qh>
-
-#include <common/items/all.qc>
-
-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
-#pragma once
-
-#include <common/items/all.qh>
-#include <common/items/item/ammo.qh>
-#include <common/items/item/powerup.qh>
-
-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"
--- /dev/null
+#pragma once
+
+#include <common/items/all.qh>
+#include <common/items/item/ammo.qh>
+#include <common/items/item/powerup.qh>
+
+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;
+}
+++ /dev/null
-#include "instagib.qc"
--- /dev/null
+#include "sv_instagib.qh"
+
+int autocvar_g_instagib_ammo_drop;
+int autocvar_g_instagib_extralives;
+float autocvar_g_instagib_speed_highspeed;
+
+#include <server/client.qh>
+
+#include <common/items/all.qc>
+
+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;
+}
--- /dev/null
+#pragma once
+
+#include "items.qh"
+
+float autocvar_g_instagib_invis_alpha;
// generated file; do not modify
-#include <common/mutators/mutator/invincibleproj/invincibleproj.qc>
+#ifdef SVQC
+ #include <common/mutators/mutator/invincibleproj/sv_invincibleproj.qc>
+#endif
// generated file; do not modify
-#include <common/mutators/mutator/invincibleproj/invincibleproj.qh>
+#ifdef SVQC
+ #include <common/mutators/mutator/invincibleproj/sv_invincibleproj.qh>
+#endif
+++ /dev/null
-#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
+++ /dev/null
-#ifdef SVQC
-#include "invincibleproj.qc"
-#endif
--- /dev/null
+#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");
+}
--- /dev/null
+#pragma once
+++ /dev/null
-#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
--- /dev/null
+// generated file; do not modify
+#include <common/mutators/mutator/itemstime/itemstime.qc>
--- /dev/null
+// generated file; do not modify
+#include <common/mutators/mutator/itemstime/itemstime.qh>
--- /dev/null
+#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
--- /dev/null
+#pragma once
// generated file; do not modify
-#include <common/mutators/mutator/melee_only/melee_only.qc>
+#ifdef SVQC
+ #include <common/mutators/mutator/melee_only/sv_melee_only.qc>
+#endif
// generated file; do not modify
-#include <common/mutators/mutator/melee_only/melee_only.qh>
+#ifdef SVQC
+ #include <common/mutators/mutator/melee_only/sv_melee_only.qh>
+#endif
+++ /dev/null
-#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
+++ /dev/null
-#ifdef SVQC
-#include "melee_only.qc"
-#endif
--- /dev/null
+#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");
+}
--- /dev/null
+#pragma once
// generated file; do not modify
-#include <common/mutators/mutator/midair/midair.qc>
+#ifdef SVQC
+ #include <common/mutators/mutator/midair/sv_midair.qc>
+#endif
// generated file; do not modify
-#include <common/mutators/mutator/midair/midair.qh>
+#ifdef SVQC
+ #include <common/mutators/mutator/midair/sv_midair.qh>
+#endif
+++ /dev/null
-#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
+++ /dev/null
-#ifdef SVQC
-#include "midair.qc"
-#endif
--- /dev/null
+#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");
+}
--- /dev/null
+#pragma once
+++ /dev/null
-#ifndef MENUQC
-#include "multijump.qc"
-#endif
-#ifdef IMPLEMENTATION
+#include "multijump.qh"
+
+#ifndef MENUQC
+
#ifdef SVQC
#include <server/antilag.qh>
#endif
}
#endif
+
#endif
--- /dev/null
+#pragma once
+++ /dev/null
-#include "nades.qc"
-#ifndef MENUQC
-#include "net.qc"
-#endif
#include "nades.qh"
-#ifdef IMPLEMENTATION
-
#ifdef SVQC
bool autocvar_g_nades_nade_small;
float autocvar_g_nades_spread = 0.04;
#ifdef SVQC
-#include <common/gamemodes/all.qh>
+#include <common/gamemodes/_all.qh>
#include <common/monsters/spawn.qh>
#include <common/monsters/sv_monsters.qh>
#include <server/g_subs.qh>
}
#endif
-#endif
-#ifndef NADES_ALL_H
-#define NADES_ALL_H
+#pragma once
#include <common/teams.qh>
MUTATOR_HOOKABLE(Nade_Damage, EV_Nade_Damage);
#endif
-
-#endif
-#include "nades.qh"
+#include "net.qh"
+
+#ifndef MENUQC
-#ifdef IMPLEMENTATION
+#include "nades.qh"
#ifdef CSQC
.float ltime;
--- /dev/null
+#pragma once
// generated file; do not modify
-#include <common/mutators/mutator/new_toys/new_toys.qc>
+#ifdef SVQC
+ #include <common/mutators/mutator/new_toys/sv_new_toys.qc>
+#endif
// generated file; do not modify
-#include <common/mutators/mutator/new_toys/new_toys.qh>
+#ifdef SVQC
+ #include <common/mutators/mutator/new_toys/sv_new_toys.qh>
+#endif
+++ /dev/null
-#ifdef SVQC
-#include "new_toys.qc"
-#endif
+++ /dev/null
-#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
--- /dev/null
+#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;
+ }
+}
--- /dev/null
+#pragma once
// generated file; do not modify
-#include <common/mutators/mutator/nix/nix.qc>
+#ifdef SVQC
+ #include <common/mutators/mutator/nix/sv_nix.qc>
+#endif
// generated file; do not modify
-#include <common/mutators/mutator/nix/nix.qh>
+#ifdef SVQC
+ #include <common/mutators/mutator/nix/sv_nix.qh>
+#endif
+++ /dev/null
-#ifdef SVQC
-#include "nix.qc"
-#endif
+++ /dev/null
-#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
--- /dev/null
+#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";
+}
--- /dev/null
+#pragma once
// generated file; do not modify
#include <common/mutators/mutator/overkill/hmg.qc>
#include <common/mutators/mutator/overkill/overkill.qc>
+#ifdef CSQC
+ #include <common/mutators/mutator/overkill/cl_overkill.qc>
+#endif
+#ifdef SVQC
+ #include <common/mutators/mutator/overkill/sv_overkill.qc>
+#endif
#include <common/mutators/mutator/overkill/rpc.qc>
// generated file; do not modify
#include <common/mutators/mutator/overkill/hmg.qh>
#include <common/mutators/mutator/overkill/overkill.qh>
+#ifdef CSQC
+ #include <common/mutators/mutator/overkill/cl_overkill.qh>
+#endif
+#ifdef SVQC
+ #include <common/mutators/mutator/overkill/sv_overkill.qh>
+#endif
#include <common/mutators/mutator/overkill/rpc.qh>
--- /dev/null
+#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";
+ }
+}
--- /dev/null
+#pragma once
-#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);
}
#endif
-#endif
--- /dev/null
+#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));
+++ /dev/null
-#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
-#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
--- /dev/null
+#pragma once
-#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); }
}
#endif
-#endif
--- /dev/null
+#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));
--- /dev/null
+#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";
+}
--- /dev/null
+#pragma once
// generated file; do not modify
-#include <common/mutators/mutator/physical_items/physical_items.qc>
+#ifdef SVQC
+ #include <common/mutators/mutator/physical_items/sv_physical_items.qc>
+#endif
// generated file; do not modify
-#include <common/mutators/mutator/physical_items/physical_items.qh>
+#ifdef SVQC
+ #include <common/mutators/mutator/physical_items/sv_physical_items.qh>
+#endif
+++ /dev/null
-#ifdef SVQC
-#include "physical_items.qc"
-#endif
+++ /dev/null
-#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
--- /dev/null
+#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);
+}
--- /dev/null
+#pragma once
// generated file; do not modify
-#include <common/mutators/mutator/pinata/pinata.qc>
+#ifdef SVQC
+ #include <common/mutators/mutator/pinata/sv_pinata.qc>
+#endif
// generated file; do not modify
-#include <common/mutators/mutator/pinata/pinata.qh>
+#ifdef SVQC
+ #include <common/mutators/mutator/pinata/sv_pinata.qh>
+#endif
+++ /dev/null
-#ifdef SVQC
-#include "pinata.qc"
-#endif
+++ /dev/null
-#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
--- /dev/null
+#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");
+}
--- /dev/null
+#pragma once
// generated file; do not modify
-#include <common/mutators/mutator/random_gravity/random_gravity.qc>
+#ifdef SVQC
+ #include <common/mutators/mutator/random_gravity/sv_random_gravity.qc>
+#endif
// generated file; do not modify
-#include <common/mutators/mutator/random_gravity/random_gravity.qh>
+#ifdef SVQC
+ #include <common/mutators/mutator/random_gravity/sv_random_gravity.qh>
+#endif
+++ /dev/null
-#ifdef SVQC
-#include "random_gravity.qc"
-#endif
+++ /dev/null
-#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
--- /dev/null
+#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");
+}
--- /dev/null
+#pragma once
// generated file; do not modify
-#include <common/mutators/mutator/rocketflying/rocketflying.qc>
+#ifdef SVQC
+ #include <common/mutators/mutator/rocketflying/sv_rocketflying.qc>
+#endif
// generated file; do not modify
-#include <common/mutators/mutator/rocketflying/rocketflying.qh>
+#ifdef SVQC
+ #include <common/mutators/mutator/rocketflying/sv_rocketflying.qh>
+#endif
+++ /dev/null
-#ifdef SVQC
-#include "rocketflying.qc"
-#endif
+++ /dev/null
-#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
--- /dev/null
+#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");
+}
--- /dev/null
+#pragma once
// generated file; do not modify
-#include <common/mutators/mutator/rocketminsta/rocketminsta.qc>
+#ifdef SVQC
+ #include <common/mutators/mutator/rocketminsta/sv_rocketminsta.qc>
+#endif
// generated file; do not modify
-#include <common/mutators/mutator/rocketminsta/rocketminsta.qh>
+#ifdef SVQC
+ #include <common/mutators/mutator/rocketminsta/sv_rocketminsta.qh>
+#endif
+++ /dev/null
-#ifdef SVQC
-#include "rocketminsta.qc"
-#endif
+++ /dev/null
-#ifdef IMPLEMENTATION
-#include <common/deathtypes/all.qh>
-#include <server/round_handler.qh>
-
-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
--- /dev/null
+#include "sv_rocketminsta.qh"
+
+#include <common/deathtypes/all.qh>
+#include <server/round_handler.qh>
+
+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
+}
--- /dev/null
+#pragma once
// generated file; do not modify
-#include <common/mutators/mutator/running_guns/running_guns.qc>
+#ifdef SVQC
+ #include <common/mutators/mutator/running_guns/sv_running_guns.qc>
+#endif
// generated file; do not modify
-#include <common/mutators/mutator/running_guns/running_guns.qh>
+#ifdef SVQC
+ #include <common/mutators/mutator/running_guns/sv_running_guns.qh>
+#endif
+++ /dev/null
-#ifdef SVQC
-#include "running_guns.qc"
-#endif
+++ /dev/null
-#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
--- /dev/null
+#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;
+}
--- /dev/null
+#pragma once
// generated file; do not modify
-#include <common/mutators/mutator/sandbox/sandbox.qc>
+#ifdef SVQC
+ #include <common/mutators/mutator/sandbox/sv_sandbox.qc>
+#endif
// generated file; do not modify
-#include <common/mutators/mutator/sandbox/sandbox.qh>
+#ifdef SVQC
+ #include <common/mutators/mutator/sandbox/sv_sandbox.qh>
+#endif
+++ /dev/null
-#ifdef SVQC
-#include "sandbox.qc"
-
-#endif
+++ /dev/null
-#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
--- /dev/null
+#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;
+}
--- /dev/null
+#pragma once
// generated file; do not modify
-#include <common/mutators/mutator/spawn_near_teammate/spawn_near_teammate.qc>
+#ifdef SVQC
+ #include <common/mutators/mutator/spawn_near_teammate/sv_spawn_near_teammate.qc>
+#endif
// generated file; do not modify
-#include <common/mutators/mutator/spawn_near_teammate/spawn_near_teammate.qh>
+#ifdef SVQC
+ #include <common/mutators/mutator/spawn_near_teammate/sv_spawn_near_teammate.qh>
+#endif
+++ /dev/null
-#ifdef SVQC
-#include "spawn_near_teammate.qc"
-#endif
+++ /dev/null
-#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
--- /dev/null
+#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");
--- /dev/null
+#pragma once
// generated file; do not modify
-#include <common/mutators/mutator/superspec/superspec.qc>
+#ifdef SVQC
+ #include <common/mutators/mutator/superspec/sv_superspec.qc>
+#endif
// generated file; do not modify
-#include <common/mutators/mutator/superspec/superspec.qh>
+#ifdef SVQC
+ #include <common/mutators/mutator/superspec/sv_superspec.qh>
+#endif
+++ /dev/null
-#ifdef SVQC
-#include "superspec.qc"
-#endif
+++ /dev/null
-#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
--- /dev/null
+#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);
+}
--- /dev/null
+#pragma once
// generated file; do not modify
-#include <common/mutators/mutator/touchexplode/touchexplode.qc>
+#ifdef SVQC
+ #include <common/mutators/mutator/touchexplode/sv_touchexplode.qc>
+#endif
// generated file; do not modify
-#include <common/mutators/mutator/touchexplode/touchexplode.qh>
+#ifdef SVQC
+ #include <common/mutators/mutator/touchexplode/sv_touchexplode.qh>
+#endif
+++ /dev/null
-#ifdef SVQC
-#include "touchexplode.qc"
-#endif
--- /dev/null
+#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;
+ }
+ ));
+}
--- /dev/null
+#pragma once
+++ /dev/null
-#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
// generated file; do not modify
-#include <common/mutators/mutator/vampire/vampire.qc>
+#ifdef SVQC
+ #include <common/mutators/mutator/vampire/sv_vampire.qc>
+#endif
// generated file; do not modify
-#include <common/mutators/mutator/vampire/vampire.qh>
+#ifdef SVQC
+ #include <common/mutators/mutator/vampire/sv_vampire.qh>
+#endif
+++ /dev/null
-#ifdef SVQC
-#include "vampire.qc"
-#endif
--- /dev/null
+#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");
+}
--- /dev/null
+#pragma once
+++ /dev/null
-#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
// generated file; do not modify
-#include <common/mutators/mutator/vampirehook/vampirehook.qc>
+#ifdef SVQC
+ #include <common/mutators/mutator/vampirehook/sv_vampirehook.qc>
+#endif
// generated file; do not modify
-#include <common/mutators/mutator/vampirehook/vampirehook.qh>
+#ifdef SVQC
+ #include <common/mutators/mutator/vampirehook/sv_vampirehook.qh>
+#endif
+++ /dev/null
-#ifdef SVQC
-#include "vampirehook.qc"
-#endif
--- /dev/null
+#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?!
+ }
+}
--- /dev/null
+#pragma once
+++ /dev/null
-#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
-#ifndef WAYPOINTS_ALL_H
-#define WAYPOINTS_ALL_H
+#pragma once
#include "waypointsprites.qh"
REGISTER_RADARICON(Weapon, 1);
#include "all.inc"
-
-#endif
+++ /dev/null
-#include "waypointsprites.qc"
#include "waypointsprites.qh"
-#ifdef IMPLEMENTATION
-
REGISTER_MUTATOR(waypointsprites, true);
REGISTER_NET_LINKED(waypointsprites)
WaypointSprite_DetachCarrier(this);
}
#endif
-#endif
// generated file; do not modify
-#include <common/mutators/mutator/weaponarena_random/weaponarena_random.qc>
+#ifdef SVQC
+ #include <common/mutators/mutator/weaponarena_random/sv_weaponarena_random.qc>
+#endif
// generated file; do not modify
-#include <common/mutators/mutator/weaponarena_random/weaponarena_random.qh>
+#ifdef SVQC
+ #include <common/mutators/mutator/weaponarena_random/sv_weaponarena_random.qh>
+#endif
+++ /dev/null
-#ifdef SVQC
-#include "weaponarena_random.qc"
-#endif
--- /dev/null
+#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);
+}
--- /dev/null
+#pragma once
+++ /dev/null
-#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
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
#pragma once
#ifdef SVQC
-#include <server/cl_client.qh>
+#include <server/client.qh>
#endif
// Full list of all stat constants, included in a single location for easy reference
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
// generated file; do not modify
#include <common/turrets/all.qc>
#include <common/turrets/checkpoint.qc>
-#include <common/turrets/cl_turrets.qc>
#include <common/turrets/config.qc>
-#include <common/turrets/sv_turrets.qc>
#include <common/turrets/targettrigger.qc>
+#include <common/turrets/turrets.qc>
+#ifdef CSQC
+ #include <common/turrets/cl_turrets.qc>
+#endif
+#ifdef SVQC
+ #include <common/turrets/sv_turrets.qc>
+#endif
#include <common/turrets/util.qc>
// generated file; do not modify
#include <common/turrets/all.qh>
#include <common/turrets/checkpoint.qh>
-#include <common/turrets/cl_turrets.qh>
#include <common/turrets/config.qh>
-#include <common/turrets/sv_turrets.qh>
#include <common/turrets/targettrigger.qh>
+#include <common/turrets/turrets.qh>
+#ifdef CSQC
+ #include <common/turrets/cl_turrets.qh>
+#endif
+#ifdef SVQC
+ #include <common/turrets/sv_turrets.qh>
+#endif
#include <common/turrets/util.qh>
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
// generated file; do not modify
#include <common/vehicles/all.qc>
-#include <common/vehicles/cl_vehicles.qc>
-#include <common/vehicles/sv_vehicles.qc>
+#include <common/vehicles/vehicles.qc>
+#ifdef CSQC
+ #include <common/vehicles/cl_vehicles.qc>
+#endif
+#ifdef SVQC
+ #include <common/vehicles/sv_vehicles.qc>
+#endif
// generated file; do not modify
#include <common/vehicles/all.qh>
-#include <common/vehicles/cl_vehicles.qh>
-#include <common/vehicles/sv_vehicles.qh>
+#include <common/vehicles/vehicles.qh>
+#ifdef CSQC
+ #include <common/vehicles/cl_vehicles.qh>
+#endif
+#ifdef SVQC
+ #include <common/vehicles/sv_vehicles.qh>
+#endif
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
// generated file; do not modify
-#include <lib/csqcmodel/cl_model.qc>
-#include <lib/csqcmodel/cl_player.qc>
#include <lib/csqcmodel/interpolate.qc>
-#include <lib/csqcmodel/sv_model.qc>
+#include <lib/csqcmodel/model.qc>
+#ifdef CSQC
+ #include <lib/csqcmodel/cl_model.qc>
+#endif
+#ifdef SVQC
+ #include <lib/csqcmodel/sv_model.qc>
+#endif
+#include <lib/csqcmodel/player.qc>
+#ifdef CSQC
+ #include <lib/csqcmodel/cl_player.qc>
+#endif
// generated file; do not modify
-#include <lib/csqcmodel/cl_model.qh>
-#include <lib/csqcmodel/cl_player.qh>
#include <lib/csqcmodel/interpolate.qh>
-#include <lib/csqcmodel/sv_model.qh>
+#include <lib/csqcmodel/model.qh>
+#ifdef CSQC
+ #include <lib/csqcmodel/cl_model.qh>
+#endif
+#ifdef SVQC
+ #include <lib/csqcmodel/sv_model.qh>
+#endif
+#include <lib/csqcmodel/player.qh>
+#ifdef CSQC
+ #include <lib/csqcmodel/cl_player.qh>
+#endif
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
--- /dev/null
+#pragma once
#include <server/antilag.qc>
#include <server/campaign.qc>
#include <server/cheats.qc>
-#include <server/cl_client.qc>
-#include <server/cl_impulse.qc>
-#include <server/cl_player.qc>
+#include <server/client.qc>
#include <server/g_damage.qc>
#include <server/g_hook.qc>
#include <server/g_lights.qc>
#include <server/g_models.qc>
#include <server/g_subs.qc>
#include <server/g_world.qc>
+#include <server/impulse.qc>
#include <server/ipban.qc>
#include <server/item_key.qc>
#include <server/mapvoting.qc>
#include <server/matrix.qc>
#include <server/miscfunctions.qc>
+#include <server/player.qc>
#include <server/playerdemo.qc>
#include <server/portals.qc>
#include <server/race.qc>
#include <server/scores_rules.qc>
#include <server/spawnpoints.qc>
#include <server/steerlib.qc>
-#include <server/sv_main.qc>
+#ifdef SVQC
+ #include <server/sv_main.qc>
+#endif
#include <server/teamplay.qc>
#include <server/tests.qc>
#include <server/t_halflife.qc>
#include <server/antilag.qh>
#include <server/campaign.qh>
#include <server/cheats.qh>
-#include <server/cl_client.qh>
-#include <server/cl_impulse.qh>
-#include <server/cl_player.qh>
+#include <server/client.qh>
#include <server/g_damage.qh>
#include <server/g_hook.qh>
#include <server/g_lights.qh>
#include <server/g_models.qh>
#include <server/g_subs.qh>
#include <server/g_world.qh>
+#include <server/impulse.qh>
#include <server/ipban.qh>
#include <server/item_key.qh>
#include <server/mapvoting.qh>
#include <server/matrix.qh>
#include <server/miscfunctions.qh>
+#include <server/player.qh>
#include <server/playerdemo.qh>
#include <server/portals.qh>
#include <server/race.qh>
#include <server/scores_rules.qh>
#include <server/spawnpoints.qh>
#include <server/steerlib.qh>
-#include <server/sv_main.qh>
+#ifdef SVQC
+ #include <server/sv_main.qh>
+#endif
#include <server/teamplay.qh>
#include <server/tests.qh>
#include <server/t_halflife.qh>
#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"
+++ /dev/null
-#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 <common/state.qh>
-
-#include <common/effects/qc/globalsound.qh>
-
-#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);
-}
+++ /dev/null
-#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); }
+++ /dev/null
-#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 <common/state.qh>
-
-#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);
-}
+++ /dev/null
-#pragma once
-
-void ImpulseCommands(entity this);
+++ /dev/null
-#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;
-}
+++ /dev/null
-#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_<gametype>_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);
--- /dev/null
+#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 <common/state.qh>
+
+#include <common/effects/qc/globalsound.qh>
+
+#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);
+}
--- /dev/null
+#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); }
// generated file; do not modify
#include <server/command/banning.qh>
#include <server/command/cmd.qh>
+#ifdef SVQC
+ #include <server/command/sv_cmd.qh>
+#endif
#include <server/command/common.qh>
#include <server/command/getreplies.qh>
#include <server/command/radarmap.qh>
#include "common.qh"
-#include "../cl_player.qh"
+#include "../player.qh"
#include "../ipban.qh"
#include <common/util.qh>
#include "../campaign.qh"
#include "../cheats.qh"
-#include "../cl_player.qh"
+#include "../player.qh"
#include "../ipban.qh"
#include "../mapvoting.qh"
#include "../scores.qh"
#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"
#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"
#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"
--- /dev/null
+#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 <common/state.qh>
+
+#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);
+}
--- /dev/null
+#pragma once
+
+void ImpulseCommands(entity this);
#include "matrix.qh"
-#include "cl_player.qh"
+#include "player.qh"
var void MX_Handle(int buf, string ancestor)
{
/**/
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) \
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
#include <server/pathlib/pathlib.qh>
#include <common/vehicles/all.qh>
-#include <server/cl_client.qh>
-#include <server/cl_player.qh>
-#include <server/cl_impulse.qh>
+#include <server/client.qh>
+#include <server/player.qh>
+#include <server/impulse.qh>
#include <server/cheats.qh>
#include <server/g_damage.qh>
#include <common/mutators/base.qh>
-#include <server/cl_client.qh>
-#include <server/cl_player.qh>
-#include <server/cl_impulse.qh>
+#include <server/client.qh>
+#include <server/player.qh>
+#include <server/impulse.qh>
#include <server/cheats.qh>
#include <server/g_damage.qh>
#include <server/round_handler.qh>
--- /dev/null
+#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;
+}
--- /dev/null
+#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_<gametype>_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);
#include "race.qh"
-#include "cl_client.qh"
+#include "client.qh"
#include "portals.qh"
#include "scores.qh"
#include "spawnpoints.qh"
#include "scores_rules.qh"
-#include "cl_client.qh"
+#include "client.qh"
#include "scores.qh"
int ScoreRules_teams;
#include "teamplay.qh"
-#include "cl_client.qh"
+#include "client.qh"
#include "race.qh"
#include "scores.qh"
#include "scores_rules.qh"
#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)
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;
#pragma once
#include "autocvars.qh"
-#include "cl_client.qh"
+#include "client.qh"
#include "command/_all.qh"
#include "weapons/common.qh"
#include "weapons/selection.qh"
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}