noref float autocvar_net_connecttimeout = 30;
+//SMB mods
#include "announcer.qc"
+#include "cvars.qh"
#ifdef GAMEQC
#include "anim.qc"
--- /dev/null
+// the mod relies on AUTOCVAR() to define cvars for use in a special dumping command, so admins know which cvars are from the mod
+// but, many of these don't have any direct references, so we need to use this custom copy of the AUTOCVAR()
+
+#define __AUTOCVAR_NOREF(file, archive, var, type, desc, default) \
+ [[accumulate]] void RegisterCvars(void(string, string, string, bool, string) f) \
+ { \
+ f( #var, repr_cvar_##type(default), desc, archive, file); \
+ } \
+ noref type autocvar_##var = default
+#define AUTOCVAR_NOREF_5(file, archive, var, type, desc) \
+ __AUTOCVAR_NOREF(file, archive, var, type, desc, default_##type)
+#define AUTOCVAR_NOREF_6(file, archive, var, type, default, desc) \
+ __AUTOCVAR_NOREF(file, archive, var, type, desc, default)
+#define _AUTOCVAR_NOREF(...) EVAL__AUTOCVAR_NOREF(OVERLOAD(AUTOCVAR_NOREF, __FILE__, __VA_ARGS__))
+#define EVAL__AUTOCVAR_NOREF(...) __VA_ARGS__
+#define AUTOCVAR_NOREF_SAVE(...) _AUTOCVAR_NOREF(true, __VA_ARGS__)
+#define AUTOCVAR_NOREF(...) _AUTOCVAR_NOREF(false, __VA_ARGS__)
#include <common/mutators/mutator/doublejump/_mod.inc>
#include <common/mutators/mutator/dynamic_handicap/_mod.inc>
#include <common/mutators/mutator/globalforces/_mod.inc>
+
+//SMB mod feature Hats:
+#include <common/mutators/mutator/hats/_mod.inc>
+
#include <common/mutators/mutator/hook/_mod.inc>
#include <common/mutators/mutator/instagib/_mod.inc>
#include <common/mutators/mutator/invincibleproj/_mod.inc>
#include <common/mutators/mutator/overkill/_mod.inc>
#include <common/mutators/mutator/physical_items/_mod.inc>
#include <common/mutators/mutator/pinata/_mod.inc>
+
+//SMB mod feature Player crush:
+#include <common/mutators/mutator/player_crush/_mod.inc>
+
#include <common/mutators/mutator/random_gravity/_mod.inc>
#include <common/mutators/mutator/random_items/_mod.inc>
#include <common/mutators/mutator/rocketflying/_mod.inc>
#include <common/mutators/mutator/doublejump/_mod.qh>
#include <common/mutators/mutator/dynamic_handicap/_mod.qh>
#include <common/mutators/mutator/globalforces/_mod.qh>
+
+//SMB mod feature Hats:
+#include <common/mutators/mutator/hats/_mod.qh>
+
#include <common/mutators/mutator/hook/_mod.qh>
#include <common/mutators/mutator/instagib/_mod.qh>
#include <common/mutators/mutator/invincibleproj/_mod.qh>
#include <common/mutators/mutator/overkill/_mod.qh>
#include <common/mutators/mutator/physical_items/_mod.qh>
#include <common/mutators/mutator/pinata/_mod.qh>
+
+//SMB mod feature Player crush:
+#include <common/mutators/mutator/player_crush/_mod.qh>
+
#include <common/mutators/mutator/random_gravity/_mod.qh>
#include <common/mutators/mutator/random_items/_mod.qh>
#include <common/mutators/mutator/rocketflying/_mod.qh>
--- /dev/null
+// generated file; do not modify
+#include <common/mutators/mutator/hats/hats.qc>
\ No newline at end of file
--- /dev/null
+// generated file; do not modify
+#include <common/mutators/mutator/hats/hats.qh>
\ No newline at end of file
--- /dev/null
+//FEATURE: Hats!
+#include "hats.qh"
+
+#ifdef SVQC
+.string cvar_cl_hat;
+.bool cvar_cl_nohats;
+#elif defined(CSQC)
+string cvar_cl_hat;
+bool cvar_cl_nohats;
+#endif
+#ifdef GAMEQC
+vector get_model_parameters_hat_height;
+float get_model_parameters_hat_scale;
+vector get_model_parameters_hat_angles;
+
+REPLICATE(cvar_cl_hat, string, "cl_magical_hax");
+REPLICATE(cvar_cl_nohats, bool, "cl_nohats");
+#endif
+
+#ifdef SVQC
+void hats_Precache(string pattern);
+
+AUTOCVAR(g_hats, bool, false, "Allow clients to use hats");
+AUTOCVAR(g_hats_default, string, "", "Default hat when player has none on");
+AUTOCVAR(g_hats_nolegacy, bool, false, "Use new default offsets (not recommended yet)");
+
+REGISTER_MUTATOR(hats, autocvar_g_hats && !cvar("g_overkill"))
+{
+ MUTATOR_ONADD
+ {
+ hats_Precache("models/hats/*.md3");
+ hats_Precache("models/hats/*.obj");
+ hats_Precache("models/hats/*.iqm");
+ }
+}
+
+.entity hatentity;
+.string hatname; // only update when the player spawns
+
+//ATTRIB(Client, cvar_cl_hat, string, this.cvar_cl_hat);
+//ATTRIB(Client, cvar_cl_nohats, bool, this.cvar_cl_nohats);
+
+void hats_Precache(string pattern)
+{
+ float globhandle, i, n;
+ string f;
+
+ globhandle = search_begin(pattern, true, false);
+ if (globhandle < 0)
+ return;
+ n = search_getsize(globhandle);
+ for (i = 0; i < n; ++i)
+ {
+ f = search_getfilename(globhandle, i);
+ precache_model(f);
+ }
+ search_end(globhandle);
+}
+
+float hats_getscale(entity e)
+{
+ float s;
+ get_model_parameters(e.model, e.skin);
+ s = get_model_parameters_hat_scale;
+ get_model_parameters(string_null, 0);
+
+ return s;
+}
+
+vector hats_getheight(entity e)
+{
+ vector s, hat_offset = '0 0 0';
+ float myscale;
+ get_model_parameters(e.model, e.skin);
+ s = get_model_parameters_hat_height;
+ myscale = get_model_parameters_hat_scale;
+ get_model_parameters(string_null, 0);
+
+ if(autocvar_g_hats_nolegacy)
+ {
+ // attempt to get default height
+ hat_offset = '0 10 -1' * ((myscale) ? myscale : 1); // legacy models are offset by a margin that varies depending on their size
+ }
+
+ return hat_offset + s;
+}
+
+vector hats_getangles(entity e)
+{
+ vector s, hat_offset = '0 0 0';
+ get_model_parameters(e.model, e.skin);
+ s = get_model_parameters_hat_angles;
+ get_model_parameters(string_null, 0);
+
+ if(autocvar_g_hats_nolegacy)
+ {
+ // attempt to get default angle
+ hat_offset = '90 0 0';
+ }
+
+ return hat_offset + s;
+}
+
+string hat_exists(string pattern)
+{
+ string try;
+ try = strcat(pattern, ".md3");
+ if(fexists(try))
+ return try;
+ try = strcat(pattern, ".obj");
+ if(fexists(try))
+ return try;
+ try = strcat(pattern, ".iqm");
+ if(fexists(try))
+ return try;
+
+ return string_null;
+}
+
+void hat_Think(entity this)
+{
+ entity player = this.owner;
+ float tag_found;
+ this.nextthink = time;
+ if (player.hatentity != this || !player)
+ {
+ delete(this);
+ return;
+ }
+ if(STAT(FROZEN, player) || IS_DEAD(player))
+ {
+ this.model = "";
+ return;
+ }
+ if (this.hatname != player.hatname || this.dmg != player.modelindex || this.deadflag != player.deadflag)
+ {
+ this.hatname = player.hatname;
+ this.dmg = player.modelindex;
+ this.deadflag = player.deadflag;
+ string exists = ((player.hatname && player.hatname != "") ? hat_exists(strcat("models/hats/", player.hatname)) : string_null);
+ if (player.hatname && player.hatname != "" && exists)
+ _setmodel(this, exists); // precision set below
+ else
+ this.model = "";
+
+ if((tag_found = gettagindex(player, "tag_head")))
+ {
+ this.tag_index = tag_found;
+ this.tag_entity = player;
+ setorigin(this, hats_getheight(player));
+ this.scale = hats_getscale(player);
+ this.angles = hats_getangles(player);
+ }
+ else
+ {
+ setattachment(this, player, "head");
+ setorigin(this, hats_getheight(player));
+ this.scale = hats_getscale(player);
+ this.angles = hats_getangles(player);
+ }
+ }
+ this.effects = player.effects;
+ this.effects |= EF_LOWPRECISION;
+ this.effects = this.effects & EFMASK_CHEAP; // eat performance
+ if(this.scale < -1)
+ this.alpha = -1;
+ else if(player.alpha == default_player_alpha)
+ this.alpha = default_weapon_alpha;
+ else if(player.alpha != 0)
+ this.alpha = player.alpha;
+ else
+ this.alpha = 1;
+
+ this.glowmod = player.glowmod;
+ this.colormap = player.colormap;
+
+ CSQCMODEL_AUTOUPDATE(this);
+}
+
+bool hats_Customize(entity this, entity client)
+{
+ if(CS_CVAR(client).cvar_cl_nohats) { return false; }
+ return true;
+}
+
+void hats_SpawnHat(entity this)
+{
+ this.hatentity = new(hatentity);
+ this.hatentity.solid = SOLID_NOT;
+ this.hatentity.owner = this;
+ this.hatentity.hatentity = this.hatentity;
+ setorigin(this.hatentity, '0 0 0');
+ this.hatentity.angles = '0 0 0';
+ setthink(this.hatentity, hat_Think);
+ this.hatentity.nextthink = time;
+ setcefc(this.hatentity, hats_Customize);
+
+ CSQCMODEL_AUTOINIT(this.hatentity);
+}
+
+MUTATOR_HOOKFUNCTION(hats, PutClientInServer)
+{
+ entity player = M_ARGV(0, entity);
+
+ hats_SpawnHat(player);
+ player.hatentity.alpha = default_weapon_alpha;
+ if(CS_CVAR(player).cvar_cl_hat != "")
+ player.hatname = strzone(CS_CVAR(player).cvar_cl_hat);
+ else
+ player.hatname = strzone(autocvar_g_hats_default);
+}
+
+MUTATOR_HOOKFUNCTION(hats, ClientDisconnect)
+{
+ entity player = M_ARGV(0, entity);
+
+ player.hatentity = NULL;
+ player.hatname = "";
+}
+
+MUTATOR_HOOKFUNCTION(hats, MakePlayerObserver)
+{
+ entity player = M_ARGV(0, entity);
+
+ player.hatentity = NULL;
+ player.hatname = "";
+}
+
+MUTATOR_HOOKFUNCTION(hats, ClearModelParams)
+{
+ get_model_parameters_hat_height = '0 0 0';
+ get_model_parameters_hat_scale = 0;
+ get_model_parameters_hat_angles = '0 0 0';
+}
+
+MUTATOR_HOOKFUNCTION(hats, GetModelParams)
+{
+ string checkmodel_input = M_ARGV(0, string);
+ string checkmodel_command = M_ARGV(1, string);
+
+ if(checkmodel_input == "hat_height")
+ get_model_parameters_hat_height = stov(checkmodel_command);
+ if(checkmodel_input == "hat_scale")
+ get_model_parameters_hat_scale = stof(checkmodel_command);
+ if(checkmodel_input == "hat_angles")
+ get_model_parameters_hat_angles = stov(checkmodel_command);
+}
+
+#elif defined(CSQC)
+
+REGISTER_MUTATOR(mod_hats, true);
+
+AUTOCVAR_NOREF_SAVE(cl_nohats, bool, false, "Disable hats completely, requires reconnect or sendcvar");
+AUTOCVAR_NOREF_SAVE(cl_magical_hax, string, "", "Magical hax, use at your own risk");
+
+string get_model_parameters_bone_head;
+
+classfield(Skeleton) .int bone_hat;
+classfield(Skeleton) .vector hat_height;
+classfield(Skeleton) .float hat_scale;
+
+MUTATOR_HOOKFUNCTION(mod_hats, TagIndex_Apply)
+{
+ entity ent = M_ARGV(0, entity);
+
+ if(substring(ent.model, 0, 12) == "models/hats/")
+ {
+ if(substring(ent.tag_entity.model, 0, 12) == "models/hats/")
+ {
+ ent.tag_index = gettagindex(ent.tag_entity, "tag_head");
+ if(!ent.tag_index)
+ ent.tag_index = gettagindex(ent.tag_entity, "head");
+ if(!ent.tag_index)
+ {
+ // we need to prevent this from 'appening
+ ent.tag_entity = NULL;
+ ent.drawmask = 0;
+ }
+ }
+ else if(ent.tag_entity.isplayermodel)
+ {
+ skeleton_loadinfo(ent.tag_entity);
+ ent.tag_index = ent.tag_entity.bone_hat;
+ }
+ }
+}
+
+MUTATOR_HOOKFUNCTION(mod_hats, ClearModelParams)
+{
+ get_model_parameters_bone_head = string_null;
+ get_model_parameters_hat_height = '0 0 0';
+ get_model_parameters_hat_scale = 0;
+ get_model_parameters_hat_angles = '0 0 0';
+}
+
+MUTATOR_HOOKFUNCTION(mod_hats, GetModelParams)
+{
+ string checkmodel_input = M_ARGV(0, string);
+ string checkmodel_command = M_ARGV(1, string);
+
+ if(checkmodel_input == "bone_head")
+ get_model_parameters_bone_head = checkmodel_command;
+ if(checkmodel_input == "hat_height")
+ get_model_parameters_hat_height = stov(checkmodel_command);
+ if(checkmodel_input == "hat_scale")
+ get_model_parameters_hat_scale = stof(checkmodel_command);
+ if(checkmodel_input == "hat_angles")
+ get_model_parameters_hat_angles = stov(checkmodel_command);
+}
+
+MUTATOR_HOOKFUNCTION(mod_hats, Skeleton_CheckBones)
+{
+ entity ent = M_ARGV(0, entity);
+
+ ent.bone_hat = gettagindex(ent, "head");
+ if(!ent.bone_hat)
+ ent.bone_hat = gettagindex(ent, "tag_head");
+ if(!ent.bone_hat)
+ ent.bone_hat = gettagindex(ent, "bip01 head");
+}
+
+MUTATOR_HOOKFUNCTION(mod_hats, Skeleton_CheckModel)
+{
+ entity ent = M_ARGV(0, entity);
+
+ if(get_model_parameters_bone_head)
+ ent.bone_hat = gettagindex(ent, get_model_parameters_bone_head);
+}
+
+#endif
--- /dev/null
+#pragma once
--- /dev/null
+// generated file; do not modify
+#include <common/mutators/mutator/player_crush/player_crush.qc>
\ No newline at end of file
--- /dev/null
+// generated file; do not modify
+#include <common/mutators/mutator/player_crush/player_crush.qh>
\ No newline at end of file
--- /dev/null
+//FEATURE: Crushing, for all your goomba stomping needs
+#include "player_crush.qh"
+
+REGISTER_MUTATOR(pc, true);
+
+REGISTER_STAT(PLAYER_CRUSH, bool, autocvar_g_player_crush)
+REGISTER_STAT(PLAYER_CRUSH_SIMPLE, int, autocvar_g_player_crush_simple)
+REGISTER_STAT(PLAYER_CRUSH_BOUNCE, float, autocvar_g_player_crush_bounce)
+REGISTER_STAT(PLAYER_CRUSH_BOUNCE_JUMP, float, autocvar_g_player_crush_bounce_jump)
+
+#ifdef GAMEQC
+void pc_PlayerTouch(entity this, entity toucher)
+{
+ if(toucher == NULL)
+ return;
+#endif
+
+#ifdef SVQC
+ bool and_monster = IS_MONSTER(toucher) && (this.monsterdef.spawnflags & MON_FLAG_CRUSH);
+
+ if(!autocvar_g_player_crush && !and_monster)
+ return;
+
+ if(!IS_PLAYER(this))
+ return;
+
+ if(!IS_PLAYER(toucher) && !and_monster)
+ return;
+
+ if(IS_DEAD(this) || IS_DEAD(toucher))
+ return;
+
+ if(!this.iscreature || !toucher.iscreature)
+ return;
+
+ if(weaponLocked(this))
+ return;
+#elif defined(CSQC)
+ if(STAT(PLAYER_CRUSH_SIMPLE) != -1)
+ return;
+ if(!IS_PLAYER(toucher))
+ return;
+ if(!STAT(PLAYER_CRUSH))
+ return;
+#endif
+
+#ifdef GAMEQC
+ if(STAT(PLAYER_CRUSH_SIMPLE, this) == 1 && IS_PLAYER(toucher))
+ {
+#endif
+
+#ifdef SVQC
+ vector vdir = normalize(toucher.origin - this.origin);
+
+ if(vdir_z > autocvar_g_player_crush_headheight) // adjust this to set how sharp from above players need to hit the player to crush them.
+ Damage (this, toucher, toucher, autocvar_g_player_crush_damage, DEATH_VH_CRUSH.m_id, DMG_NOWEP, this.origin, '0 0 0');
+#endif
+
+#ifdef GAMEQC
+ }
+ else if(STAT(PLAYER_CRUSH_SIMPLE, this) == 0)
+ {
+ tracebox(this.origin, this.mins, this.maxs, this.origin - ('0 0 1' * (this.maxs_z + 5)), MOVE_NORMAL, this);
+
+ if(trace_ent == toucher)
+ {
+ float mjumpheight = STAT(PLAYER_CRUSH_BOUNCE, this);
+
+ setorigin(this, this.origin + '0 0 2');
+
+ if(PHYS_INPUT_BUTTON_JUMP(this))
+ {
+ mjumpheight = STAT(PLAYER_CRUSH_BOUNCE_JUMP, this);
+ SET_JUMP_HELD(this);
+ }
+
+ UNSET_ONGROUND(this);
+
+ this.velocity_z = mjumpheight;
+#endif
+ #ifdef SVQC
+ this.oldvelocity_z = this.velocity_z;
+
+ animdecide_setaction(this, ANIMACTION_JUMP, true);
+
+ Damage (toucher, this, this, autocvar_g_player_crush_damage, DEATH_VH_CRUSH.m_id, DMG_NOWEP, toucher.origin, '0 0 0');
+ #endif
+#ifdef GAMEQC
+ }
+ }
+ else if(STAT(PLAYER_CRUSH_SIMPLE, this) == -1)
+ {
+ if(tracebox_hits_box(this.origin, this.mins, this.maxs, this.origin - ('0 0 1' * (this.maxs_z + 5)), toucher.origin + toucher.mins, toucher.origin + toucher.maxs))
+ {
+ float mjumpheight = STAT(PLAYER_CRUSH_BOUNCE, this);
+
+ setorigin(this, this.origin + '0 0 2');
+
+ if(PHYS_INPUT_BUTTON_JUMP(this))
+ {
+ mjumpheight = STAT(PLAYER_CRUSH_BOUNCE_JUMP, this);
+ SET_JUMP_HELD(this);
+ }
+
+ UNSET_ONGROUND(this);
+
+ this.velocity_z = mjumpheight;
+#endif
+ #ifdef SVQC
+ this.oldvelocity_z = this.velocity_z;
+
+ animdecide_setaction(this, ANIMACTION_JUMP, true);
+
+ Damage (toucher, this, this, autocvar_g_player_crush_damage, DEATH_VH_CRUSH.m_id, DMG_NOWEP, toucher.origin, '0 0 0');
+ #endif
+#ifdef GAMEQC
+ }
+ }
+}
+#endif
+
+#ifdef SVQC
+MUTATOR_HOOKFUNCTION(pc, PlayerSpawn)
+{
+ entity player = M_ARGV(0, entity);
+
+ settouch(player, pc_PlayerTouch);
+}
+#elif defined(CSQC)
+MUTATOR_HOOKFUNCTION(pc, PlayerPhysics)
+{
+ if(!STAT(PLAYER_CRUSH)) return;
+ if(STAT(PLAYER_CRUSH_SIMPLE) != -1) return;
+
+ entity player = M_ARGV(0, entity);
+ if(gettouch(player) == pc_PlayerTouch) return; // getting is cheaper than setting
+
+ settouch(player, pc_PlayerTouch);
+}
+#endif
--- /dev/null
+#pragma once
+
+#ifdef SVQC
+AUTOCVAR(g_player_crush, bool, false, "Allow crushing players by jumping on their head");
+AUTOCVAR(g_player_crush_simple, int, 1, "Use simple height checking");
+AUTOCVAR(g_player_crush_damage, float, 200, "");
+AUTOCVAR(g_player_crush_headheight, float, 0.9, "");
+AUTOCVAR(g_player_crush_bounce, float, 300, "Bounce height in advanced trace mode");
+AUTOCVAR(g_player_crush_bounce_jump, float, 600, "Bounce height while holding jump in advanced trace mode");
+#endif
\ No newline at end of file
MSG_CENTER_NOTIF(OVERTIME_CONTROLPOINT, N_ENABLE, 0, 0, "", CPID_OVERTIME, "5 0", _("^F2Now playing ^F4OVERTIME^F2!\n\nGenerators are now decaying.\nThe more control points your team holds,\nthe faster the enemy generator decays"), "")
MSG_CENTER_NOTIF(OVERTIME_TIME, N_ENABLE, 0, 1, "f1time", CPID_OVERTIME, "0 0", _("^F2Now playing ^F4OVERTIME^F2!\n^BGAdded ^F4%s^BG to the game!"), "")
+ //LegendGuard adds piggyback notifications, a feature from SMB mod 21-05-2021
+ MSG_CENTER_NOTIF(PIGGYBACK_CARRYING, N_ENABLE, 1, 0, "s1", CPID_PIGGYBACK, "0 0", "^BGYou are now carrying %s^BG!", "")
+ MSG_CENTER_NOTIF(PIGGYBACK_RIDING, N_ENABLE, 1, 0, "s1", CPID_PIGGYBACK, "0 0", "^BGYou are now riding %s^BG!", "")
+
MSG_CENTER_NOTIF(PORTO_CREATED_IN, N_ENABLE, 0, 0, "", CPID_Null, "0 0", _("^K1In^BG-portal created"), "")
MSG_CENTER_NOTIF(PORTO_CREATED_OUT, N_ENABLE, 0, 0, "", CPID_Null, "0 0", _("^F3Out^BG-portal created"), "")
MSG_CENTER_NOTIF(PORTO_FAILED, N_ENABLE, 0, 0, "", CPID_Null, "0 0", _("^F1Portal creation failed"), "")
return "";
}
//LegendGuard adds CASE(CPID, MMM) after TIMEIN for MMM 20-02-2021
+//LegendGuard adds CASE(CPID, PIGGYBACK) after OVERTIME for Piggyback 21-05-2021
ENUMCLASS(CPID)
CASE(CPID, ASSAULT_ROLE)
CASE(CPID, ROUND)
CASE(CPID, ONSLAUGHT)
CASE(CPID, ONS_CAPSHIELD)
CASE(CPID, OVERTIME)
+ CASE(CPID, PIGGYBACK)
CASE(CPID, POWERUP)
CASE(CPID, RACE_FINISHLAP)
CASE(CPID, TEAMCHANGE)
#include <server/main.qc>
#include <server/mapvoting.qc>
#include <server/matrix.qc>
+
+//SMB mods
+#include <server/mute.qc>
+//
+
#include <server/player.qc>
#include <server/portals.qc>
#include <server/race.qc>
// generated file; do not modify
#include <server/mutators/events.qc>
#include <server/mutators/loader.qc>
+
+//SMB mods
+#include <server/mutators/akimbo.qc>
+#include <server/mutators/piggyback.qc>
+#include <server/mutators/zombie_apocalypse.qc>
\ No newline at end of file
--- /dev/null
+//FEATURE: Akimbo mutator for all your dual wielding needs
+
+AUTOCVAR(g_akimbo, bool, false, "Enable Akimbo mutator (requires g_weaponswitch_debug 2)");
+AUTOCVAR(g_akimbo_atspawns, bool, false, "Allow Akimbo items to be collected from item spawnpoints, not just dropped items");
+
+REGISTER_MUTATOR(akimbo, autocvar_g_akimbo && !g_nexball && autocvar_g_weaponswitch_debug == 2);
+
+MUTATOR_HOOKFUNCTION(akimbo, ItemTouch)
+{
+ if(MUTATOR_RETURNVALUE == MUT_ITEMTOUCH_RETURN)
+ return false; // already handled, probably didn't pick it up
+
+ entity item = M_ARGV(0, entity);
+ entity player = M_ARGV(1, entity);
+
+ if((Item_IsLoot(item) || autocvar_g_akimbo_atspawns) && item.weapon &&
+ (STAT(WEAPONS, player) & item.itemdef.m_weapon.m_wepset) && item.owner != player && !(item.itemdef.m_weapon.spawnflags & WEP_FLAG_NODUAL))
+ PS(player).dual_weapons |= item.itemdef.m_weapon.m_wepset; // dual it up!
+}
+
+MUTATOR_HOOKFUNCTION(akimbo, FilterItem)
+{
+ entity item = M_ARGV(0, entity);
+
+ if((Item_IsLoot(item) || autocvar_g_akimbo_atspawns) && item.weapon && item.owner && IS_PLAYER(item.owner))
+ PS(item.owner).dual_weapons &= ~item.itemdef.m_weapon.m_wepset; // no longer akimbo'd (we don't check blacklist here, no need)
+}
--- /dev/null
+//FEATURE: Piggy-backing lets you ride your teammates to victory
+
+AUTOCVAR(g_piggyback, bool, false, "Enable piggyback mutator (riding teammates)");
+REGISTER_MUTATOR(piggyback, autocvar_g_piggyback);
+
+AUTOCVAR(g_piggyback_ride_enemies, bool, false, "Allow riding enemies");
+
+.bool cvar_cl_nocarry;
+.bool cvar_cl_noride;
+
+REPLICATE(cvar_cl_nocarry, bool, "cl_nocarry");
+REPLICATE(cvar_cl_noride, bool, "cl_noride");
+
+//ATTRIB(Client, cvar_cl_nocarry, bool, this.cvar_cl_nocarry);
+//ATTRIB(Client, cvar_cl_noride, bool, this.cvar_cl_noride);
+
+.entity piggybacker;
+.entity pbent;
+.entity pbhost;
+.float pb_canattach;
+//.float pb_attachblock;
+.float pb_oldsolid;
+
+#define BROKEN_PBREF(e) ((e).piggybacker && ((e).piggybacker.pbhost != (e) || (e).piggybacker.move_movetype != MOVETYPE_FOLLOW))
+
+void pb_Attach(entity host, entity slave);
+void pb_Detach(entity host);
+
+entity pb_TailOf(entity p);
+entity pb_RootOf(entity p);
+
+entity pb_TailOf(entity p)
+{
+ while(p.piggybacker)
+ p = p.piggybacker;
+ return p;
+}
+
+entity pb_RootOf(entity p)
+{
+ while(p.pbhost)
+ p = p.pbhost;
+ return p;
+}
+
+.float pb_oldmoveflags;
+.float pb_oldverticalfly;
+void pb_Attach(entity host, entity slave)
+{
+ entity root = pb_RootOf(host);
+ host = pb_TailOf(host);
+
+ if(host == slave || root == slave || host == slave.piggybacker || slave == host.piggybacker)
+ return;
+
+ host.piggybacker = slave;
+ set_movetype(slave, MOVETYPE_FOLLOW);
+ slave.aiment = host.pbent;
+ slave.pbhost = host;
+ slave.pb_oldsolid = slave.solid;
+ slave.solid = SOLID_CORPSE;
+ slave.pb_canattach = 0;
+
+ if(IS_MONSTER(host))
+ {
+ host.pb_oldmoveflags = host.monster_moveflags;
+ host.pb_oldverticalfly = (host.spawnflags & MONSTERFLAG_FLY_VERTICAL);
+ host.spawnflags |= MONSTERFLAG_FLY_VERTICAL;
+ host.monster_moveflags = MONSTER_MOVE_NOMOVE;
+ }
+
+ RemoveGrapplingHooks(slave);
+
+ Send_Notification(NOTIF_ONE, slave, MSG_CENTER, CENTER_PIGGYBACK_RIDING, (IS_MONSTER(host)) ? host.monster_name : host.netname);
+ if(IS_PLAYER(host))
+ Send_Notification(NOTIF_ONE, host, MSG_CENTER, CENTER_PIGGYBACK_CARRYING, slave.netname);
+}
+
+void pb_Detach(entity host)
+{
+ entity slave = host.piggybacker;
+
+ if(!slave)
+ return;
+
+ slave.aiment = NULL;
+ slave.pbhost = NULL;
+
+ if(IS_MONSTER(host))
+ {
+ host.monster_moveto = '0 0 0';
+ host.monster_moveflags = host.pb_oldmoveflags;
+ if(!host.pb_oldverticalfly) { host.spawnflags &= ~MONSTERFLAG_FLY_VERTICAL; }
+ }
+
+ if(IS_PLAYER(slave))
+ {
+ // this doesn't happen when we're fixing a broken reference
+
+ if(slave.move_movetype == MOVETYPE_FOLLOW) // don't reset if player was killed
+ set_movetype(slave, MOVETYPE_WALK);
+ slave.velocity = '0 0 0';
+ slave.solid = slave.pb_oldsolid;
+
+ tracebox(host.origin, slave.mins, slave.maxs, slave.origin, MOVE_NOMONSTERS, slave);
+
+ if(trace_fraction < 1)
+ setorigin(slave, trace_endpos); // fix player glitching out of the NULL
+ slave.oldorigin = slave.origin; // no fall damage!
+
+ Kill_Notification(NOTIF_ONE, slave, MSG_CENTER, CPID_PIGGYBACK);
+ if(IS_PLAYER(host))
+ Kill_Notification(NOTIF_ONE, host, MSG_CENTER, CPID_PIGGYBACK);
+ }
+
+ host.piggybacker = NULL;
+}
+
+void pb_PBEntThink(entity this)
+{
+ setorigin(this, this.owner.origin + '0 0 0.82' * this.owner.maxs_z);
+ this.nextthink = time;
+}
+
+void pb_FixPBEnt(entity p)
+{
+ entity e = new(pb_ent);
+ e.owner = p;
+ setthink(e, pb_PBEntThink);
+ e.nextthink = time;
+ p.pbent = e;
+}
+
+bool pb_GiveItem(entity to, entity item)
+{
+ if (Item_IsExpiring(item))
+ {
+ item.strength_finished = max(0, item.strength_finished - time);
+ item.invincible_finished = max(0, item.invincible_finished - time);
+ item.superweapons_finished = max(0, item.superweapons_finished - time);
+ }
+
+ entity it = item.itemdef;
+ bool gave = (it && it.instanceOfPickup) ? ITEM_HANDLE(Pickup, it, item, to) : Item_GiveTo(item, to);
+ if (!gave)
+ {
+ if (Item_IsExpiring(item))
+ {
+ // undo what we did above
+ item.strength_finished += time;
+ item.invincible_finished += time;
+ item.superweapons_finished += time;
+ }
+ if(!to.pbhost)
+ to = to.piggybacker; // increase it later
+ return false;
+ }
+
+ if (Item_IsExpiring(item))
+ {
+ // undo it anyway
+ item.strength_finished += time;
+ item.invincible_finished += time;
+ item.superweapons_finished += time;
+ }
+
+ STAT(LAST_PICKUP, to) = time;
+
+ _sound (to, CH_TRIGGER, item.item_pickupsound, VOL_BASE, ATTEN_NORM);
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(piggyback, MatchEnd)
+{
+ FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(pb_Detach(it)));
+ IL_EACH(g_monsters, true,
+ {
+ pb_Detach(it);
+ });
+ return false;
+}
+
+MUTATOR_HOOKFUNCTION(piggyback, PlayerUseKey)
+{
+ if(MUTATOR_RETURNVALUE || game_stopped) { return; }
+
+ entity player = M_ARGV(0, entity);
+
+ if(player.pbhost)
+ {
+ pb_Detach(player.pbhost);
+ return true;
+ }
+ if(player.piggybacker)
+ {
+ pb_Detach(player);
+ return true;
+ }
+
+#define LIMBO(v) (IS_DEAD(v) || STAT(FROZEN, v))
+
+ if(!CS_CVAR(player).cvar_cl_noride)
+ if(!player.vehicle && !IS_DEAD(player) && !STAT(FROZEN, player))
+ {
+ entity head, closest_target = NULL;
+
+ head = WarpZone_FindRadius(player.origin, autocvar_g_vehicles_enter_radius, true);
+ while(head)
+ {
+ if(IS_PLAYER(head) || (IS_MONSTER(head) && (head.monsterdef.spawnflags & MON_FLAG_RIDE)))
+ if(SAME_TEAM(head, player) || autocvar_g_piggyback_ride_enemies || (IS_MONSTER(head) && head.realowner == player))
+ if(head != player)
+ if(!CS_CVAR(head).cvar_cl_nocarry)
+ if(!IS_DEAD(head)) // we check for trapped status here, but better to be safe than sorry, considering the madness happening
+ if(!head.vehicle)
+ if((!LIMBO(head) && !LIMBO(player)) || (LIMBO(head) || LIMBO(player))) // your guess is as good as mine
+ {
+ if(closest_target)
+ {
+ if(vlen2(player.origin - head.origin) < vlen2(player.origin - closest_target.origin))
+ { closest_target = head; }
+ }
+ else { closest_target = head; }
+ }
+
+ head = head.chain;
+ }
+
+ if(closest_target)
+ if(IS_BOT_CLIENT(closest_target)) { pb_Attach(player, closest_target); return true; }
+ else { pb_Attach(closest_target, player); return true; }
+ }
+
+#undef LIMBO
+}
+
+MUTATOR_HOOKFUNCTION(piggyback, PlayerPreThink)
+{
+ entity player = M_ARGV(0, entity);
+
+ if(BROKEN_PBREF(player)) { pb_Detach(player); }
+
+ if(!player.pbent)
+ pb_FixPBEnt(player);
+
+ if(IS_MONSTER(player.pbhost))
+ if(CS(player).movement || PHYS_INPUT_BUTTON_JUMP(player) || PHYS_INPUT_BUTTON_CROUCH(player))
+ //if(!player.pbhost.enemy) // if player is trying to move, don't override
+ {
+ float forw, rit, updown = 0;
+ vector wishvel = '0 0 0';
+
+ vector forward, right, up;
+ MAKE_VECTORS(player.angles, forward, right, up);
+ if(PHYS_INPUT_BUTTON_JUMP(player))
+ updown = 300;
+ else if(PHYS_INPUT_BUTTON_CROUCH(player))
+ updown = -300;
+
+ if(CS(player).movement)
+ {
+ forw = CS(player).movement_x * 500;
+ rit = CS(player).movement_y * 500;
+ //updown = CS(player).movement_z * 100;
+
+ wishvel = forward * forw + right * rit;
+ }
+
+ //vector wishvel = normalize(('10 0 0' + v_forward * CS(player).movement_x) + ('0 10 0' + v_right * CS(player).movement_y) + ('0 0 1' * CS(player).movement_z));
+ //print(vtos(player.origin), vtos(player.origin + wishvel), "\n");
+ player.pbhost.monster_moveto = player.origin + wishvel;
+ player.pbhost.monster_moveto_z = up.z * updown;
+ }
+}
+
+MUTATOR_HOOKFUNCTION(piggyback, MonsterMove)
+{
+ entity mon = M_ARGV(0, entity);
+
+ if(BROKEN_PBREF(mon)) { pb_Detach(mon); }
+
+ if(!mon.pbent)
+ pb_FixPBEnt(mon);
+
+ if(mon.piggybacker)
+ {
+ mon.flags |= FL_PICKUPITEMS;
+ mon.last_trace = time;
+ }
+ else
+ mon.flags &= ~FL_PICKUPITEMS;
+}
+
+MUTATOR_HOOKFUNCTION(piggyback, reset_map_players)
+{
+ FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(pb_Detach(it)));
+}
+
+void pb_RemovePlayer(entity this)
+{
+ if(this.piggybacker)
+ {
+ this.piggybacker.pb_canattach = 1;
+ pb_Detach(this);
+ }
+
+ if(this.pbhost)
+ pb_Detach(this.pbhost);
+ this.pb_canattach = 0;
+
+ if(this.pbent)
+ {
+ delete(this.pbent);
+ this.pbent = NULL;
+ }
+}
+
+MUTATOR_HOOKFUNCTION(piggyback, MakePlayerObserver)
+{
+ entity player = M_ARGV(0, entity);
+
+ pb_RemovePlayer(player);
+}
+
+MUTATOR_HOOKFUNCTION(piggyback, ClientDisconnect)
+{
+ entity player = M_ARGV(0, entity);
+
+ pb_RemovePlayer(player);
+}
+
+void pb_PlayerDies(entity this)
+{
+ if(!this.pbhost && this.pb_canattach)
+ this.pb_canattach = 0;
+
+ if(this.piggybacker)
+ {
+ this.piggybacker.pb_canattach = 1;
+ pb_Detach(this);
+ }
+
+ if(this.pbhost)
+ pb_Detach(this.pbhost);
+ this.pb_canattach = 0;
+}
+
+MUTATOR_HOOKFUNCTION(piggyback, MonsterDies)
+{
+ entity frag_target = M_ARGV(0, entity);
+
+ pb_PlayerDies(frag_target);
+}
+
+MUTATOR_HOOKFUNCTION(piggyback, PlayerDies)
+{
+ entity frag_target = M_ARGV(2, entity);
+
+ pb_PlayerDies(frag_target);
+}
+
+MUTATOR_HOOKFUNCTION(piggyback, FixPlayermodel)
+{
+ entity player = M_ARGV(2, entity);
+
+ if(!player.pbhost) { return false; }
+
+ // this was the nearest hook to the original code
+ bool do_crouch = PHYS_INPUT_BUTTON_CROUCH(player);
+ if(player.hook_state
+ || player.vehicle
+ || STAT(FROZEN, player)
+ ) { do_crouch = false; }
+
+ if(!do_crouch && IS_DUCKED(player))
+ {
+ tracebox(player.origin, STAT(PL_MIN, player), STAT(PL_MAX, player), player.origin, false, player);
+ if (!trace_startsolid || player.pbhost)
+ {
+ UNSET_DUCKED(player);
+ player.view_ofs = STAT(PL_VIEW_OFS, player);
+ setsize (player, STAT(PL_MIN, player), STAT(PL_MAX, player));
+ }
+ }
+}
+
+MUTATOR_HOOKFUNCTION(piggyback, GrappleHookThink)
+{
+ entity hook_pullentity = M_ARGV(2, entity);
+
+ if(hook_pullentity.pbhost)
+ {
+ hook_pullentity = pb_RootOf(hook_pullentity);
+ M_ARGV(1, int) = 2; // enforce tarzan
+ }
+
+ M_ARGV(2, entity) = hook_pullentity;
+}
+
+MUTATOR_HOOKFUNCTION(piggyback, BuffModel_Customize)
+{
+ entity buff = M_ARGV(0, entity);
+ entity player = M_ARGV(1, entity);
+
+ if(player.pbhost == buff.owner || buff.owner.piggybacker)
+ return true; // don't show to piggybacker's carrier, and don't show if carrier is carrying someone else
+}
+
+
+MUTATOR_HOOKFUNCTION(piggyback, ItemTouch)
+{
+ entity item = M_ARGV(0, entity);
+ entity toucher = M_ARGV(1, entity);
+
+ if(IS_MONSTER(toucher))
+ {
+ entity p = toucher;
+ while(p.piggybacker)
+ {
+ p = p.piggybacker;
+ if(IS_PLAYER(p))
+ {
+ toucher = p;
+ break;
+ }
+ }
+
+ if(IS_MONSTER(toucher))
+ return MUT_ITEMTOUCH_RETURN; // make sure we're not allowing something nasty here
+ }
+
+ // give items to all piggybackers
+ // if we get here, the above stuff is out of the way
+
+ // we use pb_RootOf here, as magnet buff can give riders items, and it wouldn't be very nice if they hogged them
+ entity p = pb_RootOf(toucher);
+ if(IS_PLAYER(p))
+ toucher = p;
+ while(p.piggybacker)
+ {
+ p = p.piggybacker;
+ if(IS_PLAYER(p))
+ {
+ if(!pb_GiveItem(p, item))
+ continue;
+ }
+ }
+
+ M_ARGV(1, entity) = toucher;
+
+ return MUT_ITEMTOUCH_CONTINUE;
+}
+
+MUTATOR_HOOKFUNCTION(piggyback, BuffTouch)
+{
+ entity buff = M_ARGV(0, entity);
+ entity toucher = M_ARGV(1, entity);
+
+ if(IS_MONSTER(toucher))
+ if(toucher.piggybacker)
+ toucher = toucher.piggybacker;
+
+ if(STAT(BUFFS, toucher))
+ if(STAT(BUFFS, toucher) == STAT(BUFFS, buff) || !CS_CVAR(toucher).cvar_cl_buffs_autoreplace)
+ {
+ entity p = toucher;
+ while(p.piggybacker)
+ {
+ p = p.piggybacker;
+ if(!STAT(BUFFS, p) || (!p.piggybacker && (CS_CVAR(p).cvar_cl_buffs_autoreplace || STAT(BUFFS, p) != STAT(BUFFS, buff))))
+ {
+ toucher = p;
+ break;
+ }
+ }
+ }
+
+ M_ARGV(1, entity) = toucher;
+}
--- /dev/null
+//FEATURE: Zombie Apocalypse mutator, which spawns random monsters around the map constantly
+
+float za_spawn_delay;
+AUTOCVAR(g_za, bool, false, "Enable zombie apocalypse mutator");
+
+REGISTER_MUTATOR(za, autocvar_g_za && autocvar_g_monsters && !g_nexball && !g_invasion && !g_cts && !g_race)
+{
+ MUTATOR_ONADD
+ {
+ za_spawn_delay = time + game_starttime;
+ }
+}
+
+AUTOCVAR(g_za_max_monsters, int, 20, "");
+AUTOCVAR(g_za_spawnmonster, string, "zombie", "Spawn this type of monster, can be random or any of the mobs in 'spawnmob list'");
+AUTOCVAR(g_za_spawn_delay, float, 5, "");
+
+void za_SpawnMonster()
+{
+ if(game_stopped) { return; }
+
+ entity e = spawn();
+
+ if(MoveToRandomMapLocation(e, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, 10, 1024, 256))
+ {
+ e.angles = '0 0 0';
+ spawnmonster(e, autocvar_g_za_spawnmonster, MON_Null, NULL, NULL, e.origin, false, false, 2);
+ }
+}
+
+MUTATOR_HOOKFUNCTION(za, SV_StartFrame)
+{
+ if(time < za_spawn_delay || autocvar_g_za_max_monsters <= 0 || !autocvar_g_za)
+ return;
+
+ int n_monsters = 0, maxmon = autocvar_g_za_max_monsters;
+
+ // count dead monsters too (zombies)
+ IL_EACH(g_monsters, true,
+ {
+ ++n_monsters;
+ });
+
+ while(n_monsters < maxmon)
+ {
+ ++n_monsters;
+ za_SpawnMonster();
+ }
+
+ za_spawn_delay = time + autocvar_g_za_spawn_delay;
+}
--- /dev/null
+//FEATURE: Command to permanently mute troublesome players
+
+REGISTER_MUTATOR(muteban, true);
+
+AUTOCVAR(sv_muteban_list, string, "", "");
+
+MUTATOR_HOOKFUNCTION(muteban, ClientConnect)
+{
+ entity player = M_ARGV(0, entity);
+
+ if(PlayerInList(player, autocvar_sv_muteban_list))
+ CS(player).muted = true;
+}
+
+MUTATOR_HOOKFUNCTION(muteban, SV_ParseServerCommand)
+{
+ if(MUTATOR_RETURNVALUE) // command was already handled?
+ return false;
+
+ string cmd_name = M_ARGV(0, string);
+ int cmd_argc = M_ARGV(1, int);
+
+ if(cmd_name == "muteban")
+ {
+ entity client = GetIndexedEntity(cmd_argc, 1);
+ bool accepted = VerifyClientEntity(client, true, false);
+
+ if (accepted > 0)
+ {
+ string theid = "";
+ if(!PlayerInIPList(client, autocvar_sv_muteban_list))
+ theid = cons(theid, client.netaddress);
+ if(!PlayerInIDList(client, autocvar_sv_muteban_list))
+ theid = cons(theid, client.crypto_idfp);
+ CS(client).muted = true;
+ LOG_INFO(strcat("Mute-banning player ", GetCallerName(client), " (", argv(1), ")."));
+ cvar_set("sv_muteban_list", cons(autocvar_sv_muteban_list, theid));
+ }
+ else
+ {
+ LOG_INFO("mute-ban failed: ", GetClientErrorString(accepted, argv(1)), ".");
+ }
+
+ return true;
+ }
+
+ if(cmd_name == "unmuteban")
+ {
+ entity client = GetIndexedEntity(cmd_argc, 1);
+ bool accepted = VerifyClientEntity(client, true, false);
+ string original_arg = argv(1);
+
+ if (accepted > 0)
+ {
+ string tmp_string = "";
+ FOREACH_WORD(autocvar_sv_muteban_list, it != client.netaddress,
+ {
+ if(client.crypto_idfp && it == substring(client.crypto_idfp, 0, strlen(it)))
+ continue;
+ tmp_string = cons(tmp_string, it);
+ });
+
+ cvar_set("sv_muteban_list", tmp_string);
+ LOG_INFO(strcat("Unmuting player ", GetCallerName(client), " (", original_arg, ")."));
+ CS(client).muted = false;
+ }
+ else
+ {
+ LOG_INFO("mute-ban failed: ", GetClientErrorString(accepted, original_arg), ", attempting to unban by string.");
+
+ string tmp_string = "";
+ FOREACH_WORD(autocvar_sv_muteban_list, it != original_arg,
+ {
+ tmp_string = cons(tmp_string, it);
+ });
+
+ cvar_set("sv_muteban_list", tmp_string);
+ }
+ return true;
+ }
+}