#define AUTOPUNISH_FLOW_MIN_DISTANCE 5000.0 #define AUTOPUNISH_MODE_COUNT 3 // #define DEBUG_PHRASE_LOAD 1 void ActivateAutoPunish(int client) { if(hAutoPunish.IntValue & 2 == 2) Troll.FromName("Special Magnet").Activate(0, client, TrollMod_Constant); if(hAutoPunish.IntValue & 1 == 1) Troll.FromName("Tank Magnet").Activate(0, client, TrollMod_Constant); if(hAutoPunish.IntValue & 8 == 8) Troll.FromName("Vomit Player").Activate(0, client, TrollMod_Instant); else if(hAutoPunish.IntValue & 4 == 4) Troll.FromName("Swarm").Activate(0, client, TrollMod_Instant); if(hAutoPunishExpire.IntValue > 0) { CreateTimer(60.0 * hAutoPunishExpire.FloatValue, Timer_ResetAutoPunish, GetClientOfUserId(client)); } } // NOTE: Only supports one target at a time, stored globally bool SetWitchTarget(int witch, int target) { #if defined _actions_included g_iWitchAttackVictim = target; BehaviorAction action = ActionsManager.GetAction(witch, "WitchBehavior"); if(action == INVALID_ACTION || action.Child == INVALID_ACTION) { return false; } action = action.Child; action.OnUpdate = OnWitchActionUpdate; return true; #else PrintToServer("[FTT] SetWitchTarget() called when behaviors plugin not found"); return false; #endif } bool ToggleMarkPlayer(int client, int target) { if(pdata[target].pendingTrollBan > 0) { pdata[target].pendingTrollBan = 0; LogAction(client, target, "\"%L\" unmarked \"%L\" as troll", client, target); ShowActivityEx(client, "[FTT] ", "unmarked %N as troll", target); return true; }else{ bool isClientAdmin = GetUserAdmin(client) != INVALID_ADMIN_ID; if(!isClientAdmin) { ReplyToCommand(client, "cannot mark %N as troll as you are not an admin.", target); return false; } bool isTargetAdmin = GetUserAdmin(target) != INVALID_ADMIN_ID; if(isTargetAdmin) { ReplyToCommand(client, "cannot mark %N as troll as they are an admin.", target); return false; } Call_StartForward(g_PlayerMarkedForward); Call_PushCell(client); Call_PushCell(target); Call_Finish(); pdata[target].pendingTrollBan = GetClientUserId(client); EnableTroll(target, "No Profanity"); LogAction(client, target, "\"%L\" marked \"%L\" as troll", client, target); ShowActivityEx(client, "[FTT] ", "marked %N as troll", target); return true; } } stock bool IsPlayerIncapped(int client) { return GetEntProp(client, Prop_Send, "m_isIncapacitated") == 1; } #define MAX_TYPOS_LENGTH 16 StringMap TYPOS_DICT; void LoadTypos() { TYPOS_DICT = new StringMap(); char sPath[PLATFORM_MAX_PATH]; BuildPath(Path_SM, sPath, sizeof(sPath), "data/ftt_typos.txt"); if(!FileExists(sPath)) { PrintToServer("[FTT] Missing typos list: data/ftt_typos.txt"); return; } File file = OpenFile(sPath, "r", false, NULL_STRING); if(file == null) { PrintToServer("[FTT] Cannot open: data/ftt_typos.txt"); return; } char buffer[140], key[32]; while(file.ReadLine(buffer, sizeof(buffer))) { int index = SplitString(buffer, " ", key, sizeof(key)); AddTypo(key, buffer[index]); } delete file; } ArrayList SplitStringList(const char[] message, char separator, int wordSize = 64) { ArrayList words = new ArrayList(ByteCountToCells(wordSize)); char[] word = new char[wordSize]; int len = strlen(message); int prevIndex; for(int i = 0; i < len; i++) { if(message[i] == separator) { // Only copy the length of the string. The len includes space, which is used as null term int wordLen = (i - prevIndex); if(wordSize < wordLen) wordLen = wordSize; strcopy(word, wordLen, message[prevIndex]); words.PushString(word); prevIndex = i; } } // End of string, copy the remainder strcopy(word, len, message[prevIndex]); words.PushString(word); return words; } void ReplaceWithTypos(const char[] message, char[] output, int maxlen) { ArrayList words = SplitStringList(message, ' '); message[0] = '\0'; char word[64]; ArrayList replaceList; for(int i = 0; i < words.Length; i++) { words.GetString(i, word, sizeof(word)); if(TYPOS_DICT.GetValue(word, replaceList)) { int index = GetRandomInt(0, replaceList.Length - 1); replaceList.GetString(index, word, sizeof(word)); if(i == 0) Format(output, maxlen, "%s", word); else Format(output, maxlen, "%s %s", message, word); } } delete words; } void AddTypo(const char[] src, const char[] typo, bool save = false) { ArrayList list; TYPOS_DICT.GetValue(src, list); if(list == null) { list = new ArrayList(ByteCountToCells(MAX_TYPOS_LENGTH)); } list.PushString(typo); TYPOS_DICT.SetValue(src, list); if(save) { char sPath[PLATFORM_MAX_PATH]; BuildPath(Path_SM, sPath, sizeof(sPath), "data/ftt_typos.txt"); File file = OpenFile(sPath, "a", false, NULL_STRING); if(file == null) { PrintToServer("[FTT] Cannot open for saving: data/ftt_typos.txt"); return; } file.Seek(SEEK_END, 0); file.WriteLine("%s %s", src, typo); file.Flush(); delete file; } } #define MAX_PHRASES_PER_WORD 8 #define MAX_PHRASE_LENGTH 191 StringMap REPLACEMENT_PHRASES; ArrayList fullMessagePhraseList; /* Example: exWord { "1" "phrase1" "2" "phrase2" } */ void LoadPhrases() { KeyValues kv = new KeyValues("Phrases"); ArrayList phrases = new ArrayList(ByteCountToCells(MAX_PHRASE_LENGTH)); char sPath[PLATFORM_MAX_PATH]; BuildPath(Path_SM, sPath, sizeof(sPath), "data/ftt_phrases.cfg"); if(!FileExists(sPath) || !kv.ImportFromFile(sPath)) { delete kv; PrintToServer("[FTT] Could not load phrase list from data/ftt_phrases.cfg"); return; } char word[32]; char phrase[MAX_PHRASE_LENGTH]; // Go through all the words: kv.GotoFirstSubKey(); int i = 0; char buffer[4]; do { kv.GetSectionName(word, sizeof(word)); StringToLower(word); phrases.Clear(); for(;;) { IntToString(++i, buffer, sizeof(buffer)); kv.GetString(buffer, phrase, MAX_PHRASE_LENGTH, "_null"); if(strcmp(phrase, "_null") == 0) break; phrases.PushString(phrase); } i = 0; if(StrEqual(word, "_full message phrases")) { fullMessagePhraseList = phrases.Clone(); continue; } #if defined DEBUG_PHRASE_LOAD PrintToServer("Loaded %d phrases for word \"%s\"", phrases.Length, word); #endif REPLACEMENT_PHRASES.SetValue(word, phrases.Clone(), true); } while (kv.GotoNextKey(false)); delete kv; } ArrayList GetPhrasesArray(const char[] key) { int len = strlen(key); char[] keyLower = new char[len]; for(int i = 0; i < len; i++) { keyLower[i] = CharToLower(key[i]); } ArrayList phrases; if(REPLACEMENT_PHRASES.GetValue(keyLower, phrases)) { return phrases; } else { return null; } } stock int FindClosestClientAdminPriority(int source, float pos[3]) { int c = FindClosestAdmin(source, pos); if(c == -1) return FindClosestClient(source, true,pos); else return c; } stock int FindClosestClient(int source, bool ignoreBots, float pos[3]) { int closest = -1; float minDist = -1.0; static float pos1[3]; static float pos2[3]; GetClientAbsOrigin(source, pos1); for(int i = 1; i <= MaxClients; i++) { if(i != source && IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) == 2 && IsPlayerAlive(i) && (!ignoreBots || !IsFakeClient(i)) ) { GetClientAbsOrigin(i, pos2); float dist = GetVectorDistance(pos1, pos2); if(minDist == -1.0 || dist <= minDist) { closest = i; minDist = dist; } } } GetClientEyePosition(closest, pos); return closest; } stock int FindClosestAdmin(int source, float pos[3]) { int closest = -1; float minDist = -1.0; static float pos1[3]; static float pos2[3]; GetClientAbsOrigin(source, pos); for(int i = 1; i <= MaxClients; i++) { if(i != source && IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) == 2 && IsPlayerAlive(i) && !IsFakeClient(i) && GetUserAdmin(i) != INVALID_ADMIN_ID) { GetClientAbsOrigin(i, pos2); float dist = GetVectorDistance(pos1, pos2, true); if(minDist == -1.0 || dist <= minDist) { closest = i; minDist = dist; } } } GetClientEyePosition(closest, pos); return closest; } int SpawnItem(const char[] itemName, float pos[3], float ang[3] = NULL_VECTOR) { static char classname[32]; Format(classname, sizeof(classname), "weapon_%s", itemName); int spawner = CreateEntityByName(classname); if(spawner == -1) return -1; DispatchKeyValue(spawner, "solid", "6"); // DispatchKeyValue(entity_weapon, "model", g_bLeft4Dead2 ? g_sWeaponModels2[model] : g_sWeaponModels[model]); DispatchKeyValue(spawner, "rendermode", "3"); DispatchKeyValue(spawner, "disableshadows", "1"); TeleportEntity(spawner, pos, ang, NULL_VECTOR); DispatchSpawn(spawner); TeleportEntity(spawner, pos, ang, NULL_VECTOR); return spawner; } bool IsAnyPlayerNear(int source, float range) { static float pos1[3]; static float pos2[3]; GetClientAbsOrigin(source, pos1); for(int i = 1; i <= MaxClients; i++) { if(IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) == 2 && IsPlayerAlive(i) && i != source) { GetClientAbsOrigin(i, pos2); float dist = GetVectorDistance(pos1, pos2); if(dist <= range) return true; } } return false; } void ThrowItemToPlayer(int victim, int target, int slot) { int wpn = GetPlayerWeaponSlot(victim, slot); if(wpn > 0 && (slot != 1 || DoesClientHaveMelee(victim))) { static float pos[3]; GetClientAbsOrigin(target, pos); SDKHooks_DropWeapon(victim, wpn, pos); } } stock void AddInFrontOf(float fVecOrigin[3], float fVecAngle[3], float fUnits, float fOutPut[3]) { float fVecView[3]; GetViewVector(fVecAngle, fVecView); fOutPut[0] = fVecView[0] * fUnits + fVecOrigin[0]; fOutPut[1] = fVecView[1] * fUnits + fVecOrigin[1]; fOutPut[2] = fVecView[2] * fUnits + fVecOrigin[2]; } stock void GetViewVector(float fVecAngle[3], float fOutPut[3]) { fOutPut[0] = Cosine(fVecAngle[1] / (180 / FLOAT_PI)); fOutPut[1] = Sine(fVecAngle[1] / (180 / FLOAT_PI)); fOutPut[2] = -Sine(fVecAngle[0] / (180 / FLOAT_PI)); } stock void LookAtClient(int iClient, int iTarget) { static float fTargetPos[3]; static float fTargetAngles[3]; static float fClientPos[3]; static float fFinalPos[3]; GetClientEyePosition(iClient, fClientPos); GetClientEyePosition(iTarget, fTargetPos); GetClientEyeAngles(iTarget, fTargetAngles); float fVecFinal[3]; AddInFrontOf(fTargetPos, fTargetAngles, 7.0, fVecFinal); MakeVectorFromPoints(fClientPos, fVecFinal, fFinalPos); GetVectorAngles(fFinalPos, fFinalPos); TeleportEntity(iClient, NULL_VECTOR, fFinalPos, NULL_VECTOR); } stock int GetClientRealHealth(int client) { if(!client || !IsValidEntity(client) || !IsClientInGame(client) || !IsPlayerAlive(client) || IsClientObserver(client)) { return -1; } else if(GetClientTeam(client) != 2) { return GetClientHealth(client); } float buffer = GetEntPropFloat(client, Prop_Send, "m_healthBuffer"); float tempHealth = 0.0; if(buffer > 0.0) { float difference = GetGameTime() - GetEntPropFloat(client, Prop_Send, "m_healthBufferTime"); float decay = FindConVar("pain_pills_decay_rate").FloatValue; float constant = 1.0 / decay; tempHealth = buffer - (difference / constant); if(tempHealth < 0.0) { tempHealth = 0.0; } } return RoundToFloor(GetClientHealth(client) + tempHealth); } /// Returns TRUE if set, FALSE if not (if no weapon to shoot) bool SetBotTarget(int bot, int target, int targetHP, int loops = 15) { if(pdata[target].shootAtTarget == bot) { return false; } else if(pdata[target].shootAtTarget > 0) { return false; } LookAtClient(target, bot); int weapon = GetPlayerWeaponSlot(target, 0); if(weapon > -1) { pdata[target].shootAtTarget = bot; pdata[target].shootAtLoops = loops; pdata[bot].shootAtTargetHealth = targetHP; int ammo = GetEntProp(weapon, Prop_Send, "m_iClip1"); DataPack pack = new DataPack(); // Reverse target and bot: pack.WriteCell(target); pack.WriteCell(bot); pack.WriteCell(weapon); pack.WriteCell(ammo); CreateTimer(0.1, Timer_ShootReverse, pack, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); return true; } else { return false; } } // Taken from https://forums.alliedmods.net/showthread.php?t=220132&page=2 stock void ExplodeProjectile(int entity, bool smoke = true) { SetEntProp(entity, Prop_Data, "m_takedamage", 2); SetEntProp(entity, Prop_Data, "m_iHealth", 1); SDKHooks_TakeDamage(entity, 0, 0, 1.0); if(smoke) SetEntProp(entity, Prop_Data, "m_nNextThinkTick", 1); //for smoke } stock int CreateProp(const char[] entClass, const char[] model, const float pos[3], const float ang[3] = { 0.0, 0.0, 0.0 }, const float vel[3] = {0.0, 0.0, 0.0}) { int entity = CreateEntityByName(entClass); DispatchKeyValue(entity, "model", model); DispatchKeyValue(entity, "solid", "6"); DispatchKeyValue(entity, "targetname", "hsprop"); DispatchKeyValue(entity, "disableshadows", "1"); TeleportEntity(entity, pos, ang, vel); DispatchSpawn(entity); TeleportEntity(entity, pos, ang, vel); #if defined DEBUG_LOG_MAPSTART PrintToServer("spawn prop %.1f %.1f %.1f model %s", pos[0], pos[1], pos[2], model[7]); #endif return entity; } public bool Filter_Solid(int entity, int contentsMask, any data) { return entity <= 0; } float VEH_MIN[3] = { -30.0, -30.0, 2.0}; float VEH_MAX[3] = { 30.0, 30.0, 20.0 }; bool SpawnCarToPlayer(int target, float distance) { float pos[3], ang[3]; GetClientAbsOrigin(target, pos); pos[2] += 40.0; GetClientEyeAngles(target, ang); ang[2] = ang[0] = 0.0; float endPos[3]; GetHorizontalPositionFromOrigin(pos, ang, distance, endPos); TR_TraceHullFilter(endPos, pos, VEH_MIN, VEH_MAX, MASK_SOLID, Filter_Solid); if(TR_DidHit()) { return false; } if(distance > 0.0) ang[1] -= 180; float vel[3]; vel[0] = Cosine(DegToRad(ang[1])) * 1500.0; vel[1] = Sine(DegToRad(ang[1])) * 1500.0; int id = CreateProp("prop_physics", MODEL_CAR, endPos, ang, vel); CreateTimer(6.0, Timer_Delete, id); return true; } bool SpawnCarOnPlayer(int target) { float min[3] = { -30.0, -30.0, -2.0}; float max[3] = { 30.0, 30.0, 50.0 }; float pos[3]; float ang[3]; GetClientEyePosition(target, pos); GetClientEyeAngles(target, ang); if(IsAreaClear(pos, ang, min, max)) { pos[2] += 40.0; int id = CreateProp("prop_physics", MODEL_CAR, pos, ang); CreateTimer(4.0, Timer_Delete, id); return true; } return false; } bool g_iPendingSurvivorAdd; int isCustomSurvivor[MAXPLAYERS+1]; bool AddSurvivor() { if (GetClientCount(false) >= MaxClients - 1) { return false; } int i = CreateFakeClient("FTTSurvivorBot"); bool result; if (i > 0) { if (DispatchKeyValue(i, "classname", "SurvivorBot")) { ChangeClientTeam(i, 2); if (DispatchSpawn(i)) { result = true; } } g_iPendingSurvivorAdd = true; CreateTimer(0.2, Timer_KickBot, i); } return result; } void ClearInventory(int client) { for(int i = 0; i <= 5; i++) { int item = GetPlayerWeaponSlot(client, i); if(item > 0) { AcceptEntityInput(item, "Kill"); } } } void StopHealingBots() { healTargetPlayer = 0; for(int i = 1; i <= MaxClients; i++) { pdata[i].flags &= ~view_as(Flag_IsTargettingHealer); if(isCustomSurvivor[i]) { ClearInventory(i); KickClient(i); } } delete stopHealingTimer; if(hAbmAutoHard != null) hAbmAutoHard.IntValue = wasAbmAutoHard; if(hSbFixEnabled != null) hSbFixEnabled.BoolValue = wasSbFixEnabled; } bool IsAnySurvivorInRange(const float origin[3], float range, int ignorePlayer = 0) { float pos[3]; range = range * range; for(int i = 1; i <= MaxClients; i++) { if(i != ignorePlayer && IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) == 2 && IsPlayerAlive(i)) { GetClientAbsOrigin(i, pos); if(GetVectorDistance(origin, pos, true) <= range) { return true; } } } return false; } int GetRandomThrowableMagnetTarget(ProjectileMagnetType type, int owner = -1) { static int throwMagnetIndex; if(throwMagnetIndex == 0) throwMagnetIndex = GetTrollID("Projectile Magnet"); ArrayList checkList = new ArrayList(); for(int i = 1; i <= MaxClients; i++) { if(IsClientConnected(i) && IsClientInGame(i) && IsPlayerAlive(i) && Trolls[throwMagnetIndex].IsActive(i)) { if(type == ProjType_Survivors) { // If the projectile is not owned by player, check if troll flag not enabled if(owner != i && ~Trolls[throwMagnetIndex].activeFlagClients[i] & view_as(ProjType_Survivors)) continue; } else if(~Trolls[throwMagnetIndex].activeFlagClients[i] & view_as(type)) { // Skip if client does not have flag continue; } checkList.Push(i); } } int target = -1; if(checkList.Length > 0) { target = checkList.Get(0, checkList.Length - 1); } delete checkList; return target; } stock bool CanSeePoint(const float origin[3], const float point[3]) { TR_TraceRay(origin, point, MASK_ALL, RayType_EndPoint); return !TR_DidHit(); // Can see point if no collisions } stock LookAtPoint(int entity, const float destination[3]){ float angles[3], pos[3], result[3]; GetEntPropVector(entity, Prop_Send, "m_vecOrigin", pos); MakeVectorFromPoints(destination, pos, result); GetVectorAngles(result, angles); if(angles[0] >= 270){ angles[0] -= 270; angles[0] = (90-angles[0]); }else{ if(angles[0] <= 90){ angles[0] *= -1; } } angles[1] -= 180; TeleportEntity(entity, NULL_VECTOR, angles, NULL_VECTOR); } stock void PrintToConsoleAdmins(const char[] format, any ...) { char buffer[254]; VFormat(buffer, sizeof(buffer), format, 2); for(int i = 1; i <= MaxClients; i++) { if(IsClientConnected(i) && IsClientInGame(i)) { AdminId admin = GetUserAdmin(i); if(admin != INVALID_ADMIN_ID) { PrintToConsole(i, "%s", buffer); } } } PrintToServer("%s", buffer); } /** * Shakes a client's screen with the specified amptitude, * frequency & duration. * * @param client Client Index. * @param amplitude Shake magnitude/amplitude. * @param frequency Shake noise frequency. * @param duration Shake lasts this long. * @return True on success, false otherwise. */ stock bool ShakePlayer(int client, float amplitude=50.0, float frequency=150.0, float duration=3.0) { if (amplitude <= 0.0) { return false; } Handle userMessage = StartMessageOne("Shake", client); if (userMessage == INVALID_HANDLE) { return false; } if (GetFeatureStatus(FeatureType_Native, "GetUserMessageType") == FeatureStatus_Available && GetUserMessageType() == UM_Protobuf) { PbSetInt(userMessage, "command", 0); PbSetFloat(userMessage, "local_amplitude", amplitude); PbSetFloat(userMessage, "frequency", frequency); PbSetFloat(userMessage, "duration", duration); } else { BfWriteByte(userMessage, 0); // Shake Command BfWriteFloat(userMessage, amplitude); // shake magnitude/amplitude BfWriteFloat(userMessage, frequency); // shake noise frequency BfWriteFloat(userMessage, duration); // shake lasts this long } EndMessage(); return true; } void SetSlot(int client, int slot) { if(slot == -1) slot = GetRandomInt(0, 4); static char slotStr[8]; Format(slotStr, sizeof(slotStr), "slot%d", slot); ClientCommand(client, slotStr); } void RewindPlayer(int client, float distance = 100.0) { float curFlow = L4D2Direct_GetFlowDistance(client); ArrayList navs = new ArrayList(); L4D_GetAllNavAreas(navs); navs.Sort(Sort_Random, Sort_Integer); float minFlow = curFlow - (3.0*distance); float maxFlow = curFlow - (1.5*distance); // This finds the first nav area in range, usually closer for(int i = 0; i < navs.Length; i++) { float flow = L4D2Direct_GetTerrorNavAreaFlow(navs.Get(i)); if(flow >= minFlow && flow <= maxFlow) { float pos[3]; L4D_FindRandomSpot(navs.Get(i), pos); TeleportEntity(client, pos, NULL_VECTOR, NULL_VECTOR); L4D_WarpToValidPositionIfStuck(client); break; } } delete navs; }