diff --git a/ThrowingKnives/scripting/ThrowingKnives.sp b/ThrowingKnives/scripting/ThrowingKnives.sp new file mode 100644 index 00000000..fde1114f --- /dev/null +++ b/ThrowingKnives/scripting/ThrowingKnives.sp @@ -0,0 +1,575 @@ +#pragma semicolon 1 + +#include +#include +#include +#include +#include + +#include "zr_grenade_effects.inc" + +#pragma newdecls required + +enum +{ + Hit_Undefined = -1, + Hit_NotAPlayer, + Hit_Teammate, + Hit_Enemy +} + +ArrayList g_hLiveKnives = null; + +ConVar g_cvarThrowingKnives = null; +ConVar g_cvarKnifeDamage = null; +ConVar g_cvarKnifeDamageHS = null; +ConVar g_cvarKnifeTrail = null; +ConVar g_cvarStartingKnivesMother = null; +ConVar g_cvarStartingKnives = null; +ConVar g_cvarMaxKnives = null; +ConVar g_cvarKnifeRegenTime = null; + +Handle g_hNotifyTimer = null; +Handle g_hKnifeRegenerationTimer[MAXPLAYERS + 1] = { null, ... }; + +bool g_bEnabled = false; +bool g_bIgnoredFirstUpdate[MAXPLAYERS + 1] = { false, ... }; + +int g_iTimeUntilNextKnife[MAXPLAYERS + 1] = { -1, ... }; +int g_iPlayerKnives[MAXPLAYERS + 1] = { 0, ... }; +int g_iKnifeModelIdx = 0; +int g_iLastKnifeDeath = 0; + +public Plugin myinfo = +{ + name = "ThrowingKnives (split from FunMode)", + author = "Obus + Dogan", + description = "Tools to enable throwing knives for the Zombies in ZE", + version = "1.0.0", + url = "" +} + +public void OnPluginStart() +{ + LoadTranslations("common.phrases"); + + g_hLiveKnives = new ArrayList(2); + + HookConVarChange((g_cvarThrowingKnives = CreateConVar("sm_throwingknives_enabled", "0", "1 = Throwing Knives enabled, 0 = Throwing Knives disabled", FCVAR_NONE, true, 0.0, true, 1.0)), g_cvarThrowingKnivesHook); + g_bEnabled = g_cvarThrowingKnives.BoolValue; + g_cvarKnifeDamage = CreateConVar("sm_throwingknives_knifedamage", "15", "How much damage a knife hit shall deal"); + g_cvarKnifeDamageHS = CreateConVar("sm_throwingknives_knifedamagehs", "30", "How much damage a knife headshot shall deal"); + g_cvarKnifeTrail = CreateConVar("sm_throwingknives_knifetrail", "1", "Enables knife trails"); + g_cvarStartingKnivesMother = CreateConVar("sm_throwingknives_startingknivesmother", "3", "How many knives mother zm shall spawn with"); + g_cvarStartingKnives = CreateConVar("sm_throwingknives_startingknives", "0", "How many knives normal zm shall spawn with"); + g_cvarMaxKnives = CreateConVar("sm_throwingknives_maxknives", "5", "Maximum number of knives a zm can carry"); + g_cvarKnifeRegenTime = CreateConVar("sm_throwingknives_regentime", "30", "How long it takes to regenerate 1 knife"); + + AutoExecConfig(true, "plugin.ThrowingKnives"); + + g_cvarMaxKnives.AddChangeHook(ConVarChanged_Max_Regen); + g_cvarKnifeRegenTime.AddChangeHook(ConVarChanged_Max_Regen); + + HookEvent("player_death", EventHook_PlayerDeath, EventHookMode_Pre); + + g_hNotifyTimer = CreateTimer(0.25, Timer_UpdateKnives, _, TIMER_REPEAT); + + AddNormalSoundHook(NormalSHook_SmokeImpact); +} + +public void OnPluginEnd() +{ + if (g_hNotifyTimer != null) + delete g_hNotifyTimer; + + for (int i = 1; i <= MaxClients; i++) + { + if (!IsClientInGame(i) || IsFakeClient(i)) + continue; + + OnClientDisconnect(i); + } + + UnhookEvent("player_death", EventHook_PlayerDeath, EventHookMode_Pre); + + g_cvarMaxKnives.RemoveChangeHook(ConVarChanged_Max_Regen); + g_cvarKnifeRegenTime.RemoveChangeHook(ConVarChanged_Max_Regen); +} + +public Action ZR_OnGrenadeEffect(int client, int grenade) +{ + if (!g_bEnabled) + return Plugin_Continue; + + if (!IsValidClient(client) || !IsValidEntity(grenade)) + return Plugin_Continue; + + if (g_hLiveKnives.FindValue(grenade) != -1) + return Plugin_Stop; + + return Plugin_Continue; +} + +public void g_cvarThrowingKnivesHook(ConVar convar, const char[] oldValue, const char[] newValue) +{ + g_bEnabled = convar.BoolValue; +} + +public void OnMapStart() +{ + g_iKnifeModelIdx = PrecacheModel("models/weapons/w_knife_t.mdl"); + + for (int i = 0; i < MAXPLAYERS + 1; i++) + { + g_bIgnoredFirstUpdate[i] = false; + } +} + +public void OnMapEnd() +{ + for (int i = 1; i <= MaxClients; i++) + { + if (g_hKnifeRegenerationTimer[i] != null) + delete g_hKnifeRegenerationTimer[i]; + } +} + +public void OnClientPutInServer(int client) +{ + if (!g_bEnabled) + return; + + if (g_hKnifeRegenerationTimer[client] != null) + delete g_hKnifeRegenerationTimer[client]; + + g_iPlayerKnives[client] = 0; + g_bIgnoredFirstUpdate[client] = false; +} + +public void OnClientDisconnect(int client) +{ + if (g_hKnifeRegenerationTimer[client] != null) + delete g_hKnifeRegenerationTimer[client]; + + g_iPlayerKnives[client] = 0; + g_bIgnoredFirstUpdate[client] = false; +} + +public void OnEntityCreated(int entity, const char[] sClassName) +{ + if (!g_bEnabled) + return; + + if (!strncmp(sClassName[7], "knife", 5)) + RequestFrame(RequestFrame_OnEntityCreated_Knife, entity); + + if (!strcmp(sClassName, "smokegrenade_projectile")) + RequestFrame(RequestFrame_OnEntityCreated_SmokeProjectile, entity); +} + +public void RequestFrame_OnEntityCreated_SmokeProjectile(int entity) +{ + if (!IsValidEntity(entity)) + return; + + if (g_hLiveKnives.FindValue(entity) == -1) + return; + + SetEntProp(entity, Prop_Send, "m_CollisionGroup", 0, 4); +} + +public void RequestFrame_OnEntityCreated_Knife(int entity) +{ + if (!IsValidEntity(entity)) + return; + + if (!HasEntProp(entity, Prop_Send, "m_hOwnerEntity")) + return; + + int iOwner = GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity"); + + if (!IsValidClient(iOwner)) + return; + + g_bIgnoredFirstUpdate[iOwner] = false; +} + +public void OnEntityDestroyed(int entity) +{ + int idx = -1; + + if ((idx = g_hLiveKnives.FindValue(entity)) != -1) + g_hLiveKnives.Erase(idx); +} + +public void ZR_OnClientInfected(int client, int attacker, bool bMotherInfect, bool bRespawnOverride, bool bRespawn) +{ + if (!g_bEnabled) + return; + + if (bMotherInfect) + g_iPlayerKnives[client] = g_cvarStartingKnivesMother.IntValue; + else + g_iPlayerKnives[client] = g_cvarStartingKnives.IntValue; + + if (g_hKnifeRegenerationTimer[client] != null) + { + delete g_hKnifeRegenerationTimer[client]; + g_hKnifeRegenerationTimer[client] = null; + } + + g_hKnifeRegenerationTimer[client] = CreateTimer(g_cvarKnifeRegenTime.FloatValue, Timer_RegenerateKnives, client, TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); + g_iTimeUntilNextKnife[client] = GetTime() + g_cvarKnifeRegenTime.IntValue; +} + +public Action OnPlayerRunCmd(int client, int &buttons) +{ + static float fLastSecondaryAttack[MAXPLAYERS + 1]; + + if (!g_bEnabled) + return Plugin_Continue; + + if (!(buttons & IN_ATTACK2) || !IsValidClient(client) || !ZR_IsClientZombie(client)) + return Plugin_Continue; + + int iKnife = GetPlayerWeaponSlot(client, CS_SLOT_KNIFE); + + if (iKnife <= 0) + return Plugin_Continue; + + char sWeapon[32]; + GetClientWeapon(client, sWeapon, sizeof(sWeapon)); + + if (strncmp(sWeapon[7], "knife", 5)) + return Plugin_Continue; + + float fNextSecondaryAttack = GetEntPropFloat(iKnife, Prop_Send, "m_flNextSecondaryAttack"); + + if (fNextSecondaryAttack == fLastSecondaryAttack[client] || !g_bIgnoredFirstUpdate[client]) + { + g_bIgnoredFirstUpdate[client] = true; + return Plugin_Continue; + } + + fLastSecondaryAttack[client] = fNextSecondaryAttack; + + DispatchKnife(client); + + return Plugin_Continue; +} + +public Action EventHook_PlayerDeath(Event hEvent, const char[] sName, bool bDontBroadcast) +{ + if (!g_bEnabled) + return Plugin_Continue; + + int victim = GetClientOfUserId(hEvent.GetInt("userid")); + char sWeapon[32]; + + hEvent.GetString("weapon", sWeapon, sizeof(sWeapon)); + + if (victim == g_iLastKnifeDeath) + { + g_iLastKnifeDeath = 0; + return Plugin_Handled; + } + + return Plugin_Continue; +} + +public Action Timer_UpdateKnives(Handle hThis) +{ + if (!g_bEnabled) + return Plugin_Continue; + + /*if (IsVoteInProgress()) //not showing vote results anyway + return Plugin_Continue;*/ + + for (int i = 1; i <= MaxClients; i++) + { + if (!IsValidClient(i) || IsFakeClient(i)) + continue; + + if (!ZR_IsClientZombie(i)) + continue; + + if (g_hKnifeRegenerationTimer[i] != null) + PrintHintText(i, "Throwing knives: %d/%d [%d]", g_iPlayerKnives[i], g_cvarMaxKnives.IntValue, g_iTimeUntilNextKnife[i] - GetTime()); + else + PrintHintText(i, "Throwing knives: %d/%d", g_iPlayerKnives[i], g_cvarMaxKnives.IntValue); + + StopSound(i, SNDCHAN_STATIC, "UI/hint.wav"); + } + + return Plugin_Continue; +} + +public Action NormalSHook_SmokeImpact(int clients[MAXPLAYERS], int &numClients, char sample[PLATFORM_MAX_PATH], int &entity, int &channel, float &volume, int &level, int &pitch, int &flags, char soundEntry[PLATFORM_MAX_PATH], int &seed) +{ + if (!g_bEnabled) + return Plugin_Continue; + + int idx = -1; + + if ((idx = g_hLiveKnives.FindValue(entity)) != -1 && !strncmp(sample, "weapons/smokegrenade/grenade_hit", 32)) + { + int iHitType = g_hLiveKnives.Get(idx, 1, false); + + switch (iHitType) + { + case Hit_Undefined, Hit_Teammate: + { + return Plugin_Continue; + } + + case Hit_NotAPlayer: + { + return Plugin_Continue; + } + + case Hit_Enemy: + { + EmitSoundToAll("physics/flesh/flesh_impact_bullet4.wav", entity, channel, level, flags, volume, pitch); + return Plugin_Handled; + } + + default: + { + return Plugin_Continue; //monkaS + } + } + } + + return Plugin_Continue; +} + +void DispatchKnife(int iOwner) +{ + if (g_iPlayerKnives[iOwner] <= 0) + return; + + int iKnife = CreateEntityByName("smokegrenade_projectile"); + + if (iKnife <= 0 || !DispatchSpawn(iKnife)) + return; + + SetEntPropEnt(iKnife, Prop_Send, "m_hOwnerEntity", iOwner); + SetEntPropEnt(iKnife, Prop_Send, "m_hThrower", iOwner); + SetEntProp(iKnife, Prop_Send, "m_iTeamNum", GetClientTeam(iOwner)); + SetEntProp(iKnife, Prop_Send, "m_nModelIndex", g_iKnifeModelIdx); + SetEntPropFloat(iKnife, Prop_Send, "m_flModelScale", 1.0); + SetEntPropFloat(iKnife, Prop_Send, "m_flElasticity", 0.2); + SetEntPropFloat(iKnife, Prop_Data, "m_flGravity", 1.0); + + float vecOrigin[3]; + float vecAngles[3]; + float vecVelocity[3]; + float vecOwnerVelocity[3]; + float vecKnifeSpin[3] = { 2000.0, 0.0, 0.0 }; + + GetClientEyePosition(iOwner, vecOrigin); + GetClientEyeAngles(iOwner, vecAngles); + GetAngleVectors(vecAngles, vecVelocity, NULL_VECTOR, NULL_VECTOR); + ScaleVector(vecVelocity, 2000.0); + GetEntPropVector(iOwner, Prop_Data, "m_vecVelocity", vecOwnerVelocity); + AddVectors(vecVelocity, vecOwnerVelocity, vecVelocity); + SetEntPropVector(iKnife, Prop_Data, "m_vecAngVelocity", vecKnifeSpin); + + SetEntProp(iKnife, Prop_Data, "m_nNextThinkTick", -1); + DispatchKeyValue(iKnife, "OnUser1", "!self,Kill,,10.0,-1"); + AcceptEntityInput(iKnife, "FireUser1"); + + if (g_cvarKnifeTrail.BoolValue) + { + int iColor[4] = { 255, ... }; + TE_SetupBeamFollow(iKnife, PrecacheModel("sprites/bluelaser1.vmt"), 0, 0.5, 8.0, 1.0, 0, iColor); + TE_SendToAll(); + } + + TeleportEntity(iKnife, vecOrigin, vecAngles, vecVelocity); + SDKHook(iKnife, SDKHook_Touch, SDKHookCB_OnKnifeStartTouch); + + g_iPlayerKnives[iOwner]--; + + //PrintToServer("[DispatchKnife] %N threw knife %d | %d remaining", iOwner, iKnife, g_iPlayerKnives[iOwner]); + g_hLiveKnives.Set(g_hLiveKnives.Push(iKnife), Hit_Undefined, 1, false); + + if (g_hKnifeRegenerationTimer[iOwner] == null) + { + g_hKnifeRegenerationTimer[iOwner] = CreateTimer(g_cvarKnifeRegenTime.FloatValue, Timer_RegenerateKnives, iOwner, TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); + g_iTimeUntilNextKnife[iOwner] = GetTime() + g_cvarKnifeRegenTime.IntValue; + } +} + +public Action SDKHookCB_OnKnifeStartTouch(int iKnife, int iHitEntity) +{ + if (IsValidClient(iHitEntity)) + { + int iAttacker = GetEntPropEnt(iKnife, Prop_Send, "m_hThrower"); + + if (!IsValidClient(iAttacker)) + { + AcceptEntityInput(iKnife, "Kill"); + return Plugin_Continue; + } + + if (iHitEntity == iAttacker) + return Plugin_Continue; + + if (GetClientTeam(iAttacker) == GetClientTeam(iHitEntity)) + { + int idx = -1; + + if ((idx = g_hLiveKnives.FindValue(iKnife)) != -1) + g_hLiveKnives.Set(idx, Hit_Teammate, 1, false); + + SetEntProp(iKnife, Prop_Send, "m_CollisionGroup", 2, 4); + CreateTimer(0.050, Timer_KillKnife, iKnife, TIMER_FLAG_NO_MAPCHANGE); + return Plugin_Continue; + } + + int idx = -1; + + if ((idx = g_hLiveKnives.FindValue(iKnife)) != -1) + g_hLiveKnives.Set(idx, Hit_Enemy, 1, false); + + int iDamageToDeal = g_cvarKnifeDamage.IntValue; + float vecVictimEyePos[3]; + float vecKnifeOrigin[3]; + float vecAttackerOrigin[3]; + + GetClientEyePosition(iHitEntity, vecVictimEyePos); + GetEntPropVector(iKnife, Prop_Send, "m_vecOrigin", vecKnifeOrigin); + GetClientAbsOrigin(iAttacker, vecAttackerOrigin); + + Handle hDamageMsg = StartMessageOne("Damage", iHitEntity, USERMSG_RELIABLE); + BfWriteByte(hDamageMsg, iDamageToDeal); + BfWriteVecCoord(hDamageMsg, vecAttackerOrigin); + EndMessage(); + + float fDistToEyePos = GetVectorDistance(vecKnifeOrigin, vecVictimEyePos); + bool bHeadShot = fDistToEyePos <= 22.5; //close enough :^) + iDamageToDeal = bHeadShot ? g_cvarKnifeDamageHS.IntValue : iDamageToDeal; + + int iVictimHealth = GetClientHealth(iHitEntity); + + if (iVictimHealth - iDamageToDeal <= 0) + { + g_iLastKnifeDeath = iHitEntity; + + ForcePlayerSuicide(iHitEntity); + + Event hPlayerDeathEvent = CreateEvent("player_death"); + hPlayerDeathEvent.SetInt("userid", GetClientUserId(iHitEntity)); + hPlayerDeathEvent.SetInt("attacker", GetClientUserId(iAttacker)); + hPlayerDeathEvent.SetString("weapon", "throwing_knife"); + hPlayerDeathEvent.SetBool("headshot", bHeadShot); + hPlayerDeathEvent.Fire(); + } + else + { + SetEntProp(iHitEntity, Prop_Send, "m_iHealth", iVictimHealth - iDamageToDeal); + + Event hPlayerHurtEvent = CreateEvent("player_hurt"); + hPlayerHurtEvent.SetInt("userid", GetClientUserId(iHitEntity)); + hPlayerHurtEvent.SetInt("attacker", GetClientUserId(iAttacker)); + hPlayerHurtEvent.SetInt("health", iVictimHealth - iDamageToDeal); + hPlayerHurtEvent.SetInt("armor", GetClientArmor(iHitEntity)); + hPlayerHurtEvent.SetString("weapon", "throwing_knife"); + hPlayerHurtEvent.SetInt("dmg_health", iDamageToDeal); + hPlayerHurtEvent.SetInt("dmg_armor", 0); + hPlayerHurtEvent.SetInt("hitgroup", bHeadShot?1:2); + hPlayerHurtEvent.Fire(); + } + + SetEntPropFloat(iHitEntity, Prop_Send, "m_flStamina", 1000.0); // ¯\_(?)_/¯ + + SetEntProp(iKnife, Prop_Send, "m_CollisionGroup", 2, 4); + CreateTimer(0.050, Timer_KillKnife, iKnife, TIMER_FLAG_NO_MAPCHANGE); + + return Plugin_Continue; + } + + char sClsName[64]; + + GetEntityClassname(iHitEntity, sClsName, sizeof(sClsName)); + + if (!strncmp(sClsName, "trigger_", 8) || !strncmp(sClsName, "func_", 5)) + return Plugin_Continue; + + int idx = -1; + + if ((idx = g_hLiveKnives.FindValue(iKnife)) != -1) + g_hLiveKnives.Set(idx, Hit_NotAPlayer, 1, false); + + SetEntProp(iKnife, Prop_Send, "m_CollisionGroup", 2, 4); + CreateTimer(0.050, Timer_KillKnife, iKnife, TIMER_FLAG_NO_MAPCHANGE); + + return Plugin_Continue; +} + +public Action Timer_KillKnife(Handle hTimer, int iKnife) +{ + if (!IsValidEntity(iKnife)) + return Plugin_Handled; + + AcceptEntityInput(iKnife, "Kill"); + + return Plugin_Handled; +} + +public Action Timer_RegenerateKnives(Handle hTimer, int iOwner) +{ + if (!g_bEnabled) + return Plugin_Continue; + + g_iPlayerKnives[iOwner]++; + + if (g_iPlayerKnives[iOwner] >= g_cvarMaxKnives.IntValue) + { + g_hKnifeRegenerationTimer[iOwner] = null; + return Plugin_Stop; + } + + g_iTimeUntilNextKnife[iOwner] = GetTime() + g_cvarKnifeRegenTime.IntValue; + + return Plugin_Continue; +} + +public void ConVarChanged_Max_Regen(ConVar cvar, const char[] sOldVal, const char[] sNewVal) +{ + if (!g_bEnabled) + return; + + if (cvar == g_cvarMaxKnives) + { + int iNewVal = StringToInt(sNewVal); + + for (int i = 0; i <= MaxClients; i++) + { + if (g_iPlayerKnives[i] <= iNewVal) + continue; + + g_iPlayerKnives[i] = iNewVal; + } + } + else + { + float fNewVal = StringToFloat(sNewVal); + + for (int i = 0; i <= MaxClients; i++) + { + if (g_hKnifeRegenerationTimer[i] == null) + continue; + + delete g_hKnifeRegenerationTimer[i]; + g_hKnifeRegenerationTimer[i] = CreateTimer(fNewVal, Timer_RegenerateKnives, i, TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); + g_iTimeUntilNextKnife[i] = GetTime() + g_cvarKnifeRegenTime.IntValue; + } + } +} + +stock bool IsValidClient(int client) +{ + return (client > 0 && client <= MaxClients && IsClientInGame(client) && IsPlayerAlive(client)); +} \ No newline at end of file