From 0690c76a29bf987316ac1332ffb6513d6b65eaf2 Mon Sep 17 00:00:00 2001 From: LegendaryGuard Date: Fri, 21 May 2021 22:58:55 +0200 Subject: [PATCH] Added some SMB features, all players can wear hats, ride and stomp! --- qcsrc/common/_all.inc | 2 + qcsrc/common/cvars.qh | 17 + qcsrc/common/mutators/mutator/_mod.inc | 8 + qcsrc/common/mutators/mutator/_mod.qh | 8 + qcsrc/common/mutators/mutator/hats/_mod.inc | 2 + qcsrc/common/mutators/mutator/hats/_mod.qh | 2 + qcsrc/common/mutators/mutator/hats/hats.qc | 330 ++++++++++++ qcsrc/common/mutators/mutator/hats/hats.qh | 1 + .../mutators/mutator/player_crush/_mod.inc | 2 + .../mutators/mutator/player_crush/_mod.qh | 2 + .../mutator/player_crush/player_crush.qc | 140 +++++ .../mutator/player_crush/player_crush.qh | 10 + qcsrc/common/notifications/all.inc | 4 + qcsrc/common/notifications/all.qh | 2 + qcsrc/server/_mod.inc | 5 + qcsrc/server/mutators/_mod.inc | 5 + qcsrc/server/mutators/akimbo.qc | 27 + qcsrc/server/mutators/piggyback.qc | 479 ++++++++++++++++++ qcsrc/server/mutators/zombie_apocalypse.qc | 51 ++ qcsrc/server/mute.qc | 81 +++ 20 files changed, 1178 insertions(+) create mode 100644 qcsrc/common/cvars.qh create mode 100644 qcsrc/common/mutators/mutator/hats/_mod.inc create mode 100644 qcsrc/common/mutators/mutator/hats/_mod.qh create mode 100644 qcsrc/common/mutators/mutator/hats/hats.qc create mode 100644 qcsrc/common/mutators/mutator/hats/hats.qh create mode 100644 qcsrc/common/mutators/mutator/player_crush/_mod.inc create mode 100644 qcsrc/common/mutators/mutator/player_crush/_mod.qh create mode 100644 qcsrc/common/mutators/mutator/player_crush/player_crush.qc create mode 100644 qcsrc/common/mutators/mutator/player_crush/player_crush.qh create mode 100644 qcsrc/server/mutators/akimbo.qc create mode 100644 qcsrc/server/mutators/piggyback.qc create mode 100644 qcsrc/server/mutators/zombie_apocalypse.qc create mode 100644 qcsrc/server/mute.qc diff --git a/qcsrc/common/_all.inc b/qcsrc/common/_all.inc index bb090b99c..232de12ef 100644 --- a/qcsrc/common/_all.inc +++ b/qcsrc/common/_all.inc @@ -1,6 +1,8 @@ noref float autocvar_net_connecttimeout = 30; +//SMB mods #include "announcer.qc" +#include "cvars.qh" #ifdef GAMEQC #include "anim.qc" diff --git a/qcsrc/common/cvars.qh b/qcsrc/common/cvars.qh new file mode 100644 index 000000000..d572de19f --- /dev/null +++ b/qcsrc/common/cvars.qh @@ -0,0 +1,17 @@ +// 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__) diff --git a/qcsrc/common/mutators/mutator/_mod.inc b/qcsrc/common/mutators/mutator/_mod.inc index 1479a1dfa..1d8061419 100644 --- a/qcsrc/common/mutators/mutator/_mod.inc +++ b/qcsrc/common/mutators/mutator/_mod.inc @@ -12,6 +12,10 @@ #include #include #include + +//SMB mod feature Hats: +#include + #include #include #include @@ -27,6 +31,10 @@ #include #include #include + +//SMB mod feature Player crush: +#include + #include #include #include diff --git a/qcsrc/common/mutators/mutator/_mod.qh b/qcsrc/common/mutators/mutator/_mod.qh index 465495828..172b4a351 100644 --- a/qcsrc/common/mutators/mutator/_mod.qh +++ b/qcsrc/common/mutators/mutator/_mod.qh @@ -12,6 +12,10 @@ #include #include #include + +//SMB mod feature Hats: +#include + #include #include #include @@ -27,6 +31,10 @@ #include #include #include + +//SMB mod feature Player crush: +#include + #include #include #include diff --git a/qcsrc/common/mutators/mutator/hats/_mod.inc b/qcsrc/common/mutators/mutator/hats/_mod.inc new file mode 100644 index 000000000..46ca9fb6e --- /dev/null +++ b/qcsrc/common/mutators/mutator/hats/_mod.inc @@ -0,0 +1,2 @@ +// generated file; do not modify +#include \ No newline at end of file diff --git a/qcsrc/common/mutators/mutator/hats/_mod.qh b/qcsrc/common/mutators/mutator/hats/_mod.qh new file mode 100644 index 000000000..996095aa5 --- /dev/null +++ b/qcsrc/common/mutators/mutator/hats/_mod.qh @@ -0,0 +1,2 @@ +// generated file; do not modify +#include \ No newline at end of file diff --git a/qcsrc/common/mutators/mutator/hats/hats.qc b/qcsrc/common/mutators/mutator/hats/hats.qc new file mode 100644 index 000000000..359f6fc39 --- /dev/null +++ b/qcsrc/common/mutators/mutator/hats/hats.qc @@ -0,0 +1,330 @@ +//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 diff --git a/qcsrc/common/mutators/mutator/hats/hats.qh b/qcsrc/common/mutators/mutator/hats/hats.qh new file mode 100644 index 000000000..6f70f09be --- /dev/null +++ b/qcsrc/common/mutators/mutator/hats/hats.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mutators/mutator/player_crush/_mod.inc b/qcsrc/common/mutators/mutator/player_crush/_mod.inc new file mode 100644 index 000000000..f0fe6e8d2 --- /dev/null +++ b/qcsrc/common/mutators/mutator/player_crush/_mod.inc @@ -0,0 +1,2 @@ +// generated file; do not modify +#include \ No newline at end of file diff --git a/qcsrc/common/mutators/mutator/player_crush/_mod.qh b/qcsrc/common/mutators/mutator/player_crush/_mod.qh new file mode 100644 index 000000000..fbb085258 --- /dev/null +++ b/qcsrc/common/mutators/mutator/player_crush/_mod.qh @@ -0,0 +1,2 @@ +// generated file; do not modify +#include \ No newline at end of file diff --git a/qcsrc/common/mutators/mutator/player_crush/player_crush.qc b/qcsrc/common/mutators/mutator/player_crush/player_crush.qc new file mode 100644 index 000000000..af7824483 --- /dev/null +++ b/qcsrc/common/mutators/mutator/player_crush/player_crush.qc @@ -0,0 +1,140 @@ +//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 diff --git a/qcsrc/common/mutators/mutator/player_crush/player_crush.qh b/qcsrc/common/mutators/mutator/player_crush/player_crush.qh new file mode 100644 index 000000000..28d11a951 --- /dev/null +++ b/qcsrc/common/mutators/mutator/player_crush/player_crush.qh @@ -0,0 +1,10 @@ +#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 diff --git a/qcsrc/common/notifications/all.inc b/qcsrc/common/notifications/all.inc index 2d81dafff..2d59a6777 100644 --- a/qcsrc/common/notifications/all.inc +++ b/qcsrc/common/notifications/all.inc @@ -821,6 +821,10 @@ string multiteam_info_sprintf(string input, string teamname) { return ((input != 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"), "") diff --git a/qcsrc/common/notifications/all.qh b/qcsrc/common/notifications/all.qh index 809c1fc92..45f63b3f6 100644 --- a/qcsrc/common/notifications/all.qh +++ b/qcsrc/common/notifications/all.qh @@ -45,6 +45,7 @@ string Get_Notif_TypeName(MSG net_type) 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) @@ -72,6 +73,7 @@ ENUMCLASS(CPID) CASE(CPID, ONSLAUGHT) CASE(CPID, ONS_CAPSHIELD) CASE(CPID, OVERTIME) + CASE(CPID, PIGGYBACK) CASE(CPID, POWERUP) CASE(CPID, RACE_FINISHLAP) CASE(CPID, TEAMCHANGE) diff --git a/qcsrc/server/_mod.inc b/qcsrc/server/_mod.inc index 34ca1e2c8..588aa92bd 100644 --- a/qcsrc/server/_mod.inc +++ b/qcsrc/server/_mod.inc @@ -17,6 +17,11 @@ #include #include #include + +//SMB mods +#include +// + #include #include #include diff --git a/qcsrc/server/mutators/_mod.inc b/qcsrc/server/mutators/_mod.inc index 7b7cdf33d..bc8569325 100644 --- a/qcsrc/server/mutators/_mod.inc +++ b/qcsrc/server/mutators/_mod.inc @@ -1,3 +1,8 @@ // generated file; do not modify #include #include + +//SMB mods +#include +#include +#include \ No newline at end of file diff --git a/qcsrc/server/mutators/akimbo.qc b/qcsrc/server/mutators/akimbo.qc new file mode 100644 index 000000000..d07cab58d --- /dev/null +++ b/qcsrc/server/mutators/akimbo.qc @@ -0,0 +1,27 @@ +//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) +} diff --git a/qcsrc/server/mutators/piggyback.qc b/qcsrc/server/mutators/piggyback.qc new file mode 100644 index 000000000..f9b171ca3 --- /dev/null +++ b/qcsrc/server/mutators/piggyback.qc @@ -0,0 +1,479 @@ +//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; +} diff --git a/qcsrc/server/mutators/zombie_apocalypse.qc b/qcsrc/server/mutators/zombie_apocalypse.qc new file mode 100644 index 000000000..059b86f96 --- /dev/null +++ b/qcsrc/server/mutators/zombie_apocalypse.qc @@ -0,0 +1,51 @@ +//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; +} diff --git a/qcsrc/server/mute.qc b/qcsrc/server/mute.qc new file mode 100644 index 000000000..5c81d6b4d --- /dev/null +++ b/qcsrc/server/mute.qc @@ -0,0 +1,81 @@ +//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; + } +} -- 2.39.2