Couch changes
This commit is contained in:
+550
-31
@@ -8,6 +8,11 @@
|
||||
#define COUCH_CORE
|
||||
#include <Couch>
|
||||
|
||||
// Optional: without Zombie:Reloaded nobody counts as a zombie, so couch HP never changes.
|
||||
#undef REQUIRE_PLUGIN
|
||||
#include <zombiereloaded>
|
||||
#define REQUIRE_PLUGIN
|
||||
|
||||
#pragma semicolon 1
|
||||
#pragma newdecls required
|
||||
|
||||
@@ -28,8 +33,8 @@
|
||||
#define COUCH_TEXT_HOLD "8.0"
|
||||
#define COUCH_TEXT_COLOUR "255 144 32"
|
||||
|
||||
// Beacon timer interval, ticks per colour, ticks per light pulse.
|
||||
#define COUCH_BEACON_INTERVAL 0.1
|
||||
// Effect timer interval, ticks per colour, ticks per light pulse.
|
||||
#define COUCH_EFFECT_INTERVAL 0.1
|
||||
#define COUCH_COLOUR_TICKS 4
|
||||
#define COUCH_PULSE_TICKS 10
|
||||
|
||||
@@ -38,6 +43,17 @@
|
||||
#define COUCH_LIGHT_RADIUS_MIN 150.0
|
||||
#define COUCH_LIGHT_RADIUS_MAX 320.0
|
||||
|
||||
// Beacon, after sm_beacon: two beam rings a second from just above the couch's origin. No sound.
|
||||
#define COUCH_BEACON_INTERVAL 1.0
|
||||
#define COUCH_BEACON_HEIGHT 10.0
|
||||
#define COUCH_BEACON_SPRITE_BEAM "sprites/laser.vmt"
|
||||
#define COUCH_BEACON_SPRITE_HALO "sprites/halo01.vmt"
|
||||
|
||||
// Outer ring is sm_beacon's grey; the inner ring is the couch's own orange, or the effect's
|
||||
// current colour while the effect is on.
|
||||
static const int g_iBeaconOuterColour[4] = { 128, 128, 128, 255 };
|
||||
static const int g_iBeaconInnerColour[4] = { 255, 144, 32, 255 };
|
||||
|
||||
static const int g_iCouchColours[][3] =
|
||||
{
|
||||
{ 255, 48, 48 }, // red
|
||||
@@ -49,6 +65,39 @@ static const int g_iCouchColours[][3] =
|
||||
{ 255, 80, 224 }, // magenta
|
||||
};
|
||||
|
||||
// Couch HP: regen is checked this often while the couch is below full.
|
||||
#define COUCH_REGEN_INTERVAL 1.0
|
||||
|
||||
// Stock sounds, so they only need precaching. A zombie hit plays one of the first set at the couch;
|
||||
// the break plays one of each of the other two, the explosion for everyone regardless of distance.
|
||||
static const char g_sHitSounds[][] =
|
||||
{
|
||||
"physics/wood/wood_furniture_break1.wav",
|
||||
"physics/wood/wood_furniture_break2.wav",
|
||||
"physics/wood/wood_box_break1.wav",
|
||||
"physics/wood/wood_box_break2.wav",
|
||||
"physics/wood/wood_plank_break1.wav",
|
||||
"physics/wood/wood_plank_break2.wav",
|
||||
"physics/wood/wood_plank_break3.wav",
|
||||
"physics/wood/wood_plank_break4.wav",
|
||||
};
|
||||
|
||||
static const char g_sBreakSounds[][] =
|
||||
{
|
||||
"physics/wood/wood_crate_break1.wav",
|
||||
"physics/wood/wood_crate_break2.wav",
|
||||
"physics/wood/wood_crate_break3.wav",
|
||||
"physics/wood/wood_crate_break4.wav",
|
||||
"physics/wood/wood_crate_break5.wav",
|
||||
};
|
||||
|
||||
static const char g_sExplodeSounds[][] =
|
||||
{
|
||||
"weapons/hegrenade/explode3.wav",
|
||||
"weapons/hegrenade/explode4.wav",
|
||||
"weapons/hegrenade/explode5.wav",
|
||||
};
|
||||
|
||||
// Allowed to move the couch in pistol-only mode, along with the knife.
|
||||
static const char g_sPistols[][] =
|
||||
{
|
||||
@@ -66,6 +115,8 @@ ConVar g_cvPistolOnly;
|
||||
ConVar g_cvAllowUse;
|
||||
ConVar g_cvEffect;
|
||||
ConVar g_cvEffectIntensity;
|
||||
ConVar g_cvBeacon;
|
||||
ConVar g_cvBeaconRadius;
|
||||
ConVar g_cvIgnoreClip;
|
||||
ConVar g_cvCollisionGroup;
|
||||
|
||||
@@ -74,11 +125,26 @@ ConVar g_cvCollisionGroup;
|
||||
ConVar g_cvMoveTier;
|
||||
ConVar g_cvButtonTier;
|
||||
|
||||
// Couch HP. Only zombie hits lower it; it is kept here rather than in the entity's health so the
|
||||
// engine never breaks the prop itself.
|
||||
ConVar g_cvHp;
|
||||
ConVar g_cvHpMax;
|
||||
ConVar g_cvHpRegenDelay;
|
||||
ConVar g_cvHpRegen;
|
||||
|
||||
GlobalForward g_fwdOnCouchSpawned;
|
||||
GlobalForward g_fwdOnCouchRemoved;
|
||||
GlobalForward g_fwdOnCouchDamaged;
|
||||
GlobalForward g_fwdOnCouchInteraction;
|
||||
GlobalForward g_fwdOnClientBlocked;
|
||||
GlobalForward g_fwdOnCouchHpChanged;
|
||||
GlobalForward g_fwdOnCouchBroken;
|
||||
|
||||
int g_iHp;
|
||||
float g_fLastZombieHit; // GetGameTime() of the last hit that lowered the HP; regen waits on it.
|
||||
Handle g_hRegenTimer;
|
||||
bool g_bBroken; // Set by the breaking hit so the rest of its pellets, and anything else until the next spawn, is ignored.
|
||||
bool g_bZRLoaded;
|
||||
|
||||
int g_iCouchRef = INVALID_ENT_REFERENCE;
|
||||
int g_iLightRef = INVALID_ENT_REFERENCE;
|
||||
@@ -88,8 +154,19 @@ bool g_bPlaced;
|
||||
float g_vecPlaceOrigin[3];
|
||||
float g_vecPlaceAngles[3];
|
||||
|
||||
Handle g_hEffectTimer;
|
||||
int g_iEffectTick;
|
||||
|
||||
// Colour the effect last painted the couch; the beacon's inner ring follows it.
|
||||
int g_iEffectColour[4] = { 255, 255, 255, 255 };
|
||||
|
||||
Handle g_hBeaconTimer;
|
||||
int g_iBeaconTick;
|
||||
int g_iBeaconBeamSprite = -1;
|
||||
int g_iBeaconHaloSprite = -1;
|
||||
|
||||
// Accepted hits this round, announced at round end. Shotguns count once per pellet.
|
||||
int g_iRoundHits;
|
||||
float g_fRoundDamage;
|
||||
|
||||
Handle g_hForceVPhysicsCollide;
|
||||
|
||||
@@ -127,12 +204,20 @@ public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max
|
||||
CreateNative("Couch_Remove", Native_Remove);
|
||||
CreateNative("Couch_IsEffectEnabled", Native_IsEffectEnabled);
|
||||
CreateNative("Couch_SetEffectEnabled", Native_SetEffectEnabled);
|
||||
CreateNative("Couch_IsBeaconEnabled", Native_IsBeaconEnabled);
|
||||
CreateNative("Couch_SetBeaconEnabled", Native_SetBeaconEnabled);
|
||||
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);
|
||||
CreateNative("Couch_IsHpEnabled", Native_IsHpEnabled);
|
||||
CreateNative("Couch_SetHpEnabled", Native_SetHpEnabled);
|
||||
CreateNative("Couch_GetHp", Native_GetHp);
|
||||
CreateNative("Couch_SetHp", Native_SetHp);
|
||||
CreateNative("Couch_GetMaxHp", Native_GetMaxHp);
|
||||
CreateNative("Couch_SetMaxHp", Native_SetMaxHp);
|
||||
RegPluginLibrary("Couch");
|
||||
|
||||
MarkNativeAsOptional("GetPlayerTier_native");
|
||||
@@ -147,6 +232,8 @@ public void OnPluginStart()
|
||||
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_fwdOnCouchHpChanged = new GlobalForward("Couch_OnCouchHpChanged", ET_Ignore, Param_Cell, Param_Cell, Param_Cell);
|
||||
g_fwdOnCouchBroken = new GlobalForward("Couch_OnCouchBroken", 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", "6.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);
|
||||
@@ -154,18 +241,28 @@ public void OnPluginStart()
|
||||
g_cvAllowUse = CreateConVar("sm_allow_couch_use", "1", "Players may push the couch by holding +use on it. 0 means it can only be moved by shooting it.", FCVAR_NOTIFY, true, 0.0, true, 1.0);
|
||||
g_cvEffect = CreateConVar("sm_couch_effect", "1", "Colour cycle and pulsing light on the couch.", FCVAR_NOTIFY, true, 0.0, true, 1.0);
|
||||
g_cvEffectIntensity = CreateConVar("sm_couch_effect_intensity", "2", "Brightness of the couch effect light. 0 is off, 2 is the default, 8 is very bright. Applies immediately.", FCVAR_NOTIFY, true, 0.0, true, 10.0);
|
||||
g_cvBeacon = CreateConVar("sm_couch_beacon", "0", "Beacon on the couch, like sm_beacon: expanding rings every second.", FCVAR_NOTIFY, true, 0.0, true, 1.0);
|
||||
g_cvBeaconRadius = CreateConVar("sm_couch_beacon_radius", "375", "Radius of the couch beacon's rings.", FCVAR_NOTIFY, true, 50.0, true, 1500.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_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_cvHp = CreateConVar("sm_couch_hp", "1", "Couch HP: zombie hits wear the couch down and at 0 HP it breaks, killing every human.", FCVAR_NOTIFY, true, 0.0, true, 1.0);
|
||||
g_cvHpMax = CreateConVar("sm_couch_hp_max", "4000", "Maximum couch HP. The couch spawns with this much every round.", FCVAR_NOTIFY, true, 1.0);
|
||||
g_cvHpRegenDelay = CreateConVar("sm_couch_hp_regen_delay", "10","Seconds without a zombie hit before the couch starts regenerating HP.", FCVAR_NOTIFY, true, 0.0);
|
||||
g_cvHpRegen = CreateConVar("sm_couch_hp_regen", "25", "HP the couch regenerates per second once the regen delay has passed.", FCVAR_NOTIFY, true, 0.0);
|
||||
|
||||
g_cvModel.AddChangeHook(OnModelChanged);
|
||||
g_cvEffect.AddChangeHook(OnEffectChanged);
|
||||
g_cvEffectIntensity.AddChangeHook(OnEffectIntensityChanged);
|
||||
g_cvBeacon.AddChangeHook(OnBeaconChanged);
|
||||
g_cvHp.AddChangeHook(OnHpEnabledChanged);
|
||||
g_cvHpMax.AddChangeHook(OnHpMaxChanged);
|
||||
|
||||
AutoExecConfig(true, "Couch");
|
||||
|
||||
HookEvent("round_start", Event_RoundStart, EventHookMode_Post);
|
||||
HookEvent("round_end", Event_RoundEnd, EventHookMode_Post);
|
||||
|
||||
g_cvTurboPhysics = FindConVar("sv_turbophysics");
|
||||
|
||||
@@ -196,16 +293,42 @@ public void OnPluginEnd()
|
||||
KillCouch();
|
||||
}
|
||||
|
||||
public void OnAllPluginsLoaded()
|
||||
{
|
||||
g_bZRLoaded = LibraryExists("zombiereloaded");
|
||||
}
|
||||
|
||||
public void OnLibraryAdded(const char[] name)
|
||||
{
|
||||
if (StrEqual(name, "zombiereloaded"))
|
||||
g_bZRLoaded = true;
|
||||
}
|
||||
|
||||
public void OnLibraryRemoved(const char[] name)
|
||||
{
|
||||
if (StrEqual(name, "zombiereloaded"))
|
||||
g_bZRLoaded = false;
|
||||
}
|
||||
|
||||
public void OnMapStart()
|
||||
{
|
||||
PrecacheCouchModel();
|
||||
PrecacheBeacon();
|
||||
PrecacheHpSounds();
|
||||
|
||||
// Entities and no-mapchange timers went with the map.
|
||||
g_iCouchRef = INVALID_ENT_REFERENCE;
|
||||
g_iLightRef = INVALID_ENT_REFERENCE;
|
||||
g_bPlaced = false;
|
||||
g_hEffectTimer = null;
|
||||
g_iEffectTick = 0;
|
||||
g_hBeaconTimer = null;
|
||||
g_iBeaconTick = 0;
|
||||
g_iRoundHits = 0;
|
||||
g_fRoundDamage = 0.0;
|
||||
g_hRegenTimer = null;
|
||||
g_iHp = 0;
|
||||
g_fLastZombieHit = 0.0;
|
||||
g_bBroken = false;
|
||||
|
||||
ResetBlockedClients();
|
||||
}
|
||||
@@ -228,7 +351,9 @@ public void OnEntityDestroyed(int entity)
|
||||
// until the next round start.
|
||||
g_iCouchRef = INVALID_ENT_REFERENCE;
|
||||
KillLight();
|
||||
StopEffect();
|
||||
StopBeacon();
|
||||
StopRegen();
|
||||
|
||||
Call_StartForward(g_fwdOnCouchRemoved);
|
||||
Call_Finish();
|
||||
@@ -250,6 +375,16 @@ public void OnClientDisconnect(int client)
|
||||
|
||||
public void Event_RoundStart(Event event, const char[] name, bool dontBroadcast)
|
||||
{
|
||||
g_iRoundHits = 0;
|
||||
g_fRoundDamage = 0.0;
|
||||
|
||||
// A couch that survived the restart starts the round at full HP too.
|
||||
if (GetCouchIndex() != -1)
|
||||
{
|
||||
ResetHp();
|
||||
UpdateRegen();
|
||||
}
|
||||
|
||||
if (!g_bPlaced || GetCouchIndex() != -1)
|
||||
return;
|
||||
|
||||
@@ -260,13 +395,33 @@ public void Event_RoundStart(Event event, const char[] name, bool dontBroadcast)
|
||||
CreateTimer(COUCH_ANNOUNCE_DELAY, Timer_AnnounceCouchRound, _, TIMER_FLAG_NO_MAPCHANGE);
|
||||
}
|
||||
|
||||
// Only for rounds that had a couch: placed, or spawned mid-round and shot.
|
||||
public void Event_RoundEnd(Event event, const char[] name, bool dontBroadcast)
|
||||
{
|
||||
if (!g_bPlaced && g_iRoundHits == 0)
|
||||
return;
|
||||
|
||||
if (g_iRoundHits == 0)
|
||||
{
|
||||
PrintToChatAll(PREFIX ... "\x01The couch took \x04no damage\x01 this round.");
|
||||
return;
|
||||
}
|
||||
|
||||
char sDamage[16];
|
||||
char sHits[16];
|
||||
FormatThousands(RoundToNearest(g_fRoundDamage), sDamage, sizeof(sDamage));
|
||||
FormatThousands(g_iRoundHits, sHits, sizeof(sHits));
|
||||
|
||||
PrintToChatAll(PREFIX ... "\x01The couch took \x04%s damage\x01 from \x04%s hit%s\x01 this round.", sDamage, sHits, (g_iRoundHits == 1) ? "" : "s");
|
||||
}
|
||||
|
||||
public Action Timer_AnnounceCouchRound(Handle timer)
|
||||
{
|
||||
if (GetCouchIndex() == -1)
|
||||
return Plugin_Stop;
|
||||
|
||||
ShowCouchRoundText();
|
||||
PrintToChatAll("\x01[SM] \x04Couch round!\x01 Humans must bring the couch with them throughout the map or they fail the round.");
|
||||
PrintToChatAll(PREFIX ... "Couch round!\x01 Humans must bring the couch with them throughout the map or they fail the round.");
|
||||
|
||||
return Plugin_Stop;
|
||||
}
|
||||
@@ -450,12 +605,14 @@ int SpawnCouchAt(const float vecOrigin[3], const float vecAngles[3])
|
||||
// SDKHooks drops the hooks with the entity.
|
||||
SDKHook(couch, SDKHook_OnTakeDamage, OnCouchTakeDamage);
|
||||
|
||||
// Takes the beacon's colour modulation at full opacity.
|
||||
// Takes the effect's colour modulation at full opacity.
|
||||
SetEntityRenderMode(couch, RENDER_NORMAL);
|
||||
|
||||
g_iCouchRef = EntIndexToEntRef(couch);
|
||||
|
||||
ResetHp();
|
||||
ApplyEffectState();
|
||||
ApplyBeaconState();
|
||||
|
||||
Call_StartForward(g_fwdOnCouchSpawned);
|
||||
Call_PushCell(couch);
|
||||
@@ -471,7 +628,9 @@ bool KillCouch()
|
||||
|
||||
// Cleared first so OnEntityDestroyed doesn't run the external-removal path too.
|
||||
g_iCouchRef = INVALID_ENT_REFERENCE;
|
||||
StopEffect();
|
||||
StopBeacon();
|
||||
StopRegen();
|
||||
KillLight();
|
||||
|
||||
if (couch == -1)
|
||||
@@ -494,11 +653,33 @@ public Action OnCouchTakeDamage(int victim, int &attacker, int &inflictor, float
|
||||
{
|
||||
bool bFromClient = (attacker >= 1 && attacker <= MaxClients && IsClientInGame(attacker));
|
||||
|
||||
if (bFromClient && IsClientRefused(victim, attacker))
|
||||
return Plugin_Handled;
|
||||
// With couch HP on, a zombie's hit always costs HP: couch bans, the tier and pistol-only mode don't
|
||||
// stop it. A banned zombie's hit still counts and is reported, but is refused at the end so it
|
||||
// doesn't move the couch.
|
||||
bool bRefusePush = false;
|
||||
|
||||
if (bFromClient && g_cvPistolOnly.BoolValue && !IsPistolOrKnifeHit(attacker, weapon, damagetype))
|
||||
return Plugin_Handled;
|
||||
if (bFromClient && g_cvHp.BoolValue && IsZombie(attacker))
|
||||
{
|
||||
if (g_bBroken)
|
||||
return Plugin_Handled;
|
||||
|
||||
if (ApplyZombieHit(victim, attacker, damage))
|
||||
return Plugin_Handled;
|
||||
|
||||
bRefusePush = g_bBlocked[attacker];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (bFromClient && IsClientRefused(victim, attacker))
|
||||
return Plugin_Handled;
|
||||
|
||||
if (bFromClient && g_cvPistolOnly.BoolValue && !IsPistolOrKnifeHit(attacker, weapon, damagetype))
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
// Counted only once the hit is accepted, so refused shots don't show in the round total.
|
||||
g_iRoundHits++;
|
||||
g_fRoundDamage += damage;
|
||||
|
||||
Action result = Plugin_Continue;
|
||||
|
||||
@@ -520,7 +701,210 @@ public Action OnCouchTakeDamage(int victim, int &attacker, int &inflictor, float
|
||||
ReportHit(victim, attacker, inflictor, weapon, damagetype);
|
||||
}
|
||||
|
||||
return result;
|
||||
return bRefusePush ? Plugin_Handled : result;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
// Couch HP
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
|
||||
bool IsZombie(int client)
|
||||
{
|
||||
return g_bZRLoaded && ZR_IsClientZombie(client);
|
||||
}
|
||||
|
||||
void PrecacheHpSounds()
|
||||
{
|
||||
for (int i = 0; i < sizeof(g_sHitSounds); i++)
|
||||
PrecacheSound(g_sHitSounds[i], true);
|
||||
|
||||
for (int i = 0; i < sizeof(g_sBreakSounds); i++)
|
||||
PrecacheSound(g_sBreakSounds[i], true);
|
||||
|
||||
for (int i = 0; i < sizeof(g_sExplodeSounds); i++)
|
||||
PrecacheSound(g_sExplodeSounds[i], true);
|
||||
}
|
||||
|
||||
public void OnHpEnabledChanged(ConVar convar, const char[] oldValue, const char[] newValue)
|
||||
{
|
||||
if (convar.BoolValue)
|
||||
{
|
||||
ResetHp();
|
||||
UpdateRegen();
|
||||
return;
|
||||
}
|
||||
|
||||
// Announced so the HUD drops its HP line.
|
||||
StopRegen();
|
||||
FireHpChanged();
|
||||
}
|
||||
|
||||
// A full couch stays full; anything else is clamped to the new maximum.
|
||||
public void OnHpMaxChanged(ConVar convar, const char[] oldValue, const char[] newValue)
|
||||
{
|
||||
int iOldMax = StringToInt(oldValue);
|
||||
int iNewMax = convar.IntValue;
|
||||
|
||||
if (g_iHp >= iOldMax || g_iHp > iNewMax)
|
||||
g_iHp = iNewMax;
|
||||
|
||||
FireHpChanged();
|
||||
UpdateRegen();
|
||||
}
|
||||
|
||||
// Full HP; on spawn, round start and when the feature is switched on.
|
||||
void ResetHp()
|
||||
{
|
||||
g_iHp = g_cvHpMax.IntValue;
|
||||
g_bBroken = false;
|
||||
g_fLastZombieHit = 0.0;
|
||||
FireHpChanged();
|
||||
}
|
||||
|
||||
// Clamped to 0..max. Doesn't touch the regen timer; callers decide that.
|
||||
void SetHp(int hp)
|
||||
{
|
||||
int iMax = g_cvHpMax.IntValue;
|
||||
if (hp < 0)
|
||||
hp = 0;
|
||||
else if (hp > iMax)
|
||||
hp = iMax;
|
||||
|
||||
if (hp == g_iHp)
|
||||
return;
|
||||
|
||||
g_iHp = hp;
|
||||
FireHpChanged();
|
||||
}
|
||||
|
||||
void FireHpChanged()
|
||||
{
|
||||
Call_StartForward(g_fwdOnCouchHpChanged);
|
||||
Call_PushCell(GetCouchIndex());
|
||||
Call_PushCell(g_iHp);
|
||||
Call_PushCell(g_cvHpMax.IntValue);
|
||||
Call_Finish();
|
||||
}
|
||||
|
||||
// Brings the regen timer in line with the state: it runs only while a live couch is below full HP.
|
||||
// Not called from inside Timer_Regen; that returns Plugin_Stop instead.
|
||||
void UpdateRegen()
|
||||
{
|
||||
if (!g_cvHp.BoolValue || g_bBroken || GetCouchIndex() == -1 || g_iHp >= g_cvHpMax.IntValue)
|
||||
{
|
||||
StopRegen();
|
||||
return;
|
||||
}
|
||||
|
||||
StartRegen();
|
||||
}
|
||||
|
||||
void StartRegen()
|
||||
{
|
||||
if (g_hRegenTimer == null)
|
||||
g_hRegenTimer = CreateTimer(COUCH_REGEN_INTERVAL, Timer_Regen, _, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE);
|
||||
}
|
||||
|
||||
void StopRegen()
|
||||
{
|
||||
delete g_hRegenTimer;
|
||||
}
|
||||
|
||||
public Action Timer_Regen(Handle timer)
|
||||
{
|
||||
int iMax = g_cvHpMax.IntValue;
|
||||
|
||||
if (!g_cvHp.BoolValue || g_bBroken || GetCouchIndex() == -1 || g_iHp >= iMax)
|
||||
{
|
||||
g_hRegenTimer = null;
|
||||
return Plugin_Stop;
|
||||
}
|
||||
|
||||
// Waiting out the delay since the last hit; a new hit pushes it back.
|
||||
if (GetGameTime() - g_fLastZombieHit < g_cvHpRegenDelay.FloatValue)
|
||||
return Plugin_Continue;
|
||||
|
||||
SetHp(g_iHp + g_cvHpRegen.IntValue);
|
||||
|
||||
if (g_iHp >= iMax)
|
||||
{
|
||||
g_hRegenTimer = null;
|
||||
return Plugin_Stop;
|
||||
}
|
||||
|
||||
return Plugin_Continue;
|
||||
}
|
||||
|
||||
// Takes the hit off the couch's HP and plays a break sound at it. Returns true if this hit broke the couch.
|
||||
bool ApplyZombieHit(int couch, int attacker, float damage)
|
||||
{
|
||||
// Every hit costs at least 1 HP, whatever the knife does to props.
|
||||
int iDamage = RoundToNearest(damage);
|
||||
if (iDamage < 1)
|
||||
iDamage = 1;
|
||||
|
||||
g_fLastZombieHit = GetGameTime();
|
||||
SetHp(g_iHp - iDamage);
|
||||
|
||||
EmitSoundToAll(g_sHitSounds[GetRandomInt(0, sizeof(g_sHitSounds) - 1)], couch, SNDCHAN_STATIC, SNDLEVEL_NORMAL);
|
||||
|
||||
if (g_iHp > 0)
|
||||
{
|
||||
UpdateRegen();
|
||||
return false;
|
||||
}
|
||||
|
||||
BreakCouch(couch, attacker);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 0 HP: the couch breaks loudly, every human dies and the couch is removed; it comes back at full HP
|
||||
// at the next round start if it was placed. attacker is 0 for an admin set.
|
||||
void BreakCouch(int couch, int attacker)
|
||||
{
|
||||
g_bBroken = true;
|
||||
StopRegen();
|
||||
|
||||
// The explosion is heard by everyone at full volume; the splintering is placed at the couch.
|
||||
EmitSoundToAll(g_sExplodeSounds[GetRandomInt(0, sizeof(g_sExplodeSounds) - 1)], SOUND_FROM_PLAYER, SNDCHAN_STATIC, SNDLEVEL_NONE);
|
||||
EmitSoundToAll(g_sBreakSounds[GetRandomInt(0, sizeof(g_sBreakSounds) - 1)], couch, SNDCHAN_STATIC, SNDLEVEL_SCREAMING);
|
||||
|
||||
if (attacker >= 1 && attacker <= MaxClients && IsClientInGame(attacker))
|
||||
PrintToChatAll(PREFIX ... "%N\x01 broke the couch! Game over!", attacker);
|
||||
else
|
||||
PrintToChatAll(PREFIX ... "The couch\x01 broke! Game over!");
|
||||
|
||||
// While the entity still exists, so listeners can read its position.
|
||||
Call_StartForward(g_fwdOnCouchBroken);
|
||||
Call_PushCell(couch);
|
||||
Call_PushCell(attacker);
|
||||
Call_Finish();
|
||||
|
||||
if (g_bZRLoaded)
|
||||
{
|
||||
for (int i = 1; i <= MaxClients; i++)
|
||||
{
|
||||
if (IsClientInGame(i) && IsPlayerAlive(i) && ZR_IsClientHuman(i))
|
||||
KillHuman(i);
|
||||
}
|
||||
}
|
||||
|
||||
// Last: fires Couch_OnCouchRemoved. The Kill input is deferred, so this is safe from inside the
|
||||
// couch's own damage hook; g_bBroken swallows the rest of that hit's pellets.
|
||||
KillCouch();
|
||||
}
|
||||
|
||||
// Kills a human when the couch breaks. Lethal world damage is an ordinary death: it runs the damage
|
||||
// path, fires player_death and leaves a ragdoll, where CommitSuicide can be refused or skip the
|
||||
// ragdoll. The damage is well over health plus kevlar so armour cannot save them; suicide is the
|
||||
// fallback if another plugin swallowed the damage.
|
||||
void KillHuman(int client)
|
||||
{
|
||||
float fDamage = float(GetClientHealth(client) + GetClientArmor(client)) * 2.0 + 1000.0;
|
||||
SDKHooks_TakeDamage(client, 0, 0, fDamage, DMG_GENERIC);
|
||||
|
||||
if (IsPlayerAlive(client))
|
||||
ForcePlayerSuicide(client);
|
||||
}
|
||||
|
||||
// Reach of the engine's +use push.
|
||||
@@ -934,14 +1318,14 @@ void ApplyLightIntensity()
|
||||
AcceptEntityInput(light, "brightness");
|
||||
}
|
||||
|
||||
// Brings the light and beacon in line with sm_couch_effect. Safe to call at any time.
|
||||
// Brings the light and colour cycle in line with sm_couch_effect. Safe to call at any time.
|
||||
void ApplyEffectState()
|
||||
{
|
||||
int couch = GetCouchIndex();
|
||||
|
||||
if (couch == -1 || !g_cvEffect.BoolValue)
|
||||
{
|
||||
StopBeacon();
|
||||
StopEffect();
|
||||
KillLight();
|
||||
|
||||
if (couch != -1)
|
||||
@@ -953,7 +1337,7 @@ void ApplyEffectState()
|
||||
if (GetLightIndex() == -1)
|
||||
g_iLightRef = SpawnCouchLight(couch);
|
||||
|
||||
StartBeacon();
|
||||
StartEffect();
|
||||
}
|
||||
|
||||
// light_dynamic parented to the couch. Returns INVALID_ENT_REFERENCE on failure.
|
||||
@@ -1002,6 +1386,84 @@ void KillLight()
|
||||
AcceptEntityInput(light, "Kill");
|
||||
}
|
||||
|
||||
void StartEffect()
|
||||
{
|
||||
if (g_hEffectTimer == null)
|
||||
g_hEffectTimer = CreateTimer(COUCH_EFFECT_INTERVAL, Timer_Effect, _, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE);
|
||||
}
|
||||
|
||||
// Not called from inside Timer_Effect; that returns Plugin_Stop instead.
|
||||
void StopEffect()
|
||||
{
|
||||
delete g_hEffectTimer;
|
||||
}
|
||||
|
||||
public Action Timer_Effect(Handle timer)
|
||||
{
|
||||
int couch = GetCouchIndex();
|
||||
if (couch == -1)
|
||||
{
|
||||
g_hEffectTimer = null;
|
||||
return Plugin_Stop;
|
||||
}
|
||||
|
||||
g_iEffectTick++;
|
||||
|
||||
int colour = (g_iEffectTick / COUCH_COLOUR_TICKS) % sizeof(g_iCouchColours);
|
||||
int r = g_iCouchColours[colour][0];
|
||||
int g = g_iCouchColours[colour][1];
|
||||
int b = g_iCouchColours[colour][2];
|
||||
|
||||
SetEntityRenderColor(couch, r, g, b, 255);
|
||||
|
||||
g_iEffectColour[0] = r;
|
||||
g_iEffectColour[1] = g;
|
||||
g_iEffectColour[2] = b;
|
||||
|
||||
int light = GetLightIndex();
|
||||
if (light == -1)
|
||||
return Plugin_Continue;
|
||||
|
||||
SetEntityRenderColor(light, r, g, b, 255);
|
||||
|
||||
// One sine period per pulse cycle, mapped onto the radius range.
|
||||
float fPhase = float(g_iEffectTick % COUCH_PULSE_TICKS) / float(COUCH_PULSE_TICKS);
|
||||
float fPulse = (Sine(fPhase * 2.0 * FLOAT_PI) + 1.0) / 2.0;
|
||||
|
||||
SetVariantFloat(COUCH_LIGHT_RADIUS_MIN + fPulse * (COUCH_LIGHT_RADIUS_MAX - COUCH_LIGHT_RADIUS_MIN));
|
||||
AcceptEntityInput(light, "distance");
|
||||
|
||||
return Plugin_Continue;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
// Beacon
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
|
||||
public void OnBeaconChanged(ConVar convar, const char[] oldValue, const char[] newValue)
|
||||
{
|
||||
ApplyBeaconState();
|
||||
}
|
||||
|
||||
// The sprites are stock CS:S content, the same ones sm_beacon uses.
|
||||
void PrecacheBeacon()
|
||||
{
|
||||
g_iBeaconBeamSprite = PrecacheModel(COUCH_BEACON_SPRITE_BEAM, true);
|
||||
g_iBeaconHaloSprite = PrecacheModel(COUCH_BEACON_SPRITE_HALO, true);
|
||||
}
|
||||
|
||||
// Brings the beacon in line with sm_couch_beacon. Safe to call at any time.
|
||||
void ApplyBeaconState()
|
||||
{
|
||||
if (GetCouchIndex() == -1 || !g_cvBeacon.BoolValue)
|
||||
{
|
||||
StopBeacon();
|
||||
return;
|
||||
}
|
||||
|
||||
StartBeacon();
|
||||
}
|
||||
|
||||
void StartBeacon()
|
||||
{
|
||||
if (g_hBeaconTimer == null)
|
||||
@@ -1014,6 +1476,7 @@ void StopBeacon()
|
||||
delete g_hBeaconTimer;
|
||||
}
|
||||
|
||||
// Same rings as sm_beacon's Timer_Beacon, from the couch instead of a player, without its blip.
|
||||
public Action Timer_Beacon(Handle timer)
|
||||
{
|
||||
int couch = GetCouchIndex();
|
||||
@@ -1023,27 +1486,26 @@ public Action Timer_Beacon(Handle timer)
|
||||
return Plugin_Stop;
|
||||
}
|
||||
|
||||
g_iBeaconTick++;
|
||||
|
||||
int colour = (g_iBeaconTick / COUCH_COLOUR_TICKS) % sizeof(g_iCouchColours);
|
||||
int r = g_iCouchColours[colour][0];
|
||||
int g = g_iCouchColours[colour][1];
|
||||
int b = g_iCouchColours[colour][2];
|
||||
|
||||
SetEntityRenderColor(couch, r, g, b, 255);
|
||||
|
||||
int light = GetLightIndex();
|
||||
if (light == -1)
|
||||
if (g_iBeaconBeamSprite <= 0 || g_iBeaconHaloSprite <= 0)
|
||||
return Plugin_Continue;
|
||||
|
||||
SetEntityRenderColor(light, r, g, b, 255);
|
||||
float vecOrigin[3];
|
||||
GetEntPropVector(couch, Prop_Send, "m_vecOrigin", vecOrigin);
|
||||
vecOrigin[2] += COUCH_BEACON_HEIGHT;
|
||||
|
||||
// One sine period per pulse cycle, mapped onto the radius range.
|
||||
float fPhase = float(g_iBeaconTick % COUCH_PULSE_TICKS) / float(COUCH_PULSE_TICKS);
|
||||
float fPulse = (Sine(fPhase * 2.0 * FLOAT_PI) + 1.0) / 2.0;
|
||||
int iInnerColour[4];
|
||||
if (g_cvEffect.BoolValue)
|
||||
iInnerColour = g_iEffectColour;
|
||||
else
|
||||
iInnerColour = g_iBeaconInnerColour;
|
||||
|
||||
SetVariantFloat(COUCH_LIGHT_RADIUS_MIN + fPulse * (COUCH_LIGHT_RADIUS_MAX - COUCH_LIGHT_RADIUS_MIN));
|
||||
AcceptEntityInput(light, "distance");
|
||||
float fRadius = g_cvBeaconRadius.FloatValue;
|
||||
|
||||
TE_SetupBeamRingPoint(vecOrigin, 10.0, fRadius, g_iBeaconBeamSprite, g_iBeaconHaloSprite, 0, 15, 0.5, 5.0, 0.0, g_iBeaconOuterColour, 10, 0);
|
||||
TE_SendToAll();
|
||||
|
||||
TE_SetupBeamRingPoint(vecOrigin, 10.0, fRadius, g_iBeaconBeamSprite, g_iBeaconHaloSprite, 0, 10, 0.6, 10.0, 0.5, iInnerColour, 10, 0);
|
||||
TE_SendToAll();
|
||||
|
||||
return Plugin_Continue;
|
||||
}
|
||||
@@ -1151,6 +1613,17 @@ public any Native_SetEffectEnabled(Handle plugin, int numParams)
|
||||
return 0;
|
||||
}
|
||||
|
||||
public any Native_IsBeaconEnabled(Handle plugin, int numParams)
|
||||
{
|
||||
return g_cvBeacon.BoolValue;
|
||||
}
|
||||
|
||||
public any Native_SetBeaconEnabled(Handle plugin, int numParams)
|
||||
{
|
||||
g_cvBeacon.SetBool(view_as<bool>(GetNativeCell(1)));
|
||||
return 0;
|
||||
}
|
||||
|
||||
public any Native_IsPistolOnly(Handle plugin, int numParams)
|
||||
{
|
||||
return g_cvPistolOnly.BoolValue;
|
||||
@@ -1191,3 +1664,49 @@ public any Native_IsClientBlocked(Handle plugin, int numParams)
|
||||
|
||||
return g_bBlocked[client];
|
||||
}
|
||||
|
||||
public any Native_IsHpEnabled(Handle plugin, int numParams)
|
||||
{
|
||||
return g_cvHp.BoolValue;
|
||||
}
|
||||
|
||||
public any Native_SetHpEnabled(Handle plugin, int numParams)
|
||||
{
|
||||
g_cvHp.SetBool(view_as<bool>(GetNativeCell(1)));
|
||||
return 0;
|
||||
}
|
||||
|
||||
public any Native_GetHp(Handle plugin, int numParams)
|
||||
{
|
||||
return (GetCouchIndex() == -1) ? 0 : g_iHp;
|
||||
}
|
||||
|
||||
// 0 breaks the couch like a zombie hit would, which gives admins a way to test the break.
|
||||
public any Native_SetHp(Handle plugin, int numParams)
|
||||
{
|
||||
SetHp(GetNativeCell(1));
|
||||
|
||||
int couch = GetCouchIndex();
|
||||
if (g_iHp == 0 && g_cvHp.BoolValue && !g_bBroken && couch != -1)
|
||||
BreakCouch(couch, 0);
|
||||
else
|
||||
UpdateRegen();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public any Native_GetMaxHp(Handle plugin, int numParams)
|
||||
{
|
||||
return g_cvHpMax.IntValue;
|
||||
}
|
||||
|
||||
// The cvar's change hook keeps a full couch full and clamps the rest.
|
||||
public any Native_SetMaxHp(Handle plugin, int numParams)
|
||||
{
|
||||
int maxhp = GetNativeCell(1);
|
||||
if (maxhp < 1)
|
||||
maxhp = 1;
|
||||
|
||||
g_cvHpMax.SetInt(maxhp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user