Add extra couch features
This commit is contained in:
+188
-33
@@ -63,6 +63,7 @@ static const char g_sPistols[][] =
|
||||
ConVar g_cvModel;
|
||||
ConVar g_cvPushScale;
|
||||
ConVar g_cvPistolOnly;
|
||||
ConVar g_cvAllowUse;
|
||||
ConVar g_cvEffect;
|
||||
ConVar g_cvEffectIntensity;
|
||||
ConVar g_cvIgnoreClip;
|
||||
@@ -70,11 +71,13 @@ ConVar g_cvCollisionGroup;
|
||||
|
||||
// Optional native from the UNLOZE playtime plugin; nobody is refused if it is absent.
|
||||
// The plugin reports tier -1 until a player's hours have loaded.
|
||||
ConVar g_cvTierRequirement;
|
||||
ConVar g_cvMoveTier;
|
||||
ConVar g_cvButtonTier;
|
||||
|
||||
GlobalForward g_fwdOnCouchSpawned;
|
||||
GlobalForward g_fwdOnCouchRemoved;
|
||||
GlobalForward g_fwdOnCouchDamaged;
|
||||
GlobalForward g_fwdOnCouchInteraction;
|
||||
GlobalForward g_fwdOnClientBlocked;
|
||||
|
||||
int g_iCouchRef = INVALID_ENT_REFERENCE;
|
||||
@@ -90,7 +93,7 @@ int g_iBeaconTick;
|
||||
|
||||
Handle g_hForceVPhysicsCollide;
|
||||
|
||||
// Couch bans: hits are refused in OnCouchTakeDamage, and the vphysics collision filter is
|
||||
// Couch bans: hits and +use are refused in OnCouchTakeDamage/OnCouchUse, and the vphysics collision filter is
|
||||
// detoured so a blocked player can't push it. The detour is only enabled while someone is blocked.
|
||||
Handle g_hShouldCollideDetour;
|
||||
Handle g_hCollisionRulesChanged;
|
||||
@@ -123,6 +126,8 @@ public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max
|
||||
CreateNative("Couch_SetEffectEnabled", Native_SetEffectEnabled);
|
||||
CreateNative("Couch_IsPistolOnly", Native_IsPistolOnly);
|
||||
CreateNative("Couch_SetPistolOnly", Native_SetPistolOnly);
|
||||
CreateNative("Couch_IsUseAllowed", Native_IsUseAllowed);
|
||||
CreateNative("Couch_SetUseAllowed", Native_SetUseAllowed);
|
||||
CreateNative("Couch_SetClientBlocked", Native_SetClientBlocked);
|
||||
CreateNative("Couch_IsClientBlocked", Native_IsClientBlocked);
|
||||
RegPluginLibrary("Couch");
|
||||
@@ -137,15 +142,18 @@ public void OnPluginStart()
|
||||
g_fwdOnCouchSpawned = new GlobalForward("Couch_OnCouchSpawned", ET_Ignore, Param_Cell);
|
||||
g_fwdOnCouchRemoved = new GlobalForward("Couch_OnCouchRemoved", ET_Ignore);
|
||||
g_fwdOnCouchDamaged = new GlobalForward("Couch_OnCouchDamaged", ET_Ignore, Param_Cell, Param_Cell, Param_Float);
|
||||
g_fwdOnCouchInteraction = new GlobalForward("Couch_OnCouchInteraction", ET_Ignore, Param_Cell, Param_Cell, Param_Cell, Param_String);
|
||||
g_fwdOnClientBlocked = new GlobalForward("Couch_OnClientBlocked", ET_Ignore, Param_Cell, Param_Cell);
|
||||
|
||||
g_cvModel = CreateConVar("sm_couch_model", COUCH_DEFAULT_MODEL, "Model used for the couch. Applies to the next spawn.", FCVAR_NOTIFY);
|
||||
g_cvPushScale = CreateConVar("sm_couch_push_scale", "1.0", "Multiplier on the force bullets apply to the couch. 1.0 is the engine's own push.", FCVAR_NOTIFY, true, 0.0, true, 100.0);
|
||||
g_cvPistolOnly = CreateConVar("sm_couch_pistol_only", "0", "Only pistols and knives move the couch; other guns and grenades are ignored.", FCVAR_NOTIFY, true, 0.0, true, 1.0);
|
||||
g_cvAllowUse = CreateConVar("sm_allow_couch_use", "1", "Players may +use the couch to pick it up. 0 means it can only be moved by shooting it.", FCVAR_NOTIFY, true, 0.0, true, 1.0);
|
||||
g_cvEffect = CreateConVar("sm_couch_effect", "0", "Colour cycle and pulsing light on the couch.", FCVAR_NOTIFY, true, 0.0, true, 1.0);
|
||||
g_cvEffectIntensity = CreateConVar("sm_couch_effect_intensity", "3", "Brightness of the couch effect light. 0 is off, 3 is the default, 8 is very bright. Applies immediately.", FCVAR_NOTIFY, true, 0.0, true, 10.0);
|
||||
g_cvIgnoreClip = CreateConVar("sm_couch_ignore_clip", "1", "Let the couch pass through func_clip_vphysics brushes.", FCVAR_NOTIFY, true, 0.0, true, 1.0);
|
||||
g_cvTierRequirement = CreateConVar("sm_couch_tier_requirement", "0", "Minimum playtime tier needed to shoot the couch.", FCVAR_NOTIFY, true, 0.0);
|
||||
g_cvMoveTier = CreateConVar("sm_couch_move_tier", "0", "Minimum playtime tier needed to shoot or +use the couch.", FCVAR_NOTIFY, true, 0.0);
|
||||
g_cvButtonTier = CreateConVar("sm_couch_button_tier", "0", "Minimum playtime tier needed to press or shoot map buttons (func_button).", FCVAR_NOTIFY, true, 0.0);
|
||||
g_cvCollisionGroup = CreateConVar("sm_couch_collision_group", "5", "Collision group given to the couch at spawn. 5 (player), 0 (prop)", FCVAR_NOTIFY, true, 0.0, true, 20.0);
|
||||
|
||||
g_cvModel.AddChangeHook(OnModelChanged);
|
||||
@@ -158,13 +166,17 @@ public void OnPluginStart()
|
||||
|
||||
LoadGameData();
|
||||
|
||||
// Late load: the clip brushes already exist, so hook them now.
|
||||
// Late load: the clip brushes and buttons already exist, so hook them now.
|
||||
int entity = -1;
|
||||
if (g_hForceVPhysicsCollide != null)
|
||||
{
|
||||
int entity = -1;
|
||||
while ((entity = FindEntityByClassname(entity, "func_clip_vphysics")) != -1)
|
||||
DHookEntity(g_hForceVPhysicsCollide, false, entity);
|
||||
}
|
||||
|
||||
entity = -1;
|
||||
while ((entity = FindEntityByClassname(entity, "func_button")) != -1)
|
||||
HookButton(entity);
|
||||
}
|
||||
|
||||
public void OnPluginEnd()
|
||||
@@ -189,9 +201,11 @@ public void OnMapStart()
|
||||
|
||||
public void OnEntityCreated(int entity, const char[] classname)
|
||||
{
|
||||
// The cvar is checked in the callback so it can be flipped at runtime.
|
||||
// The cvars are checked in the callbacks so they can be flipped at runtime.
|
||||
if (g_hForceVPhysicsCollide != null && StrEqual(classname, "func_clip_vphysics"))
|
||||
DHookEntity(g_hForceVPhysicsCollide, false, entity);
|
||||
else if (StrEqual(classname, "func_button"))
|
||||
HookButton(entity);
|
||||
}
|
||||
|
||||
public void OnEntityDestroyed(int entity)
|
||||
@@ -415,8 +429,9 @@ int SpawnCouchAt(const float vecOrigin[3], const float vecAngles[3])
|
||||
SetEntityFlags(couch, FL_CLIENT);
|
||||
SetEntProp(couch, Prop_Data, "m_iTeamNum", CS_TEAM_CT);
|
||||
|
||||
// SDKHooks drops the hook with the entity.
|
||||
// SDKHooks drops the hooks with the entity.
|
||||
SDKHook(couch, SDKHook_OnTakeDamage, OnCouchTakeDamage);
|
||||
SDKHook(couch, SDKHook_Use, OnCouchUse);
|
||||
|
||||
// Takes the beacon's colour modulation at full opacity.
|
||||
SetEntityRenderMode(couch, RENDER_NORMAL);
|
||||
@@ -462,22 +477,8 @@ public Action OnCouchTakeDamage(int victim, int &attacker, int &inflictor, float
|
||||
{
|
||||
bool bFromClient = (attacker >= 1 && attacker <= MaxClients && IsClientInGame(attacker));
|
||||
|
||||
// Couch-banned: the ban plugin gets a forward so it can tell them why.
|
||||
if (bFromClient && g_bBlocked[attacker])
|
||||
{
|
||||
Call_StartForward(g_fwdOnClientBlocked);
|
||||
Call_PushCell(victim);
|
||||
Call_PushCell(attacker);
|
||||
Call_Finish();
|
||||
|
||||
if (bFromClient && IsClientRefused(victim, attacker))
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
if (bFromClient && !MeetsTierRequirement(attacker))
|
||||
{
|
||||
PrintHintText(attacker, "You need to be tier %d or higher to shoot the couch.", g_cvTierRequirement.IntValue);
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
if (bFromClient && g_cvPistolOnly.BoolValue && !IsPistolOrKnifeHit(attacker, weapon, damagetype))
|
||||
return Plugin_Handled;
|
||||
@@ -498,15 +499,103 @@ public Action OnCouchTakeDamage(int victim, int &attacker, int &inflictor, float
|
||||
Call_PushCell(attacker);
|
||||
Call_PushFloat(damage);
|
||||
Call_Finish();
|
||||
|
||||
ReportHit(victim, attacker, inflictor, weapon, damagetype);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Without the playtime plugin nobody is refused.
|
||||
bool MeetsTierRequirement(int client)
|
||||
// +use on the couch: same ban and tier rules as shooting it, and off entirely with sm_allow_couch_use 0.
|
||||
// Plugin_Handled stops the pickup.
|
||||
public Action OnCouchUse(int entity, int activator, int caller, UseType type, float value)
|
||||
{
|
||||
if (activator < 1 || activator > MaxClients || !IsClientInGame(activator))
|
||||
return Plugin_Continue;
|
||||
|
||||
// Ban and tier first; their message is the more useful one.
|
||||
if (IsClientRefused(entity, activator))
|
||||
return Plugin_Handled;
|
||||
|
||||
if (!g_cvAllowUse.BoolValue)
|
||||
{
|
||||
PrintHintText(activator, "The couch can't be picked up. Shoot it to move it.");
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
ReportInteraction(entity, activator, CouchMethod_Use, "");
|
||||
return Plugin_Continue;
|
||||
}
|
||||
|
||||
// Works out how an accepted hit moved the couch. Skipped when nobody listens; it runs per pellet.
|
||||
void ReportHit(int couch, int attacker, int inflictor, int weapon, int damagetype)
|
||||
{
|
||||
if (g_fwdOnCouchInteraction.FunctionCount == 0)
|
||||
return;
|
||||
|
||||
char sWeapon[64];
|
||||
CouchMethod method = CouchMethod_Other;
|
||||
|
||||
if (damagetype & (DMG_SLASH | DMG_CLUB))
|
||||
{
|
||||
strcopy(sWeapon, sizeof(sWeapon), "knife");
|
||||
method = CouchMethod_Knife;
|
||||
}
|
||||
else if (damagetype & DMG_BULLET)
|
||||
{
|
||||
GetBulletWeapon(attacker, weapon, sWeapon, sizeof(sWeapon));
|
||||
ReplaceStringEx(sWeapon, sizeof(sWeapon), "weapon_", "");
|
||||
method = StrEqual(sWeapon, "knife") ? CouchMethod_Knife : CouchMethod_Bullet;
|
||||
}
|
||||
else if (inflictor > MaxClients && IsValidEntity(inflictor))
|
||||
{
|
||||
// Blast from anything but a grenade (env_explosion and the like) stays Other.
|
||||
GetEntityClassname(inflictor, sWeapon, sizeof(sWeapon));
|
||||
if ((damagetype & DMG_BLAST) && StrEqual(sWeapon, "hegrenade_projectile"))
|
||||
{
|
||||
strcopy(sWeapon, sizeof(sWeapon), "hegrenade");
|
||||
method = CouchMethod_Grenade;
|
||||
}
|
||||
}
|
||||
|
||||
ReportInteraction(couch, attacker, method, sWeapon);
|
||||
}
|
||||
|
||||
void ReportInteraction(int couch, int client, CouchMethod method, const char[] sWeapon)
|
||||
{
|
||||
Call_StartForward(g_fwdOnCouchInteraction);
|
||||
Call_PushCell(couch);
|
||||
Call_PushCell(client);
|
||||
Call_PushCell(method);
|
||||
Call_PushString(sWeapon);
|
||||
Call_Finish();
|
||||
}
|
||||
|
||||
// Couch ban or tier requirement. Tells the client why, or for a ban lets the ban plugin do so.
|
||||
bool IsClientRefused(int couch, int client)
|
||||
{
|
||||
if (g_bBlocked[client])
|
||||
{
|
||||
Call_StartForward(g_fwdOnClientBlocked);
|
||||
Call_PushCell(couch);
|
||||
Call_PushCell(client);
|
||||
Call_Finish();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!MeetsTier(client, g_cvMoveTier.IntValue))
|
||||
{
|
||||
PrintHintText(client, "You need to be tier %d or higher to move the couch.", g_cvMoveTier.IntValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Without the playtime plugin nobody is refused.
|
||||
bool MeetsTier(int client, int required)
|
||||
{
|
||||
int required = g_cvTierRequirement.IntValue;
|
||||
if (required <= 0)
|
||||
return true;
|
||||
|
||||
@@ -526,15 +615,9 @@ bool IsPistolOrKnifeHit(int attacker, int weapon, int damagetype)
|
||||
if (!(damagetype & DMG_BULLET))
|
||||
return false;
|
||||
|
||||
// CS:S rarely names the weapon in bullet damage, so fall back to the active weapon.
|
||||
if (weapon <= MaxClients || !IsValidEntity(weapon))
|
||||
weapon = GetEntPropEnt(attacker, Prop_Send, "m_hActiveWeapon");
|
||||
|
||||
if (weapon == -1)
|
||||
return false;
|
||||
|
||||
char sClass[32];
|
||||
GetEntityClassname(weapon, sClass, sizeof(sClass));
|
||||
if (!GetBulletWeapon(attacker, weapon, sClass, sizeof(sClass)))
|
||||
return false;
|
||||
|
||||
// In case a knife hit ever arrives typed as a bullet.
|
||||
if (StrEqual(sClass, "weapon_knife"))
|
||||
@@ -549,6 +632,67 @@ bool IsPistolOrKnifeHit(int attacker, int weapon, int damagetype)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Classname of the weapon behind a bullet hit. CS:S rarely names it in bullet damage, so this
|
||||
// falls back to the active weapon. Returns false, with an empty buffer, if there is none.
|
||||
bool GetBulletWeapon(int attacker, int weapon, char[] buffer, int maxlen)
|
||||
{
|
||||
if (weapon <= MaxClients || !IsValidEntity(weapon))
|
||||
weapon = GetEntPropEnt(attacker, Prop_Send, "m_hActiveWeapon");
|
||||
|
||||
if (weapon == -1)
|
||||
{
|
||||
buffer[0] = '\0';
|
||||
return false;
|
||||
}
|
||||
|
||||
GetEntityClassname(weapon, buffer, maxlen);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
// Buttons
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
|
||||
// func_button spawnflag: shooting the button presses it.
|
||||
#define SF_BUTTON_DAMAGE_ACTIVATES 512
|
||||
|
||||
void HookButton(int button)
|
||||
{
|
||||
SDKHook(button, SDKHook_Use, OnButtonUse);
|
||||
SDKHook(button, SDKHook_OnTakeDamage, OnButtonTakeDamage);
|
||||
}
|
||||
|
||||
// A player's own +use has them as both activator and caller. A Press input relayed from map logic
|
||||
// has the firing entity as caller, so !activator chains through triggers still work.
|
||||
public Action OnButtonUse(int entity, int activator, int caller, UseType type, float value)
|
||||
{
|
||||
if (activator < 1 || activator > MaxClients || activator != caller || !IsClientInGame(activator))
|
||||
return Plugin_Continue;
|
||||
|
||||
return IsClientRefusedButton(activator) ? Plugin_Handled : Plugin_Continue;
|
||||
}
|
||||
|
||||
// Only buttons that shooting presses; hits on any other func_button are left alone.
|
||||
public Action OnButtonTakeDamage(int victim, int &attacker, int &inflictor, float &damage, int &damagetype, int &weapon, float damageForce[3], float damagePosition[3], int damagecustom)
|
||||
{
|
||||
if (attacker < 1 || attacker > MaxClients || !IsClientInGame(attacker))
|
||||
return Plugin_Continue;
|
||||
|
||||
if (!(GetEntProp(victim, Prop_Data, "m_spawnflags") & SF_BUTTON_DAMAGE_ACTIVATES))
|
||||
return Plugin_Continue;
|
||||
|
||||
return IsClientRefusedButton(attacker) ? Plugin_Handled : Plugin_Continue;
|
||||
}
|
||||
|
||||
bool IsClientRefusedButton(int client)
|
||||
{
|
||||
if (MeetsTier(client, g_cvButtonTier.IntValue))
|
||||
return false;
|
||||
|
||||
PrintHintText(client, "You need to be tier %d or higher to press buttons.", g_cvButtonTier.IntValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
// func_clip_vphysics
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
@@ -962,6 +1106,17 @@ public any Native_SetPistolOnly(Handle plugin, int numParams)
|
||||
return 0;
|
||||
}
|
||||
|
||||
public any Native_IsUseAllowed(Handle plugin, int numParams)
|
||||
{
|
||||
return g_cvAllowUse.BoolValue;
|
||||
}
|
||||
|
||||
public any Native_SetUseAllowed(Handle plugin, int numParams)
|
||||
{
|
||||
g_cvAllowUse.SetBool(view_as<bool>(GetNativeCell(1)));
|
||||
return 0;
|
||||
}
|
||||
|
||||
public any Native_SetClientBlocked(Handle plugin, int numParams)
|
||||
{
|
||||
int client = GetNativeCell(1);
|
||||
|
||||
Reference in New Issue
Block a user