#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)); }