diff --git a/plugins/l4d2_extraplayeritems.smx b/plugins/l4d2_extraplayeritems.smx index 78d8c7f..ef69b65 100644 Binary files a/plugins/l4d2_extraplayeritems.smx and b/plugins/l4d2_extraplayeritems.smx differ diff --git a/plugins/l4d2_feedthetrolls.smx b/plugins/l4d2_feedthetrolls.smx index 1d530e5..94467d2 100644 Binary files a/plugins/l4d2_feedthetrolls.smx and b/plugins/l4d2_feedthetrolls.smx differ diff --git a/plugins/l4d2_hats.smx b/plugins/l4d2_hats.smx index 2d24ad6..55eba8b 100644 Binary files a/plugins/l4d2_hats.smx and b/plugins/l4d2_hats.smx differ diff --git a/plugins/sm_player_notes.smx b/plugins/sm_player_notes.smx index 43c9937..5ac1d3d 100644 Binary files a/plugins/sm_player_notes.smx and b/plugins/sm_player_notes.smx differ diff --git a/scripting/L4D2Tools.sp b/scripting/L4D2Tools.sp index 5125faa..2355abf 100644 --- a/scripting/L4D2Tools.sp +++ b/scripting/L4D2Tools.sp @@ -5,20 +5,22 @@ #define PLUGIN_VERSION "1.0" -#define PRECACHE_SOUNDS_COUNT 5 +#define PRECACHE_SOUNDS_COUNT 6 char PRECACHE_SOUNDS[PRECACHE_SOUNDS_COUNT][] = { "custom/meow1.mp3", "custom/xen_teleport.mp3", "custom/mariokartmusic.mp3", "custom/spookyscaryskeletons.mp3", - "custom/wearenumberone2.mp3" + "custom/wearenumberone2.mp3", + "custom/quack.mp3" }; #include #include #include #include -#include +#undef REQUIRE_PLUGIN +#tryinclude #include #include "l4d_survivor_identity_fix.inc" @@ -177,7 +179,7 @@ Action Timer_CheckPlayerPings(Handle timer) { if(iHighPingCount[i]++ > 2) { PrintToChat(i, "Due to your high ping (%d ms) you have been moved to AFK.", ping); PrintToChat(i, "You will be automatically switched back once your ping restores"); - SDKCall(hGoAwayFromKeyboard, i); + // SDKCall(hGoAwayFromKeyboard, i); //PrintToChat(i, "Type /pingignore to disable this feature."); // L4D_ReplaceWithBot(i); isHighPingIdle[i] = true; @@ -462,7 +464,7 @@ void SetCharacter(int target, int survivorIndex, L4DModelId modelIndex, bool kee if (IsFakeClient(target)) { char name[32]; GetSurvivorName(target, name, sizeof(name)); - SetClientInfo(target, "name", name); + // SetClientInfo(target, "name", name); } UpdatePlayerIdentity(target, view_as(survivorIndex), keepModel); @@ -636,6 +638,7 @@ public void OnConfigsExecuted() { } } +#if defined _sceneprocessor_included public void OnSceneStageChanged(int scene, SceneStages stage) { if(stage == SceneStage_Started) { int activator = GetSceneInitiator(scene); @@ -650,6 +653,7 @@ public void OnSceneStageChanged(int scene, SceneStages stage) { } } } +#endif ///AFK BOT WEAPON FIX public void Event_BotPlayerSwap(Event event, const char[] name, bool dontBroadcast) { int bot = GetClientOfUserId(event.GetInt("bot")); diff --git a/scripting/include/feedthetrolls.inc b/scripting/include/feedthetrolls.inc index 7af8ceb..4b5087e 100644 --- a/scripting/include/feedthetrolls.inc +++ b/scripting/include/feedthetrolls.inc @@ -15,4 +15,22 @@ native void ApplyTroll(int victim, const char[] name, TrollModifier modifier = T forward void OnTrollApplied(int victim, const char[] trollName, int flags = 0, int activator = 0); -forward void OnTrollMarked(int activator, int victim); \ No newline at end of file +forward void OnTrollMarked(int activator, int victim); + +public SharedPlugin __pl_myfile = +{ + name = "feedthetrolls", + file = "feedthetrolls.smx", +#if defined REQUIRE_PLUGIN + required = 1, +#else + required = 0, +#endif +}; + +#if !defined REQUIRE_PLUGIN +public void __pl_myfile_SetNTVOptional() +{ + MarkNativeAsOptional("ApplyTroll"); +} +#endif \ No newline at end of file diff --git a/scripting/include/feedthetrolls/base.inc b/scripting/include/feedthetrolls/base.inc index ffd8d1c..edd6948 100644 --- a/scripting/include/feedthetrolls/base.inc +++ b/scripting/include/feedthetrolls/base.inc @@ -4,7 +4,7 @@ //Allow MAX_TROLLS to be defined elsewhere #if defined MAX_TROLLS #else - #define MAX_TROLLS 56 + #define MAX_TROLLS 55 #endif Troll t_metaReverse; @@ -21,13 +21,15 @@ enum TrollEffectResponse { TE_Error, // Error, continue menu (retry) TE_Menu // Switching menus / etc, don't continue menu } - +typeset PromptActivateFunction { + function TrollEffectResponse (Troll troll, int activator, int victim, any data, int flags, trollModifier mod) + function void (Troll troll, int activator, int victim, any data, int flags, trollModifier mod) +} typedef ActivateFunction = function void (Troll troll, int activator, int victim, int flags, trollModifier mod); typedef ResetFunction = function void (Troll troll, int activator, int victim); -typedef PromptActivateFunction = function TrollEffectResponse (Troll troll, int activator, int victim, any data, int flags, trollModifier mod); +// typedef PromptActivateFunction = function TrollEffectResponse (Troll troll, int activator, int victim, any data, int flags, trollModifier mod); StringMap trollKV; -char trollIds[MAX_TROLLS+1][MAX_TROLL_NAME_LENGTH]; char DEFAULT_FLAG_PROMPT_MULTIPLE[] = "Enable options (Multiple)"; char DEFAULT_FLAG_PROMPT[] = "Select an option"; bool SilentMenuSelected[MAXPLAYERS+1]; @@ -91,45 +93,12 @@ enum struct TrollData { Handle timerHandles[MAXPLAYERS+1]; float timerInterval; int timerRequiredFlags; + bool timerIsDataPack; - void Toggle(int client, int flags) { - if(this.IsActive(client)) { - this.Disable(client); - } else { - this.Enable(client, flags); - } - } - - void Enable(int client, int flags) { - this.activeFlagClients[client] = flags; - // If a timer is assigned, start it: - if(this.timerHandles[client] != null) { - delete this.timerHandles[client]; - PrintToServer("FTT Debug: Old timer for %N, killing", client); - } - if(this.timerInterval > 0.0) { - this.timerHandles[client] = CreateTimer(this.timerInterval, this.timerFunction, GetClientUserId(client), TIMER_REPEAT); - } - } - - void Disable(int client) { - this.activeFlagClients[client] = -1; - // Stop any running timer: - if(this.timerHandles[client] != null) { - PrintToServer("FTT Debug: Disabling timer for %N", client); - delete this.timerHandles[client]; - } - if(this.resetFn != null) { - Call_StartForward(this.resetFn); - Call_PushCell(0); - Call_PushCell(client); - Call_PushCell(0); - Call_Finish(); - } - } - + // TODO: REMOVE OLD bool IsActive(int client) { - return this.activeFlagClients[client] != -1; + if(this.id == 0 || client == 0) return false; // bug fix + return this.activeFlagClients[client] >= 0; } } @@ -170,6 +139,12 @@ methodmap Troll { LogError("Unknown troll \"%s\"", name); return view_as(i); } + public static bool TryFromName(const char[] name, Troll &troll) { + int i = GetTrollID(name); + if(i > -1) + troll = Troll(i); + return i > -1; + } property bool Hidden { public get() { return Trolls[this.Id].hidden; } } @@ -194,8 +169,13 @@ methodmap Troll { public get() { return this.TotalOptionsCount > 0; } } - public bool IsActive(int client) { - return Trolls[this.Id].activeFlagClients[client] != -1; + /// Is troll active for client. If flags is > 0, will do bitwise and + public bool IsActive(int client, int flags = 0) { + if(this.Id == 0 || client == 0) return false; // bug fix + if(flags > 0) { + return (Trolls[this.Id].activeFlagClients[client] & flags) == flags; + } else + return Trolls[this.Id].activeFlagClients[client] >= 0; } public bool HasFlag(int client, int flag) { @@ -203,7 +183,7 @@ methodmap Troll { } public int GetFlags(int client) { - return Trolls[this.Id].activeFlagClients[client] + return Trolls[this.Id].activeFlagClients[client]; } public bool HasMod(trollModifier mod) { @@ -215,18 +195,23 @@ methodmap Troll { } public TrollEffectResponse Activate(int activator, int victim, trollModifier modifier = TrollMod_Invalid, int flags = 0, bool silent = false) { - PrintToServer("Activate: act:%d vic:%d", activator, victim); if(modifier == TrollMod_Invalid) modifier = this.GetDefaultMod(); + if(victim == 0) ThrowError("Victim is invalid"); return ApplyTroll(victim, this, activator, modifier, flags, silent); } + public void Reset(int victim) { Trolls[this.Id].activeFlagClients[victim] = -1; + // Stop any running timer: + if(Trolls[this.Id].timerHandles[victim] != null) { + PrintToServer("FTT Debug: Disabling timer for %N", victim); + delete Trolls[this.Id].timerHandles[victim]; + } if(Trolls[this.Id].resetFn != null) { Call_StartForward(Trolls[this.Id].resetFn); - Call_PushCell(this); + Call_PushCell(Troll(this.Id)); Call_PushCell(0); Call_PushCell(victim); - Call_PushCell(0); Call_Finish(); } } @@ -310,6 +295,43 @@ methodmap Troll { else if(Trolls[this.Id].mods == view_as(TrollMod_Constant)) return TrollMod_Constant; else return TrollMod_Invalid; } + + public TrollEffectResponse _triggerActivateFn(int activator, int victim, int flags, trollModifier modifier) { + if(Trolls[this.Id].activateFn == null) return; + Call_StartForward(Trolls[this.Id].activateFn); + Call_PushCell(this); + Call_PushCell(activator); + Call_PushCell(victim); + Call_PushCell(flags); + Call_PushCell(modifier); + Call_Finish(); + + // TrollFlagPrompt prompt; + // for(int i = 0; i < Trolls[trollIndex].flagPrompts.Length; i++) { + // Trolls[trollIndex].flagPrompts.GetArray(i, prompt); + // if(!prompt.multiselect && prompt.activateFn != null) { + // int value; + // instance.GetPromptDataInt(victim, i, value); + // for(int j = 0; j < Trolls[trollIndex].promptOptions.Length; j++) { + // int bit = 1 << j; + // if(flags & bit && prompt.flags & bit) { + // Call_StartForward(prompt.activateFn); + // Call_PushCell(instance); + // Call_PushCell(activator); + // Call_PushCell(victim); + // Call_PushCell(value); + // Call_PushCell(flags); + // Call_PushCell(modifier); + // response = view_as(Call_Finish()); + // if(response != TE_Success) return response; // Let the menu handler deal with checking + // break; + // } + // } + // break; + // } + // } + // return + } } int g_iTrollIndex; @@ -349,16 +371,22 @@ methodmap TrollBuilder { strcopy(Trolls[this.Id].description, 128, description); } - public TrollBuilder SetTimer(float interval, Timer timer, int requiredFlags = 0) { + public TrollBuilder SetTimer(float interval, Timer timer, int requiredFlags = 0, bool isDatapack = false) { Trolls[this.Id].timerInterval = interval; Trolls[this.Id].timerFunction = timer; Trolls[this.Id].timerRequiredFlags = requiredFlags; + Trolls[this.Id].timerIsDataPack = isDatapack; + // Don't think this is necessary but whatever for(int i = 0; i <= MAXPLAYERS; i++) { Trolls[this.Id].timerHandles[i] = null; } return this; } + public TrollBuilder SetAutoTimer(float interval, int requiredFlags = 0) { + this.SetTimer(interval, Timer_GenericTrollActivate, requiredFlags, true); + } + public TrollBuilder AddPrompt(const char[] customPrompt = "", int requiredFlags = 0) { this._AddPrompt(false, requiredFlags, customPrompt); return this; @@ -436,7 +464,7 @@ methodmap TrollBuilder { } - public TrollBuilder SetActivationFunction(ActivateFunction fn) { + public TrollBuilder OnActivate(ActivateFunction fn) { if(Trolls[this.Id].activateFn == null) { Trolls[this.Id].activateFn = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell); } @@ -444,7 +472,7 @@ methodmap TrollBuilder { return this; } - public TrollBuilder SetResetFunction(ResetFunction fn) { + public TrollBuilder OnReset(ResetFunction fn) { if(Trolls[this.Id].resetFn == null) { Trolls[this.Id].resetFn = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell, Param_Cell); } @@ -458,6 +486,7 @@ methodmap TrollBuilder { } + int GetTrollID(const char[] name) { static int i = 0; char buffer[MAX_TROLL_NAME_LENGTH]; @@ -471,21 +500,12 @@ int GetTrollID(const char[] name) { } bool IsAnyTrollActive(int victim) { - for(int i = 0; i <= MAX_TROLLS; i++) { - if(Trolls[i].activeFlagClients[victim] >= 0) return true; + for(int i = 1; i <= MAX_TROLLS; i++) { + if(Troll(i).IsActive(victim)) return true; } return false; } -void SetTrollFlags(int client, const char[] name, int flags = -1) { - int index = GetTrollID(name); - if(flags == -1) - Trolls[index].Disable(client); - else - Trolls[index].Enable(client, flags); -} - - TrollEffectResponse ApplyTroll(int victim, Troll troll, int activator, trollModifier modifier, int flags = 0, bool silent = false) { char name[MAX_TROLL_NAME_LENGTH]; troll.GetName(name, sizeof(name)); @@ -497,7 +517,16 @@ TrollEffectResponse ApplyTroll(int victim, Troll troll, int activator, trollModi if(troll.HasTimer) { if(!isActive) { if(modifier & TrollMod_Constant && (Trolls[trollIndex].timerRequiredFlags == 0 || Trolls[trollIndex].timerRequiredFlags & flags)) { - Trolls[trollIndex].timerHandles[victim] = CreateTimer(Trolls[trollIndex].timerInterval, Trolls[trollIndex].timerFunction, victim, TIMER_REPEAT); + if(Trolls[trollIndex].timerIsDataPack) { + DataPack pack; + Trolls[trollIndex].timerHandles[victim] = CreateDataTimer(Trolls[trollIndex].timerInterval, Trolls[trollIndex].timerFunction, pack, TIMER_REPEAT); + pack.WriteCell(troll); + pack.WriteCell(activator); + pack.WriteCell(victim); + pack.WriteCell(flags); + } else { + Trolls[trollIndex].timerHandles[victim] = CreateTimer(Trolls[trollIndex].timerInterval, Trolls[trollIndex].timerFunction, victim, TIMER_REPEAT); + } } } else if(Trolls[trollIndex].timerHandles[victim] != null) { delete Trolls[trollIndex].timerHandles[victim]; @@ -517,7 +546,7 @@ TrollEffectResponse ApplyTroll(int victim, Troll troll, int activator, trollModi // If victim is a survivor bot, check if has an idle player if(IsFakeClient(victim) && GetClientTeam(victim) == 2) { - int player = GetSpectatorClient(victim); + int player = GetRealClient(victim); if(player > 0) { // If there is an idle player, apply troll to them ApplyTroll(player, troll, activator, modifier, flags, silent); @@ -530,8 +559,10 @@ TrollEffectResponse ApplyTroll(int victim, Troll troll, int activator, trollModi // Toggle on flags for client, if it's not a single run. - if(modifier & TrollMod_Constant) { - Trolls[trollIndex].activeFlagClients[victim] = isActive ? -1 : flags; + if(isActive) { + Trolls[trollIndex].activeFlagClients[victim] = -1; + } else if(modifier & TrollMod_Constant) { + Trolls[trollIndex].activeFlagClients[victim] = flags; } // Applies any custom logic needed for a troll, mostly only used for TrollMod_Instant @@ -541,15 +572,7 @@ TrollEffectResponse ApplyTroll(int victim, Troll troll, int activator, trollModi // Invoke Callbacks: if(!isActive) { Troll instance = Troll(trollIndex); - if(Trolls[trollIndex].activateFn != null) { - Call_StartForward(Trolls[trollIndex].activateFn); - Call_PushCell(instance); - Call_PushCell(activator); - Call_PushCell(victim); - Call_PushCell(flags); - Call_PushCell(modifier); - Call_Finish(); - } + instance._triggerActivateFn(activator, victim, flags, modifier); // Call the corresponding prompt callback if applicable TrollFlagPrompt prompt; @@ -558,7 +581,7 @@ TrollEffectResponse ApplyTroll(int victim, Troll troll, int activator, trollModi if(!prompt.multiselect && prompt.activateFn != null) { int value; instance.GetPromptDataInt(victim, i, value); - for(int j = 0; j < Trolls[trollIndex].promptOptions.Length; i++) { + for(int j = 0; j < Trolls[trollIndex].promptOptions.Length; j++) { int bit = 1 << j; if(flags & bit && prompt.flags & bit) { Call_StartForward(prompt.activateFn); @@ -582,7 +605,6 @@ TrollEffectResponse ApplyTroll(int victim, Troll troll, int activator, trollModi Call_PushCell(Troll(trollIndex)); Call_PushCell(activator); Call_PushCell(victim); - Call_PushCell(modifier); Call_Finish(); } @@ -611,11 +633,11 @@ TrollEffectResponse ApplyTroll(int victim, Troll troll, int activator, trollModi void EnableTroll(int client, const char[] troll, int flags = 0) { - SetTrollFlags(client, troll, flags); + Troll.FromName(troll).Activate(0, client, TrollMod_Invalid, flags); } void DisableTroll(int client, const char[] troll) { - SetTrollFlags(client, troll, -1); + Troll.FromName(troll).Reset(client); } public void SetCategory(const char[] newCat) { @@ -627,7 +649,6 @@ void GetCategory(int category, char[] buffer, int size) { categories.GetString(category, buffer, size); } - public int Native_ApplyTroll(Handle plugin, int numParams) { int victim = GetNativeCell(1); char name[MAX_TROLL_NAME_LENGTH]; @@ -640,7 +661,7 @@ public int Native_ApplyTroll(Handle plugin, int numParams) { int activator = GetNativeCell(5); Troll troll = Troll.FromName(name); - troll.Activate(victim, activator, modifier, flags, GetNativeCell(6)); + troll.Activate(activator, victim, modifier, flags, GetNativeCell(6)); return 0; } \ No newline at end of file diff --git a/scripting/include/feedthetrolls/combos.inc b/scripting/include/feedthetrolls/combos.inc index aecc340..498d053 100644 --- a/scripting/include/feedthetrolls/combos.inc +++ b/scripting/include/feedthetrolls/combos.inc @@ -6,39 +6,45 @@ enum struct SpecifiedTroll { trollModifier mod; int flags; } -enum struct TrollCombo { +enum struct TrollComboData { char name[32]; ArrayList trolls; - void AddTroll(const char[] name, int flags = 0, trollModifier mod = TrollMod_Invalid) { - Troll instance = Troll.FromName(name); + bool AddTroll(const char[] name, int flags = 0, trollModifier mod = TrollMod_Invalid) { + Troll instance; + if(!Troll.TryFromName(name, instance)) { + PrintToServer("[FTT] Combo \"%s\": unknown troll named \"%s\"", this.name, name); + return false; + } if(mod == TrollMod_Invalid) mod = instance.GetDefaultMod(); SpecifiedTroll troll; troll.id = instance.Id; troll.mod = mod; troll.flags = flags; this.trolls.PushArray(troll, sizeof(troll)); + return true; } void Activate(int client, int target) { + PrintToServer("Applying %d trolls for combo %s for %N", this.trolls.Length, this.name, target); + SpecifiedTroll troll; for(int i = 0; i < this.trolls.Length; i++) { - SpecifiedTroll troll; this.trolls.GetArray(i, troll, sizeof(troll)); - Troll(troll.id).Activate(target, client, troll.mod, troll.flags); + Troll(troll.id).Activate(client, target, troll.mod, troll.flags); } } } -void SetupCombo(TrollCombo combo, const char[] name) { +void SetupCombo(TrollComboData combo, const char[] name) { strcopy(combo.name, sizeof(combo.name), name); combo.trolls = new ArrayList(sizeof(SpecifiedTroll)); combos.PushArray(combo, sizeof(combo)); } void SetupsTrollCombos() { - combos = new ArrayList(sizeof(TrollCombo)); + combos = new ArrayList(sizeof(TrollComboData)); - TrollCombo combo; + TrollComboData combo; SetupCombo(combo, "Magnet Galore"); combo.AddTroll("Special Magnet"); combo.AddTroll("Tank Magnet"); @@ -79,7 +85,7 @@ void SetupsTrollCombos() { SetupCombo(combo, "Shut up"); combo.AddTroll("Vocalize Gag"); - combo.AddTroll("Honk / Meow / Woof", .flags=1); + combo.AddTroll("Honk & Animal Sounds", .flags=1); SetupCombo(combo, "Weakness Compels You"); combo.AddTroll("No Shove"); diff --git a/scripting/include/feedthetrolls/commands.inc b/scripting/include/feedthetrolls/commands.inc index c23dbba..1beac8b 100644 --- a/scripting/include/feedthetrolls/commands.inc +++ b/scripting/include/feedthetrolls/commands.inc @@ -383,10 +383,7 @@ public Action Command_ListTheTrolls(int client, int args) { char buffer[50]; for(int p = 0; p < target_count; p++) { int target = target_list[p]; - if(IsPlayerAlive(target)) - ReplyToCommand(client, "> Active Trolls for %N:", target); - else - ReplyToCommand(client, "> Active Trolls for %N: (Paused)", target); + CReplyToCommand(client, "> Active Trolls for {olive}%N:", target); if(IsFakeClient(target)) { int player = GetRealClient(target); @@ -401,9 +398,9 @@ public Action Command_ListTheTrolls(int client, int args) { if(flags > 0) { buffer[0] = '\0'; troll.GetFlagNames(target, flags, buffer, sizeof(buffer)); - ReplyToCommand(client, "\"%s\" Flags: %s", Trolls[troll.Id].name, buffer); + CReplyToCommand(client, "\t{green}%s:{default} %s", Trolls[troll.Id].name, buffer); } else - ReplyToCommand(client, "%s", Trolls[troll.Id].name); + CReplyToCommand(client, "\t{green}%s", Trolls[troll.Id].name); } } } @@ -411,31 +408,29 @@ public Action Command_ListTheTrolls(int client, int args) { } int count = 0; - char[][] modeListArr = new char[MAX_TROLLS+1][MAX_TROLL_NAME_LENGTH]; - static char modeList[255]; - for(int i = 1; i <= MaxClients; i++) { - if(IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) > 1 && IsAnyTrollActive(i)) { - if(IsFakeClient(i)) { - int player = GetRealClient(i); - if(player != -1) i = player; + char[][] bufferList = new char[MAX_TROLLS+1][MAX_TROLL_NAME_LENGTH]; + char buffer[255]; + for(int player = 1; player <= MaxClients; player++) { + if(IsClientConnected(player) && IsClientInGame(player) && GetClientTeam(player) > 1 && IsAnyTrollActive(player)) { + if(IsFakeClient(player)) { + int realPlayer = GetRealClient(player); + if(realPlayer != -1) player = realPlayer; } - int modeCount = 0; - for(int j = 1; j <= MAX_TROLLS; j++) { + int trollCount = 0; + for(int j = 1; j < MAX_TROLLS; j++) { Troll troll = Troll(j); - if(troll.IsActive(i)) { - if(troll.GetFlags(i) > 0) - Format(modeListArr[modeCount], MAX_TROLL_NAME_LENGTH, "%s(%d)", trollIds[j], Trolls[j].activeFlagClients[i]); + if(troll.IsActive(player)) { + int flags = troll.GetFlags(player); + if(flags > 0) + Format(bufferList[trollCount], MAX_TROLL_NAME_LENGTH, "%s(%d)", Trolls[j].name, flags); else - strcopy(modeListArr[modeCount], MAX_TROLL_NAME_LENGTH, trollIds[j]); - modeCount++; + strcopy(bufferList[trollCount], MAX_TROLL_NAME_LENGTH, Trolls[j].name); + trollCount++; } } - ImplodeStrings(modeListArr, modeCount, ", ", modeList, sizeof(modeList)); - if(IsPlayerAlive(i)) - ReplyToCommand(client, "%N | %s", i, modeList); - else - ReplyToCommand(client, "%N (Paused) | %s", i, modeList); + ImplodeStrings(bufferList, trollCount, ", ", buffer, sizeof(buffer)); + ReplyToCommand(client, "%N | %s", player, buffer); count++; } } diff --git a/scripting/include/feedthetrolls/events.inc b/scripting/include/feedthetrolls/events.inc index 5885944..2117fab 100644 --- a/scripting/include/feedthetrolls/events.inc +++ b/scripting/include/feedthetrolls/events.inc @@ -196,7 +196,7 @@ void Event_PlayerDisconnect(Event event, const char[] name, bool dontBroadcast) int client = GetClientOfUserId(event.GetInt("userid")); if(client > 0) { for(int i = 0 ; i < MAX_TROLLS; i++) { - Trolls[i].activeFlagClients[client] = 0; + Trolls[i].activeFlagClients[client] = -1; if(Trolls[i].timerHandles[client] != null) { delete Trolls[i].timerHandles[client]; } @@ -321,10 +321,6 @@ public Action RushPlayer(Handle h, int user) { return Plugin_Handled; } public Action L4D2_OnChooseVictim(int attacker, int &curTarget) { - static int spMagnetID, tankMagnetID; - if(spMagnetID == 0) spMagnetID = GetTrollID("Special Magnet"); - if(tankMagnetID == 0) tankMagnetID = GetTrollID("Tank Magnet"); - L4D2Infected class = view_as(GetEntProp(attacker, Prop_Send, "m_zombieClass")); // Check for any existing victims int existingTarget = GetClientOfUserId(pdata[attacker].attackerTargetUid); @@ -337,16 +333,16 @@ public Action L4D2_OnChooseVictim(int attacker, int &curTarget) { } // Stop targetting if no longer magnetted: if(class == L4D2Infected_Tank) { - if(!Trolls[tankMagnetID].IsActive(existingTarget) || !WillMagnetRun(Trolls[tankMagnetID], existingTarget)) return Plugin_Continue; + if(!t_tankMagnet.IsActive(existingTarget) || !WillMagnetRun(t_tankMagnet, existingTarget)) return Plugin_Continue; } else if(class != L4D2Infected_Tank) { - if(!Trolls[spMagnetID].IsActive(existingTarget) || !WillMagnetRun(Trolls[spMagnetID], existingTarget)) return Plugin_Continue; + if(!t_specialMagnet.IsActive(existingTarget) || !WillMagnetRun(t_specialMagnet, existingTarget)) return Plugin_Continue; } // Only set target based on incap rules: - if(class == L4D2Infected_Tank && (!IsPlayerIncapped(existingTarget) || hMagnetTargetMode.IntValue & 2) && WillMagnetRun(Trolls[tankMagnetID], existingTarget)) { + if(class == L4D2Infected_Tank && (!IsPlayerIncapped(existingTarget) || hMagnetTargetMode.IntValue & 2) && WillMagnetRun(t_tankMagnet, existingTarget)) { curTarget = existingTarget; return Plugin_Changed; - } else if(class != L4D2Infected_Tank && (!IsPlayerIncapped(existingTarget) || hMagnetTargetMode.IntValue & 1) && WillMagnetRun(Trolls[spMagnetID], existingTarget)) { + } else if(class != L4D2Infected_Tank && (!IsPlayerIncapped(existingTarget) || hMagnetTargetMode.IntValue & 1) && WillMagnetRun(t_specialMagnet, existingTarget)) { curTarget = existingTarget; return Plugin_Changed; } @@ -367,9 +363,9 @@ public Action L4D2_OnChooseVictim(int attacker, int &curTarget) { for(int i = 1; i <= MaxClients; i++) { if(IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) == 2 && IsPlayerAlive(i)) { if(class == L4D2Infected_Tank) { - if(!Trolls[tankMagnetID].IsActive(i) || !WillMagnetRun(Trolls[tankMagnetID], i)) continue; + if(!t_tankMagnet.IsActive(i) || !WillMagnetRun(t_tankMagnet, i)) continue; } else if(class != L4D2Infected_Tank) { - if(!Trolls[spMagnetID].IsActive(i) || !WillMagnetRun(Trolls[spMagnetID], i)) continue; + if(!t_specialMagnet.IsActive(i) || !WillMagnetRun(t_specialMagnet, i)) continue; } if(IsPlayerIncapped(i)) { @@ -395,26 +391,21 @@ public Action L4D2_OnChooseVictim(int attacker, int &curTarget) { return Plugin_Continue; } -// TODO: migrate to Troll -bool WillMagnetRun(const TrollData troll, int i) { +bool WillMagnetRun(Troll troll, int client) { // In the case none of the flags are set, return true (100% chance) // Some systems may give magnet w/ no flags - if(troll.activeFlagClients[i] == 0) return true; - - float cChance = 1.0; - //Skip first bit as it is ('Always') - if(troll.activeFlagClients[i] & 2) // 2nd: 50% - cChance = 0.5; - else if(troll.activeFlagClients[i] & 4) //3rd: 10% - cChance = 0.1; - return GetRandomFloat() <= cChance; + int flags = troll.GetFlags(client); + if(flags == 0) return true; + float chance = 1.0; + troll.GetPromptDataFloat(client, 0, chance); + return GetRandomFloat() <= chance; } public Action L4D2_OnEntityShoved(int client, int entity, int weapon, float vecDir[3], bool bIsHighPounce) { if(client > 0 && client <= MaxClients) { - static int noShoveIndex; - if(noShoveIndex == 0) noShoveIndex == GetTrollID("No Shove"); - if(Trolls[noShoveIndex].IsActive(client) && GetRandomFloat() < hShoveFailChance.FloatValue) { + static Troll t_noShove; + if(t_noShove.Id == 0) t_noShove == Troll.FromName("No Shove"); + if(t_noShove.IsActive(client) && GetRandomFloat() < hShoveFailChance.FloatValue) { float shoveTime = L4D2Direct_GetNextShoveTime(client); L4D2Direct_SetNextShoveTime(client, shoveTime + 2.0); return Plugin_Handled; @@ -426,14 +417,12 @@ public Action L4D2_OnEntityShoved(int client, int entity, int weapon, float vecD public Action OnClientSayCommand(int client, const char[] command, const char[] sArgs) { if(client <= 0 || sArgs[0] == '@') return Plugin_Continue; //Ignore admin chat or console - static int honkID; static int profanityID; static int typooId; - if(honkID == 0) honkID = GetTrollID("Honk / Meow / Woof"); if(profanityID == 0) profanityID = GetTrollID("No Profanity"); if(typooId == 0) typooId = GetTrollID("Typoos"); - if(Trolls[honkID].IsActive(client) && Trolls[honkID].activeFlagClients[client] & 1) { + if(t_honk.IsActive(client) && t_honk.GetFlags(client) & 1) { // Honk Processing static char strings[32][8]; int words = ExplodeString(sArgs, " ", strings, sizeof(strings), 5); @@ -446,17 +435,14 @@ public Action OnClientSayCommand(int client, const char[] command, const char[] int length = 8 * words; char[] message = new char[length]; ImplodeStrings(strings, 32, " ", message, length); - if(Trolls[honkID].activeFlagClients[client] & 1) + if(t_honk.HasFlag(client, 16)) { + // Show modified to them CPrintToChatAll("{blue}%N {default}: %s", client, message); - else { + } else { CPrintToChat(client, "{blue}%N {default}: %s", client, message); - bool showOriginalToOthers = Trolls[honkID].activeFlagClients[client] & 4 != 0; for(int i = 1; i <= MaxClients; i++) { if(IsClientConnected(i) && IsClientInGame(i) && i != client) { - if(showOriginalToOthers) - CPrintToChat(i, "{blue}%N {default}: %s", client, sArgs); - else - CPrintToChat(i, "{blue}%N {default}: %s", client, message); + CPrintToChat(i, "{blue}%N {default}: %s", client, sArgs); } } } @@ -742,7 +728,7 @@ public Action OnPlayerRunCmd(int client, int& buttons, int& impulse, float vel[3 } // Inverted control code: - if(Trolls[t_invertedTrollIndex].IsActive(client)) { + if(t_invertedTroll.IsActive(client)) { if(buttons & IN_MOVELEFT || buttons & IN_MOVERIGHT) { vel[1] = -vel[1]; } @@ -786,7 +772,7 @@ Action Event_TakeDamage(int victim, int& attacker, int& inflictor, float& damage if(GetClientTeam(attacker) == 4 && IsFakeClient(attacker)) return Plugin_Continue; } // Boost all damage no matter what - if(Trolls[t_damageBoostIndex].IsActive(victim)) { + if(t_damageBoost.IsActive(victim)) { damage * 2; return Plugin_Changed; } @@ -804,29 +790,22 @@ Action Event_TakeDamage(int victim, int& attacker, int& inflictor, float& damage if(damage > 0.0 && Trolls[t_slipperyShoesIndex].IsActive(victim) && Trolls[t_slipperyShoesIndex].activeFlagClients[victim] & 16) { L4D_StaggerPlayer(victim, victim, NULL_VECTOR); } - if(isSameTeam && Trolls[t_reverseFFIndex].IsActive(attacker)) { + if(isSameTeam && t_reverseFF.IsActive(attacker)) { // Should this be applied? (as in no FF granted) bool disableFF = false; + int flags = t_reverseFF.GetFlags(attacker); if(damagetype & DMG_BURN) { - disableFF = Trolls[t_reverseFFIndex].activeFlagClients[attacker] & 32 != 0; + disableFF = flags & 64 != 0; } else if(damagetype & DMG_BLAST) { - disableFF = Trolls[t_reverseFFIndex].activeFlagClients[attacker] & 64 != 0; + disableFF = flags & 32 != 0; } else { - // Does not run if DMG_BURN or DMG_BLAST + // Does not run if DMG_BURN or DMG_BLAST, basically any other damage was caused besides burn/blast, then allow it disableFF = true; } if(disableFF) { float returnDmg = damage; //default is 1:1 - if(Trolls[t_reverseFFIndex].activeFlagClients[attacker] & 2) { - returnDmg *= 2.0; - } else if(Trolls[t_reverseFFIndex].activeFlagClients[attacker] & 4) { - returnDmg /= 2.0; - } else if(Trolls[t_reverseFFIndex].activeFlagClients[attacker] & 8) { - returnDmg = 0.0; - } else if(Trolls[t_reverseFFIndex].activeFlagClients[attacker] & 16) { - returnDmg *= 3.0; - } + t_reverseFF.GetPromptDataFloat(attacker, 0, returnDmg); SDKHooks_TakeDamage(attacker, attacker, attacker, returnDmg, damagetype, -1); damage = 0.0; return Plugin_Changed; @@ -863,11 +842,7 @@ Action Event_TakeDamage(int victim, int& attacker, int& inflictor, float& damage } public Action OnVocalizeCommand(int client, const char[] vocalize, int initiator) { - static int vocalGagID; - static int noRushingUsID; - if(vocalGagID == 0) vocalGagID = GetTrollID("Vocalize Gag"); - if(noRushingUsID == 0) noRushingUsID = GetTrollID("No Rushing Us"); - if(Trolls[noRushingUsID].IsActive(client) && (StrEqual(vocalize, "PlayerHurryUp") || StrEqual(vocalize, "PlayerYellRun") || StrEqual(vocalize, "PlayerMoveOn") || StrEqual(vocalize, "PlayerLeadOn"))) { + if(t_noRushingUs.IsActive(client) && (StrEqual(vocalize, "PlayerHurryUp") || StrEqual(vocalize, "PlayerYellRun") || StrEqual(vocalize, "PlayerMoveOn") || StrEqual(vocalize, "PlayerLeadOn"))) { noRushingUsSpeed[client] -= 0.01; if(noRushingUsSpeed[client] < 0.05) { noRushingUsSpeed[client] = 0.05; @@ -878,7 +853,7 @@ public Action OnVocalizeCommand(int client, const char[] vocalize, int initiator if(Trolls[t_slotRouletteIndex].IsActive(client) && GetURandomFloat() < 0.10) { SetSlot(client, -1); } - if(Trolls[vocalGagID].IsActive(client)) { + if(t_vocalGag.IsActive(client) && t_vocalGag.GetFlags(client) == 0) { return Plugin_Handled; } return Plugin_Continue; @@ -922,11 +897,6 @@ public void OnSceneStageChanged(int scene, SceneStages stage) { #endif public Action SoundHook(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) { - static int honkID; - static int vocalGagID; - if(honkID == 0) honkID = GetTrollID("Honk / Meow / Woof"); - if(vocalGagID == 0) vocalGagID = GetTrollID("Vocalize Gag"); - if(lastButtonUser > 0 && IsClientConnected(lastButtonUser) && !IsFakeClient(lastButtonUser) && StrEqual(sample, "npc/mega_mob/mega_mob_incoming.wav")) { PrintToConsoleAll("CRESCENDO STARTED BY %N", lastButtonUser); #if defined DEBUG @@ -943,24 +913,37 @@ public Action SoundHook(int clients[MAXPLAYERS], int& numClients, char sample[PL lastButtonUser = -1; }else if(numClients > 0 && entity > 0 && entity <= MaxClients) { if(StrContains(sample, "survivor\\voice") > -1) { - if(Trolls[honkID].IsActive(entity)) { - if(Trolls[honkID].activeFlagClients[entity] & 1) + if(t_honk.IsActive(entity)) { + int trollFlags = t_honk.GetFlags(entity); + if(trollFlags & 1) { strcopy(sample, sizeof(sample), "player/footsteps/clown/concrete1.wav"); - else if(Trolls[honkID].activeFlagClients[entity] & 2) { + } else if(trollFlags & 2) { + strcopy(sample, sizeof(sample), "custom/quack.mp3"); + // volume += 0.2; + } else if(trollFlags & 4) { strcopy(sample, sizeof(sample), "custom/meow1.mp3"); volume += 0.2; - } else if(Trolls[honkID].activeFlagClients[entity] & 4) { + } else if(trollFlags & 8) { strcopy(sample, sizeof(sample), "custom/woof1.mp3"); volume += 0.6; } else return Plugin_Continue; return Plugin_Changed; - } else if(Trolls[vocalGagID].IsActive(entity)) { - if(Trolls[vocalGagID].activeFlagClients[entity] & 2) { - clients[0] = entity; - numClients = 1; + } else if(t_vocalGag.IsActive(entity)) { + int trollFlags = t_vocalGag.GetFlags(entity); + if(trollFlags & 2) { + SDKHooks_TakeDamage(entity, entity, entity, 1.0, DMG_GENERIC); + } + if(trollFlags & 1) { + volume /= 2.0; return Plugin_Changed; } + + // if(Trolls[vocalGagID].activeFlagClients[entity] & 2) { + // clients[0] = entity; + // numClients = 1; + // return Plugin_Changed; + // } return Plugin_Handled; } } @@ -1008,11 +991,10 @@ public void Event_WitchVictimSet(Event event, const char[] name, bool dontBroadc public Action L4D2_MeleeGetDamageForVictim(int client, int weapon, int victim, float &damage) { static int dullMeleeID; if(!dullMeleeID) dullMeleeID = GetTrollID("Dull Melee"); - if(Trolls[dullMeleeID].IsActive(client)) { - float max = 1.0; - if(Trolls[dullMeleeID].activeFlagClients[client] & 2) max = 0.5; - else if(Trolls[dullMeleeID].activeFlagClients[client] & 4) max = 0.1; - if(GetRandomFloat() <= max) { + if(t_dullMelee.IsActive(client)) { + float chance = 1.0; + t_dullMelee.GetPromptDataFloat(client, 0, chance); + if(GetURandomFloat() <= chance) { damage = 0.0; return Plugin_Changed; } diff --git a/scripting/include/feedthetrolls/menus.inc b/scripting/include/feedthetrolls/menus.inc index cb6992b..528510f 100644 --- a/scripting/include/feedthetrolls/menus.inc +++ b/scripting/include/feedthetrolls/menus.inc @@ -129,7 +129,7 @@ public int ChooseComboHandler(Menu menu, MenuAction action, int client, int para return 0; } - static TrollCombo combo; + static TrollComboData combo; combos.GetArray(comboID, combo, sizeof(combo)); combo.Activate(client, victim); } else if (action == MenuAction_End) @@ -377,7 +377,7 @@ void ShowTrollCombosMenu(int client, int victimUserID) { Format(id, sizeof(id), "Choose troll combo"); comboMenu.SetTitle(id); - static TrollCombo combo; + static TrollComboData combo; if(combos.Length == 0) { ReplyToCommand(client, "FTT: No troll combos available"); @@ -457,7 +457,8 @@ void ShowSelectFlagMenu(int activator, int victimUserID, int modifiers, Troll tr prompt.GetPromptText(info, sizeof(info)); flagMenu.SetTitle("%s", info); - if(prevFlags == -1) prevFlags = prompt.defaults; + if(prevFlags == -1 && prompt.multiselect) prevFlags = prompt.defaults; + Format(info, sizeof(info), "%d|%d|%d|%d|%d|1", victimUserID, troll.Id, modifiers, prevFlags, promptIndex); diff --git a/scripting/include/feedthetrolls/misc.inc b/scripting/include/feedthetrolls/misc.inc index e0bbabc..27f993d 100644 --- a/scripting/include/feedthetrolls/misc.inc +++ b/scripting/include/feedthetrolls/misc.inc @@ -63,18 +63,6 @@ bool ToggleMarkPlayer(int client, int target) { } } -// Finds the survivor bot that took over an idle player -int GetSpectatorClient(int bot) { - if(!IsFakeClient(bot)) return -1; - static char netclass[16]; - GetEntityNetClass(bot, netclass, sizeof(netclass)); - if(strcmp(netclass, "SurvivorBot") == 0 ) { - int user = GetEntProp(bot, Prop_Send, "m_humanSpectatorUserID"); - if(user > 0) return GetClientOfUserId(user); - } - return -1; -} - stock bool IsPlayerIncapped(int client) { return GetEntProp(client, Prop_Send, "m_isIncapacitated") == 1; } @@ -641,13 +629,13 @@ void SetSlot(int client, int slot) { ClientCommand(client, slotStr); } -void RewindPlayer(int client) { +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 - 300.0; - float maxFlow = curFlow - 150.0; + 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)); diff --git a/scripting/include/feedthetrolls/specials.inc b/scripting/include/feedthetrolls/specials.inc index e7e7e0b..dd4574f 100644 --- a/scripting/include/feedthetrolls/specials.inc +++ b/scripting/include/feedthetrolls/specials.inc @@ -154,6 +154,9 @@ Action Timer_SetWitchTarget(Handle h, DataPack pack) { } stock SpecialType GetSpecialType(const char[] input) { + if(StrEqual(input, "random")) { + return view_as(GetRandomInt(0, 6)) + } for(int i = 0; i < 8; i++) { if(strcmp(SPECIAL_NAMES[i], input, false) == 0) return view_as(i + 1); } diff --git a/scripting/include/feedthetrolls/timers.inc b/scripting/include/feedthetrolls/timers.inc index d664b48..876a004 100644 --- a/scripting/include/feedthetrolls/timers.inc +++ b/scripting/include/feedthetrolls/timers.inc @@ -481,8 +481,21 @@ Action Timer_RestoreHud(Handle h, int userid) { } Action Timer_RandomRewind(Handle h, int client) { if(IsClientInGame(client) && GetURandomFloat() > 0.3) { - RewindPlayer(client); + float distance = 100.0; + t_rewind.GetPromptDataFloat(client, 0, distance); + RewindPlayer(client, distance); } return Plugin_Handled; +} +Action Timer_GenericTrollActivate(Handle h, DataPack pack) { + pack.Reset(); + Troll troll = Troll(pack.ReadCell()); + int activator = pack.ReadCell(); + int victim = pack.ReadCell(); + if(IsClientInGame(victim)) { + int flags = pack.ReadCell(); + troll._triggerActivateFn(activator, victim, flags, TrollMod_Constant); + } + return Plugin_Handled; } \ No newline at end of file diff --git a/scripting/include/feedthetrolls/trolls.inc b/scripting/include/feedthetrolls/trolls.inc index 9a7f578..d0e9299 100644 --- a/scripting/include/feedthetrolls/trolls.inc +++ b/scripting/include/feedthetrolls/trolls.inc @@ -1,19 +1,26 @@ // UP THE VALUE 'MAX_TROLLS' in base.inc before adding new ones! +Troll t_specialMagnet; +Troll t_tankMagnet; int t_slipperyShoesIndex = 0; Troll t_stickyGoo; -int t_invertedTrollIndex; +Troll t_invertedTroll; int t_randomizeAnglesIndex; int t_randomizeVelocityIndex; int t_vomitPlayerIndex; int t_shakeyCameraIndex; int t_slotRouletteIndex; -int t_damageBoostIndex; -int t_reverseFFIndex; +Troll t_damageBoost; +Troll t_reverseFF; int t_hideHUDIndex; Troll t_throwItAll; Troll t_voiceMute; Troll t_gunJam; +Troll t_honk; +Troll t_vocalGag; +Troll t_dullMelee; +Troll t_rewind; +Troll t_noRushingUs; void SetupTrolls() { trollKV = new StringMap(); @@ -25,8 +32,10 @@ void SetupTrolls() { SetCategory("Magnets"); troll = TrollBuilder("Special Magnet", "Attracts ALL specials to any alive target with this troll enabled", TrollMod_Constant); AddMagnetFlags(troll); + t_specialMagnet = troll.Build(); troll = TrollBuilder("Tank Magnet", "Attracts ALL tanks to any alive target with this troll enabled", TrollMod_Constant); AddMagnetFlags(troll); + t_tankMagnet = troll.Build(); #if defined _actions_included TrollBuilder("Witch Magnet", "All witches when startled will target any player with this troll", TrollMod_Constant); #endif @@ -112,14 +121,14 @@ void SetupTrolls() { .AddOption("UZI Only", true) .AddOption("AWP Only", false) TrollBuilder("Primary Disable", "Player cannot pickup any weapons, only melee/pistols", TrollMod_Constant); - TrollBuilder("Dull Melee", "Player's melee weapon does 0 damage (based on %). Headshots still work", TrollMod_Constant) + t_dullMelee = TrollBuilder("Dull Melee", "Player's melee weapon does 0 damage (based on %). Headshots still work", TrollMod_Constant) .AddPrompt() - .AddOption("Always (100%)", false) - .AddOption("Half Time (50%)", true) - .AddOption("Rare (10%)", false); + .AddOptionFloat("Always (100%)", false, 1.0) + .AddOptionFloat("Half Time (50%)", true, 0.5) + .AddOptionFloat("Rare (10%)", false, 0.10) + .Build(); TrollBuilder("Nerf Gun", "When they shoot it does no damage.", TrollMod_Constant); TrollBuilder("Randomize Clip Ammo", "Randomly changes their clip ammo downwards", TrollMod_Constant | TrollMod_Instant); - TrollBuilder("CameTooEarly", "When they shoot, random chance they empty whole clip", TrollMod_Constant); t_slotRouletteIndex = TrollBuilder("Slot Roulette", "Randomize their slots", TrollMod_Constant) .SetTimer(0.2, Timer_SlotRoulette, 8) @@ -146,40 +155,42 @@ void SetupTrolls() { .AddPrompt() .AddOption("Show Modified to Them", true) .AddOption("Show Original to Them"); - TrollBuilder("Vocalize Gag", "Prevents player from sending any vocalizations (even automatic)", TrollMod_Constant); - // .AddPrompt() - // .AddOption("Mute for All", true); - // .AddOption("Mute For All But Them", false); - TrollBuilder("Honk / Meow / Woof", "Custom sounds", TrollMod_Constant) + t_vocalGag = TrollBuilder("Vocalize Gag", "Prevents player from sending any vocalizations (even automatic)", TrollMod_Constant) + .AddPromptMulti() + .AddOption("Quieter", false) + .AddOption("Painful", false) + .Build() + t_honk = TrollBuilder("Honk & Animal Sounds", "Custom sounds", TrollMod_Constant) .AddPrompt("Choose Sound:") - .AddOption("Honk", true) - .AddOption("Meow", false) - .AddOption("Woof", false) + .AddOption("Honk", true) // 1 << 0 + .AddOption("Quack", false) // 1 << 1 + .AddOption("Meow", false) // 1 << 2 + .AddOption("Woof", false) // 1 << 3 .AddPrompt("Choose Chat modifier:", 1) - .AddOption("Show Modified to Them", true) - .AddOption("Show Original to Them", false) - .AddOption("Show Modified Only To Them", false); + .AddOption("Show Modified To All", true) // 1 << 4 + .AddOption("Show Original To Others", false) // 1 << 5 + .Build(); TrollBuilder("Reversed", "Reserves their message", TrollMod_Constant); t_voiceMute = TrollBuilder("Voice Mute", "Mutes from voice", TrollMod_Constant).Build(); - TrollBuilder("No Rushing Us", "Decreases player speed everytime they yell hurry up", TrollMod_Constant); + t_noRushingUs = TrollBuilder("No Rushing Us", "Decreases player speed everytime they yell hurry up", TrollMod_Constant).Build(); /// CATEGORY: Health SetCategory("Health"); - t_damageBoostIndex = TrollBuilder("Damage Boost", "Makes a player take more damage than normal", TrollMod_Constant).Id; + t_damageBoost = TrollBuilder("Damage Boost", "Makes a player take more damage than normal", TrollMod_Constant).Build(); TrollBuilder("Temp Health Quick Drain", "Makes a player's temporarily health drain very quickly", TrollMod_Constant); TrollBuilder("Slow Drain", "Will make the player slowly lose health over time", TrollMod_Constant); TrollBuilder("KillMeSoftly", "Make player eat or waste pills whenever possible", TrollMod_Instant | TrollMod_Constant); - t_reverseFFIndex = TrollBuilder("Reverse FF", "All damage dealt to a player is reversed", TrollMod_Constant) + t_reverseFF = TrollBuilder("Reverse FF", "All damage dealt to a player is reversed", TrollMod_Constant) .AddPrompt("Choose Reverse FF", false) - .AddOption("1:1 Ratio", true) //1 - .AddOption("2x Ratio", false) //2 - .AddOption("0.5x Ratio", false) //4 - .AddOption("0.0x Ratio (None)", false) //8 - .AddOption("3x Ratio", false) //16 + .AddOptionFloat("1:1 Ratio", true, 1.0) //1 + .AddOptionFloat("2x Ratio", false, 2.0) //2 + .AddOptionFloat("0.5x Ratio", false, 0.5) //4 + .AddOptionFloat("0.0x Ratio (None)", false, 0.0) //8 + .AddOptionFloat("3x Ratio", false, 3.0) //16 .AddPromptMulti("Modes") .AddOption("Reverse Fire Damage", false) //32 .AddOption("Reverse Explosions", false) //64 - .Id; + .Build(); TrollBuilder("Dep Bots", "Makes bots heal a player. At any cost", TrollMod_Constant) @@ -202,15 +213,19 @@ void SetupTrolls() { /// CATEGORY: Movement SetCategory("Movement"); TrollBuilder("Slow Speed", "Sets player speed to 0.8x of normal speed", TrollMod_Constant) + .OnReset(Reset_SlowSpeed) .AddPrompt() + .OnPromptActivate(Activate_SlowSpeed) .AddOptionFloat("90% Movement Speed", true, 0.9) .AddOptionFloat("80% Movement Speed", false, 0.8) .AddOptionFloat("70% Movement Speed", false, 0.7) .AddOptionFloat("50% Movement Speed", false, 0.5) .AddOptionFloat("30% Movement Speed", false, 0.3) .AddOptionFloat("0% Movement Speed", false, 0.0); - TrollBuilder("Higher Gravity", "Sets player gravity to 1.3x of normal gravity", TrollMod_Constant); - t_invertedTrollIndex = TrollBuilder("Inverted Controls", "Well, aint it obvious", TrollMod_Constant).Id; + TrollBuilder("Higher Gravity", "Sets player gravity to 1.3x of normal gravity", TrollMod_Constant) + .OnActivate(Activate_HighGravity) + .OnReset(Reset_HighGravity); + t_invertedTroll = TrollBuilder("Inverted Controls", "Well, aint it obvious", TrollMod_Constant).Build(); t_slipperyShoesIndex = TrollBuilder("Slippery Shoes", "Periodically stumbles around.", TrollMod_Constant | TrollMod_Instant) .AddPromptMulti() .AddOption("Periodically", true) // 1 << 0 @@ -236,8 +251,16 @@ void SetupTrolls() { .AddOption("Severe Earthquake", false) //8 .AddOption("Bouncy Castle", false) //16 .Id; - TrollBuilder("Rewind", "Teleports player backwards", TrollMod_Instant | TrollMod_Constant) - .SetTimer(10.0, Timer_RandomRewind); + t_rewind = TrollBuilder("Rewind", "Teleports player backwards", TrollMod_Instant | TrollMod_Constant) + // .SetTimer(10.0, Timer_RandomRewind) + .SetAutoTimer(10.0) + .OnActivate(Activate_Rewind) + .AddPrompt("Distance") + .AddOptionFloat("Subtle", false, 10.0) + .AddOptionFloat("Tiny", false, 50.0) + .AddOptionFloat("Normal", true, 100.0) + .AddOptionFloat("Far", false, 250.0) + .Build(); /// CATEGORY: MISC SetCategory("Misc"); @@ -297,8 +320,25 @@ TrollEffectResponse Activate_SmartCharge(Troll troll, int activator, int victim, return TE_Success; } +void Activate_Rewind(Troll troll, int activator, int victim, int flags, trollModifier mod) { + float distance; + troll.GetPromptDataFloat(activator, 0, distance); + RewindPlayer(victim, distance); +} +void Activate_SlowSpeed(Troll troll, int activator, int victim, float movement, int flags, trollModifier mod) { + SetEntPropFloat(victim, Prop_Send, "m_flLaggedMovementValue", movement); +} +void Reset_SlowSpeed(Troll troll, int activator, int victim) { + SetEntPropFloat(victim, Prop_Send, "m_flLaggedMovementValue", 1.0); +} +void Activate_HighGravity(Troll troll, int activator, int victim, int flags, trollModifier mod) { + SetEntityGravity(victim, 1.3); +} +void Reset_HighGravity(Troll troll, int activator, int victim) { + SetEntityGravity(victim, 1.0); +} + void AddMagnetFlags(TrollBuilder troll) { - PrintToServer("adding: %d", troll.Id); troll.AddPrompt("Choose Magnet Chance:") .AddOptionFloat("Always (100%)", true, 1.0) .AddOptionFloat("Half Time (50%)", false, 0.5) @@ -309,30 +349,12 @@ TrollEffectResponse ApplyAffect(int victim, Troll troll, int activator, trollMod bool toActive = troll.IsActive(victim); char name[MAX_TROLL_NAME_LENGTH]; troll.GetName(name, sizeof(name)); - if(StrEqual(name, "Reset User")) { + if(troll.Id == 0) { LogAction(activator, victim, "\"%L\" reset all effects for \"%L\"", activator, victim); ShowActivityEx(activator, "[FTT] ", "reset effects for %N. ", victim); - // for(int i = 0; i <= MAX_TROLLS; i++) { - // Trolls[i].activeFlagClients[victim] = -1; - // } - // SetEntityGravity(victim, 1.0); - // SetEntPropFloat(victim, Prop_Send, "m_flLaggedMovementValue", 1.0); ResetClient(victim, true); return TE_Error; // Not an error, but don't want to show activation - } else if(StrEqual(name, "Slow Speed")) { - if(toActive) { - float movement = 0.0; - if(flags & 1) movement = 0.9; - else if(flags & 2) movement = 0.8; - else if(flags & 4) movement = 0.7; - else if(flags & 8) movement = 0.5; - else if(flags & 16) movement = 0.3; - SetEntPropFloat(victim, Prop_Send, "m_flLaggedMovementValue", movement); - } else - SetEntPropFloat(victim, Prop_Send, "m_flLaggedMovementValue", 1.0); - } else if(StrEqual(name, "Higher Gravity")) - SetEntityGravity(victim, toActive ? 1.3 : 1.0); - else if(StrEqual(name, "UziRules / AwpSmells")) { + } else if(StrEqual(name, "UziRules / AwpSmells")) { DisableTroll(victim, "No Pickup"); DisableTroll(victim, "Primary Disable"); } else if(StrEqual(name, "Primary Disable")) { @@ -343,8 +365,6 @@ TrollEffectResponse ApplyAffect(int victim, Troll troll, int activator, trollMod DisableTroll(victim, "UziRules / AwpSmells"); DisableTroll(victim, "Primary Disable"); SDKHook(victim, SDKHook_WeaponCanUse, Event_ItemPickup); - } else if(StrEqual(name, "CameTooEarly")) { - ReplyToCommand(activator, "This troll mode is not implemented."); } else if(StrEqual(name, "KillMeSoftly")) { static char wpn[32]; GetClientWeaponName(victim, 4, wpn, sizeof(wpn)); diff --git a/scripting/include/hats.inc b/scripting/include/hats.inc new file mode 100644 index 0000000..186370d --- /dev/null +++ b/scripting/include/hats.inc @@ -0,0 +1 @@ +native bool SpawnSchematic(const char name[32], const float pos[3], const float angles[3] = NULL_VECTOR); \ No newline at end of file diff --git a/scripting/include/hats/editor.sp b/scripting/include/hats/editor.sp index 695cca0..8dcdd31 100644 --- a/scripting/include/hats/editor.sp +++ b/scripting/include/hats/editor.sp @@ -155,7 +155,7 @@ enum struct EditorData { bool CheckEntity() { if(this.entity != INVALID_ENT_REFERENCE) { - if(!IsValidEntity(this.entity)) { + if(this.entity == -1 && !IsValidEntity(this.entity)) { PrintToChat(this.client, "\x04[Editor]\x01 Entity has vanished, editing cancelled."); this.Reset(); return false; @@ -301,7 +301,7 @@ enum struct EditorData { char component[16]; for(int i = 0; i < 4; i++) { if(this.colorIndex == i) - Format(component, sizeof(component), "%s \x05 %c \x01", component, COLOR_INDEX[i]); + Format(component, sizeof(component), "%s \x05%c\x01", component, COLOR_INDEX[i]); else Format(component, sizeof(component), "%s %c", component, COLOR_INDEX[i]); } diff --git a/scripting/include/hats/props/base.sp b/scripting/include/hats/props/base.sp index df769dd..36fbbec 100644 --- a/scripting/include/hats/props/base.sp +++ b/scripting/include/hats/props/base.sp @@ -1,5 +1,9 @@ -char g_pendingSaveName[64]; int g_pendingSaveClient; +ArrayList g_previewItems; +CategoryData ROOT_CATEGORY; +ArrayList g_spawnedItems; // ArrayList(block=2) +ArrayList g_savedItems; // ArrayList +StringMap g_recentItems; // Key: model[128], value: RecentEntry /* Wish to preface this file: * It's kinda messy. The main structs are: @@ -12,7 +16,123 @@ The rest are kinda necessary, for sorting reasons (SearchData, RecentEntry). enum ChatPrompt { Prompt_None, Prompt_Search, - Prompt_Save + Prompt_SaveScene, + Prompt_SaveSchematic +} +enum SaveType { + Save_None, + Save_Scene, + Save_Schematic +} + +enum struct Schematic { + char name[64]; + char creatorSteamid[32]; + char creatorName[32]; + ArrayList entities; + + void Reset() { + this.name[0] = '\0'; + this.creatorSteamid[0] = '\0'; + this.creatorName[0] = '\0'; + if(this.entities != null) delete this.entities; + } + + void AddEntity(int entity, int client) { + SaveData save; + save.FromEntity(entity); + this.entities.PushArray(save); + } + + void New(int client, const char[] name) { + if(client > 0) { + GetClientName(client, this.creatorName, sizeof(this.creatorName)); + GetClientAuthId(client, AuthId_Steam2, this.creatorSteamid, sizeof(this.creatorSteamid)); + } + strcopy(this.name, sizeof(this.name), name); + this.entities = new ArrayList(sizeof(SaveData)); + } + + bool Save() { + char path[PLATFORM_MAX_PATH]; + BuildPath(Path_SM, path, sizeof(path), "data/prop_spawner/schematics/%s.schem", this.name); + CreateDirectory("data/prop_spawner/schematics", 0775); + KeyValues kv = new KeyValues(this.name); + kv.SetString("creator_steamid", this.creatorSteamid); + kv.SetString("creator_name", this.creatorName); + kv.JumpToKey("entities"); + this.entities = new ArrayList(sizeof(SaveData)); + SaveData ent; + while(kv.GotoNextKey()) { + kv.GetVector("offset", ent.origin); + kv.GetVector("angles", ent.angles); + kv.GetColor4("color", ent.color); + kv.GetString("model", ent.model, sizeof(ent.model)); + this.entities.PushArray(ent); + } + kv.ExportToFile(path); + delete kv; + return true; + } + + bool Import(const char[] name) { + char path[PLATFORM_MAX_PATH]; + BuildPath(Path_SM, path, sizeof(path), "data/prop_spawner/schematics/%s.schem", name); + KeyValues kv = new KeyValues("root"); + if(kv.ImportFromFile(path)) { + delete kv; + return false; + } + strcopy(this.name, sizeof(this.name), name); + kv.GetString("creator_steamid", this.creatorSteamid, sizeof(this.creatorSteamid)); + kv.GetString("creator_name", this.creatorName, sizeof(this.creatorName)); + kv.JumpToKey("entities"); + this.entities = new ArrayList(sizeof(SaveData)); + SaveData ent; + while(kv.GotoNextKey()) { + kv.GetVector("offset", ent.origin); + kv.GetVector("angles", ent.angles); + kv.GetColor4("color", ent.color); + kv.GetString("model", ent.model, sizeof(ent.model)); + this.entities.PushArray(ent); + } + delete kv; + return true; + } + + /// Spawns all schematics entities, returns list of entities, first being parent. + ArrayList SpawnEntities(const float origin[3], bool asPreview = true) { + if(this.entities == null) return null; + SaveData ent; + int parent = -1; + ArrayList spawnedEntities = new ArrayList(); + for(int i = 0; i < this.entities.Length; i++) { + this.entities.GetArray(i, ent, sizeof(ent)); + int entity = ent.ToEntity(origin, asPreview); + spawnedEntities.Push(EntIndexToEntRef(entity)); + if(i == 0) { + SetParent(entity, parent) + } else { + parent = entity; + } + } + return spawnedEntities; + } +} +public any Native_SpawnSchematic(Handle plugin, int numParams) { + char name[32]; + float pos[3]; + float ang[3]; + GetNativeString(0, name, sizeof(name)); + GetNativeArray(1, pos, 3); + GetNativeArray(1, ang, 3); + Schematic schem; + if(!schem.Import(name)) { + return false; + } + ArrayList list = schem.SpawnEntities(pos, false); + delete list; + return true; } enum struct PlayerPropData { ArrayList categoryStack; @@ -25,6 +145,9 @@ enum struct PlayerPropData { char classnameOverride[64]; ChatPrompt chatPrompt; ArrayList markedProps; + SaveType pendingSaveType; + + Schematic schematic; // Called on PlayerDisconnect void Reset() { @@ -36,6 +159,15 @@ enum struct PlayerPropData { this.lastActiveTime = 0; this.classnameOverride[0] = '\0'; this.CleanupBuffers(); + this.pendingSaveType = Save_None; + this.schematic.Reset(); + } + + void StartSchematic(int client, const char[] name) { + this.schematic.New(client, name); + this.pendingSaveType = Save_Schematic; + PrintToChat(client, "\x04[Editor]\x01 Started new schematic: \x05%s", name); + ShowCategoryList(client, ROOT_CATEGORY); } // Sets the list buffer @@ -97,6 +229,7 @@ enum struct PlayerPropData { } PlayerPropData g_PropData[MAXPLAYERS+1]; + enum struct CategoryData { // The display name of category char name[64]; @@ -124,6 +257,8 @@ enum struct SearchData { strcopy(this.name, sizeof(this.name), item.name); } } + + enum struct SaveData { char model[128]; buildType type; @@ -148,6 +283,43 @@ enum struct SaveData { GetEntityRenderColor(entity, this.color[0],this.color[1],this.color[2],this.color[3]); } + int ToEntity(const float offset[3], bool asPreview = true) { + int entity = -1; + if(this.type == Build_Physics) + entity = CreateEntityByName("prop_physics"); + else + entity = CreateEntityByName("prop_dynamic_override"); + if(entity == -1) { + return -1; + } + PrecacheModel(this.model); + DispatchKeyValue(entity, "model", this.model); + DispatchKeyValue(entity, "targetname", "saved_prop"); + if(asPreview) { + DispatchKeyValue(entity, "rendermode", "1"); + DispatchKeyValue(entity, "solid", "0"); + } else { + DispatchKeyValue(entity, "solid", this.type == Build_NonSolid ? "0" : "6"); + } + float pos[3]; + for(int i = 0; i < 3; i++) + pos[i] = this.origin[i] + offset[i]; + + TeleportEntity(entity, pos, this.angles, NULL_VECTOR); + if(!DispatchSpawn(entity)) { + return -1; + } + int alpha = asPreview ? 200 : this.color[3]; + SetEntityRenderColor(entity, this.color[0], this.color[1], this.color[2], alpha); + + if(asPreview) + g_previewItems.Push(EntIndexToEntRef(entity)); + else + AddSpawnedItem(entity); + return entity; + } + + void Serialize(char[] output, int maxlen) { Format( output, maxlen, "%s,%d,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%d,%d,%d,%d", @@ -181,10 +353,6 @@ enum struct RecentEntry { char name[64]; int count; } -CategoryData ROOT_CATEGORY; -ArrayList g_spawnedItems; // ArrayList(block=2) -ArrayList g_savedItems; // ArrayList -StringMap g_recentItems; // Key: model[128], value: RecentEntry #include #include diff --git a/scripting/include/hats/props/cmd.sp b/scripting/include/hats/props/cmd.sp index 76ae1d5..8167c3b 100644 --- a/scripting/include/hats/props/cmd.sp +++ b/scripting/include/hats/props/cmd.sp @@ -12,6 +12,27 @@ Action Command_Props(int client, int args) { PrintToConsole(client, "favorite - favorites active editor entity"); PrintToConsole(client, "controls - list all the controls"); PrintToConsole(client, "reload - reload prop list"); + PrintToConsole(client, "schem[atic] "); + } else if(StrEqual(arg, "schem") || StrEqual(arg, "schematic")) { + char arg2[16]; + GetCmdArg(2, arg2, sizeof(arg2)); + if(StrEqual(arg2, "new")) { + char name[32]; + GetCmdArg(3, name, sizeof(name)); + if(name[0] == '\0') { + PrintToChat(client, "\x04[Editor]\x01 Please enter a name"); + } else { + g_PropData[client].StartSchematic(client, name); + } + } else if(StrEqual(arg2, "save")) { + if(g_PropData[client].pendingSaveType == Save_Schematic) { + g_PropData[client].schematic.Save(); + } else { + PrintToChat(client, "\x04[Editor]\x01 No schematic to save."); + } + } else { + PrintToChat(client, "\x04[Editor]\x01 Unknown option: %s", arg2); + } } else if(StrEqual(arg, "list")) { char arg2[16]; GetCmdArg(2, arg2, sizeof(arg2)); diff --git a/scripting/include/hats/props/menu_handlers.sp b/scripting/include/hats/props/menu_handlers.sp index 032ab31..6003fb4 100644 --- a/scripting/include/hats/props/menu_handlers.sp +++ b/scripting/include/hats/props/menu_handlers.sp @@ -75,44 +75,89 @@ void AdminMenu_SaveLoad(TopMenu topmenu, TopMenuAction action, TopMenuObject obj if(action == TopMenuAction_DisplayOption) { Format(buffer, maxlength, "Save / Load"); } else if(action == TopMenuAction_SelectOption) { - Menu menu = new Menu(SaveLoadHandler); - menu.SetTitle("Save / Load"); - char name[64]; - // TODO: possibly let you overwrite saves? - menu.AddItem("", "[New Save]"); - ArrayList saves = LoadSaves(); - if(saves != null) { - for(int i = 0; i < saves.Length; i++) { - saves.GetString(i, name, sizeof(name)); - menu.AddItem(name, name); - } - delete saves; - } - menu.ExitBackButton = true; - menu.ExitButton = true; - menu.Display(param, MENU_TIME_FOREVER); + Spawn_ShowSaveLoadMainMenu(param); } } -int SaveLoadHandler(Menu menu, MenuAction action, int client, int param2) { +int SaveLoadMainMenuHandler(Menu menu, MenuAction action, int client, int param2) { + if (action == MenuAction_Select) { + char info[2]; + menu.GetItem(param2, info, sizeof(info)); + SaveType type = view_as(StringToInt(info)); + ShowSaves(client, type); + } else if (action == MenuAction_Cancel) { + if(param2 == MenuCancel_ExitBack) { + Spawn_ShowSaveLoadMainMenu(client); + } + } else if (action == MenuAction_End) + delete menu; + return 0; +} + +int SaveLoadSceneHandler(Menu menu, MenuAction action, int client, int param2) { if (action == MenuAction_Select) { char saveName[64]; menu.GetItem(param2, saveName, sizeof(saveName)); if(saveName[0] == '\0') { // Save new FormatTime(saveName, sizeof(saveName), "%Y-%m-%d_%H-%I-%M"); - if(CreateSave(saveName)) { + if(CreateSceneSave(saveName)) { PrintToChat(client, "\x04[Editor]\x01 Saved as \x05%s/%s.txt", g_currentMap, saveName); } else { PrintToChat(client, "\x04[Editor]\x01 Unable to save. Sorry."); } - } else if(LoadSave(saveName, true)) { - strcopy(g_pendingSaveName, sizeof(g_pendingSaveName), saveName); + } else if(g_pendingSaveClient != 0 && g_pendingSaveClient != client) { + PrintToChat(client, "\x04[Editor]\x01 Another user is currently loading a save."); + } else if(g_PropData[client].pendingSaveType == Save_Schematic) { + PrintToChat(client, "\x04[Editor]\x01 Please complete or cancel current schematic to continue."); + } else if(LoadScene(saveName, true)) { + ConfirmSave(client, saveName); + g_pendingSaveClient = client; + PrintToChat(client, "\x04[Editor]\x01 Previewing save \x05%s", saveName); + PrintToChat(client, "\x04[Editor]\x01 Press \x05Shift + Middle Mouse\x01 to spawn, \x05Middle Mouse\x01 to cancel"); + } else { + PrintToChat(client, "\x04[Editor]\x01 Could not load save file."); + } + } else if (action == MenuAction_Cancel) { + if(param2 == MenuCancel_ExitBack) { + Spawn_ShowSaveLoadMainMenu(client); + } + } else if (action == MenuAction_End) + delete menu; + return 0; +} + +int SaveLoadSchematicHandler(Menu menu, MenuAction action, int client, int param2) { + if (action == MenuAction_Select) { + char saveName[64]; + menu.GetItem(param2, saveName, sizeof(saveName)); + Schematic schem; + if(saveName[0] == '\0') { + if(g_PropData[client].pendingSaveType == Save_Schematic) { + if(g_PropData[client].schematic.Save()) { + PrintToChat(client, "\x04[Editor]\x01 Saved schematic as \x05%s", g_PropData[client].schematic.name); + } else { + PrintToChat(client, "\x04[Editor]\x01 Failed to save schematic."); + } + g_PropData[client].schematic.Reset(); + g_PropData[client].pendingSaveType = Save_None; + } else { + g_PropData[client].chatPrompt = Prompt_SaveSchematic; + PrintToChat(client, "\x04[Editor]\x01 Enter in chat a name for schematic"); + } + } else if(schem.Import(saveName)) { + float pos[3]; + GetCursorLocation(client, pos); + ArrayList list = schem.SpawnEntities(pos, true); + SaveData save; + int parent = list.GetArray(0, save); + delete list; + Editor[client].Import(parent); if(g_pendingSaveClient != 0 && g_pendingSaveClient != client) { - PrintToChat(client, "\x04[Editor]\x01 Another user is currently loading a save."); + PrintToChat(client, "\x04[Editor]\x01 Another user is currently loading a scene."); } else { g_pendingSaveClient = client; - PrintToChat(client, "\x04[Editor]\x01 Previewing save \x05%s", saveName); + PrintToChat(client, "\x04[Editor]\x01 Previewing schematic \x05%s", saveName); PrintToChat(client, "\x04[Editor]\x01 Press \x05Shift + Middle Mouse\x01 to spawn, \x05Middle Mouse\x01 to cancel"); } } else { @@ -120,12 +165,31 @@ int SaveLoadHandler(Menu menu, MenuAction action, int client, int param2) { } } else if (action == MenuAction_Cancel) { if(param2 == MenuCancel_ExitBack) { - DisplayTopMenuCategory(g_topMenu, g_propSpawnerCategory, client); + Spawn_ShowSaveLoadMainMenu(client); } } else if (action == MenuAction_End) delete menu; return 0; } + +int SaveLoadConfirmHandler(Menu menu, MenuAction action, int client, int param2) { + if (action == MenuAction_Select) { + ClearSavePreview(); + char info[64]; + menu.GetItem(param2, info, sizeof(info)); + if(info[0] != '\0') { + PrintToChat(client, "\x04[Editor]\x01 Loaded scene \x05%s", info); + LoadScene(info, false); + } + } else if (action == MenuAction_Cancel) { + if(param2 == MenuCancel_ExitBack) { + Spawn_ShowSaveLoadMainMenu(client); + } + } else if (action == MenuAction_End) + delete menu; + return 0; +} + int DeleteHandler(Menu menu, MenuAction action, int client, int param2) { if (action == MenuAction_Select) { char info[8]; diff --git a/scripting/include/jutils.inc b/scripting/include/jutils.inc index f97252d..ec2efea 100644 --- a/scripting/include/jutils.inc +++ b/scripting/include/jutils.inc @@ -657,16 +657,15 @@ stock void StringToUpper(char[] str) { } } -stock int GetRealClient(int client) { - if(IsFakeClient(client)) { - char netclass[32]; - GetEntityClassname(client, netclass, sizeof(netclass)); - if(!StrEqual(netclass, "SurvivorBot", false)) return false; - int realPlayer = GetClientOfUserId(GetEntProp(client, Prop_Send, "m_humanSpectatorUserID")); - return realPlayer > 0 ? realPlayer : -1; - }else{ - return client; +stock int GetRealClient(int bot) { + if(!IsFakeClient(bot)) return -1; + static char netclass[16]; + GetEntityNetClass(bot, netclass, sizeof(netclass)); + if(StrEqual(netclass, "SurvivorBot")) { + int user = GetEntProp(bot, Prop_Send, "m_humanSpectatorUserID"); + if(user > 0) return GetClientOfUserId(user); } + return -1; } stock int FindIdleBot(int client) { diff --git a/scripting/l4d2_extraplayeritems.sp b/scripting/l4d2_extraplayeritems.sp index 1fd2de5..abc9ab1 100644 --- a/scripting/l4d2_extraplayeritems.sp +++ b/scripting/l4d2_extraplayeritems.sp @@ -259,6 +259,7 @@ public void OnPluginStart() { HookEvent("player_spawn", Event_PlayerSpawn); HookEvent("player_first_spawn", Event_PlayerFirstSpawn); + HookEvent("player_left_start_area", Event_LeaveStartArea); //Tracking player items: HookEvent("item_pickup", Event_ItemPickup); HookEvent("weapon_drop", Event_ItemPickup); @@ -939,6 +940,10 @@ void Event_PlayerDisconnect(Event event, const char[] name, bool dontBroadcast) delete g_saveTimer[client]; } +void Event_LeaveStartArea(Event event, const char[] name, bool dontBroadcast) { + PopulateItems(); +} + void Event_PlayerInfo(Event event, const char[] name, bool dontBroadcast) { int client = GetClientOfUserId(event.GetInt("userid")); if(client && !IsFakeClient(client)) { @@ -1336,7 +1341,6 @@ public void OnMapStart() { L4D2_RunScript(HUD_SCRIPT_CLEAR); Director_OnMapStart(); g_areItemsPopulated = false; - CreateTimer(30.0, Timer_Populate); if(g_isLateLoaded) { UpdateSurvivorCount(); @@ -1486,11 +1490,6 @@ public void OnMapEnd() { void Event_FinaleStart(Event event, const char[] name, bool dontBroadcast) { g_finaleStage = Stage_Active; } -Action Timer_Populate(Handle h) { - PopulateItems(); - return Plugin_Continue; - -} public void OnClientSpeaking(int client) { g_isSpeaking[client] = true; } @@ -1566,7 +1565,7 @@ Action Hook_CabinetItemSpawn(int entity) { int cabEnt = cabinets[ci].items[block]; PrintDebug(DEBUG_ANY, "cabinet %d spawner %d block %d: %d", cabinet, entity, block, cabEnt); if(cabEnt <= 0) { - cabinets[ci].items[block] = entity; + cabinets[ci].items[block] = EntIndexToEntRef(entity); PrintDebug(DEBUG_SPAWNLOGIC, "Adding spawner %d for cabinet %d block %d", entity, cabinet, block); break; } @@ -1770,6 +1769,7 @@ Action Timer_UpdateHud(Handle h) { /////////////////////////////////////////////////////////////////////////////// void PopulateItems() { + PrintToServer("[EPI:TEMP] PopulateItems hasRan=%b finale=%b", g_areItemsPopulated, L4D_IsMissionFinalMap(true)); if(g_areItemsPopulated) return; UpdateSurvivorCount(); if(!IsEPIActive()) return; @@ -1831,7 +1831,8 @@ void PopulateCabinets() { while(extraAmount > 0) { //FIXME: spawner is sometimes invalid entity. Ref needed? for(int block = 0; block < CABINET_ITEM_BLOCKS; block++) { - spawner = cabinets[i].items[block]; + if(cabinets[i].items[block] == 0) break; + spawner = EntRefToEntIndex(cabinets[i].items[block]); if(spawner > 0) { if(!HasEntProp(spawner, Prop_Data, "m_itemCount")) continue; hasSpawner = true; diff --git a/scripting/l4d2_feedthetrolls.sp b/scripting/l4d2_feedthetrolls.sp index 92fd432..59144f1 100644 --- a/scripting/l4d2_feedthetrolls.sp +++ b/scripting/l4d2_feedthetrolls.sp @@ -32,7 +32,9 @@ public Plugin myinfo = }; public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) { + // return APLRes_SilentFailure; CreateNative("ApplyTroll", Native_ApplyTroll); + RegPluginLibrary("feedthetrolls"); return APLRes_Success; } public void OnLibraryAdded(const char[] name) { diff --git a/scripting/l4d2_hats.sp b/scripting/l4d2_hats.sp index 3330cfd..3950f3d 100644 --- a/scripting/l4d2_hats.sp +++ b/scripting/l4d2_hats.sp @@ -575,12 +575,8 @@ public Action OnPlayerRunCmd(int client, int& buttons, int& impulse, float vel[3 // OnPlayerRunCmd :: ENTITY EDITOR ///////////////////////////// if(g_pendingSaveClient == client) { - if(buttons & IN_ZOOM) { - ClearSavePreview(); - if(buttons & IN_SPEED) { - PrintToChat(client, "\x04[Editor]\x01 Loaded save \x05%s", g_pendingSaveName); - LoadSave(g_pendingSaveName, false); - } + if(g_PropData[client].pendingSaveType == Save_Schematic) { + // move cursor? or should be editor anyway } } else if(g_PropData[client].markedProps != null) { SetWeaponDelay(client, 0.5); @@ -615,10 +611,10 @@ public Action OnPlayerRunCmd(int client, int& buttons, int& impulse, float vel[3 cmdThrottle[client] = tick; } } else if(Editor[client].IsActive()) { - if(buttons & IN_USE && buttons & IN_RELOAD) { - ClientCommand(client, "sm_wall done"); - return Plugin_Handled; - } + // if(buttons & IN_USE && buttons & IN_RELOAD) { + // ClientCommand(client, "sm_wall done"); + // return Plugin_Handled; + // } bool allowMove = true; switch(Editor[client].mode) { case MOVE_ORIGIN: { @@ -626,7 +622,7 @@ public Action OnPlayerRunCmd(int client, int& buttons, int& impulse, float vel[3 bool isRotate; int flags = GetEntityFlags(client); - if(buttons & IN_USE && ~buttons & IN_ZOOM) { + if(buttons & IN_RELOAD && ~buttons & IN_ZOOM) { if(!g_inRotate[client]) { g_inRotate[client] = true; } @@ -717,22 +713,19 @@ public Action OnPlayerRunCmd(int client, int& buttons, int& impulse, float vel[3 } } } - if(tick - cmdThrottle[client] > 0.13) { - if(buttons & IN_RELOAD) - Editor[client].CycleMode(); // R: Cycle forward - else if(buttons & IN_ZOOM) { - buttons &= ~IN_ZOOM; - - if(buttons & IN_SPEED) { - int entity; - Editor[client].Done(entity); - } else if(Editor[client].flags & Edit_Preview && buttons & IN_DUCK) { - Editor[client].CycleBuildType(); - } else if(~buttons & IN_DUCK) { - Editor[client].Cancel(); - } + if(!(oldButtons & IN_USE) && buttons & IN_USE) { + if(buttons & IN_SPEED) { + Editor[client].Cancel(); + } else if(buttons & IN_DUCK) { + if(Editor[client].flags & Edit_Preview) + Editor[client].CycleBuildType(); + } else { + int entity; + Editor[client].Done(entity); } - cmdThrottle[client] = tick; + + } else if(!(oldButtons & IN_ZOOM) && buttons & IN_ZOOM) { + Editor[client].CycleMode(); // ZOOM: Cycle forward } Editor[client].Draw(BUILDER_COLOR, 0.1, 0.1); @@ -900,7 +893,7 @@ stock bool Filter_NoPlayers(int entity, int mask, int data) { } stock bool Filter_IgnorePlayerAndWall(int entity, int mask, int data) { - if(entity > MaxClients && entity != data && EntRefToEntIndex(Editor[data].entity) != entity) { + if(entity > MaxClients && entity != data && EntRefToEntIndex(Editor[data].entity) != entity && EntRefToEntIndex(hatData[data].entity) != entity) { static char classname[16]; GetEntityClassname(entity, classname, sizeof(classname)); // Ignore infected @@ -912,8 +905,9 @@ stock bool Filter_IgnorePlayerAndWall(int entity, int mask, int data) { bool Filter_ValidHats(int entity, int mask, int data) { if(entity == data) return false; - if(entity <= MaxClients) { + if(entity <= MaxClients && entity > 0) { int client = GetRealClient(data); + if(client == -1) client = data; return CanTarget(client); // Don't target if player targetting off } return CheckBlacklist(entity); diff --git a/scripting/sm_player_notes.sp b/scripting/sm_player_notes.sp index f8a9637..5382aae 100644 --- a/scripting/sm_player_notes.sp +++ b/scripting/sm_player_notes.sp @@ -14,7 +14,7 @@ #include // Addons: #undef REQUIRE_PLUGIN -#tryinclude +#include #undef REQUIRE_PLUGIN #tryinclude @@ -504,6 +504,7 @@ bool ApplyAction(int targetUserId, const char[] action, const char[] key, const #if defined _ftt_included_ if(GetFeatureStatus(FeatureType_Native, "ApplyTroll") != FeatureStatus_Available) { PrintToServer("[PlayerNotes] Warn: Action \"%s\" for %N has missing plugin: Feed The Trolls", action, target); + return false; } // Replace under scores with spaces char newKey[32]; @@ -524,6 +525,7 @@ bool ApplyAction(int targetUserId, const char[] action, const char[] key, const #if defined _tkstopper_included_ if(GetFeatureStatus(FeatureType_Native, "SetImmunity") != FeatureStatus_Available) { PrintToServer("[PlayerNotes] Warn: Action \"%s\" for %N has missing plugin: TKStopper", action, target); + return false; } else if(StrEqual(key, "rff")) { SetImmunity(target, TKImmune_ReverseFriendlyFire, true); } else if(StrEqual(key, "tk")) { @@ -544,6 +546,8 @@ bool ApplyAction(int targetUserId, const char[] action, const char[] key, const } else { } + } else if(strncmp(action, "model", 4) == 0) { + ServerCommand("sm_model %s #%d", key, GetClientUserId(target)); } else { PrintToServer("[PlayerNotes] Warn: Action (\"%s\") for %N is not valid", action, target); return false;