Guess who?

This commit is contained in:
Jackz 2022-06-27 11:39:50 -05:00
parent ca7d54b6f3
commit 4482eccb7c
No known key found for this signature in database
GPG key ID: E0BBD94CF657F603
3 changed files with 672 additions and 0 deletions

BIN
plugins/l4d2_guesswho.smx Normal file

Binary file not shown.

View file

@ -0,0 +1,41 @@
// Meta
char gamemode[32];
char currentMap[64];
bool isEnabled, lateLoaded;
// Internal State
char currentSet[16] = "default";
char nextRoundMap[64];
int seekerCam = INVALID_ENT_REFERENCE;
bool isNavBlockersEnabled = true, isPropsEnabled = true, isPortalsEnabled = true;
int g_iLaserIndex;
// Gamemode state
bool isPendingPlay[MAXPLAYERS+1];
bool isViewingCam[MAXPLAYERS+1];
enum struct EntityConfig {
float origin[3];
float rotation[3];
char type[32];
char model[64];
float scale[3];
float offset[3];
}
enum struct MapConfig {
ArrayList entities;
ArrayList inputs;
float spawnpoint[3];
bool hasSpawnpoint;
int mapTime;
bool canClimb;
bool pressButtons;
}
MapConfig mapConfig;
ArrayList validMaps;
ArrayList validSets;

631
scripting/l4d2_guesswho.sp Normal file
View file

@ -0,0 +1,631 @@
#pragma semicolon 1
#pragma newdecls required
//#define DEBUG
#define PLUGIN_VERSION "1.0"
#define DEBUG_SEEKER_PATH_CREATION 1
#define SEED_TIME 10.0
#define MAX_VALID_LOCATIONS 1000
#include <sourcemod>
#include <sdktools>
#include <left4dhooks>
#include <smlib/effects>
#include <sceneprocessor>
#include <basegamemode>
//#include <sdkhooks>
// TODO: Hiders can switch models
char SURVIVOR_MODELS[8][] = {
"models/survivors/survivor_gambler.mdl",
"models/survivors/survivor_producer.mdl",
"models/survivors/survivor_coach.mdl",
"models/survivors/survivor_mechanic.mdl",
"models/survivors/survivor_namvet.mdl",
"models/survivors/survivor_teenangst.mdl",
"models/survivors/survivor_biker.mdl",
"models/survivors/survivor_manager.mdl"
};
enum GameState {
State_Unknown = 0,
State_Starting,
State_Active,
}
int currentSeeker;
bool hasBeenSeeker[MAXPLAYERS+1];
bool ignoreSeekerBalance;
Handle spawningTimer;
ConVar cvar_survivorLimit;
ConVar cvar_separationMinRange;
ConVar cvar_separationMaxRange;
ConVar cvar_abmAutoHard;
ConVar cvar_sbFixEnabled;
ConVar cvar_seekerFailDamageAmount;
ArrayList validLocations;
enum struct LocationMeta {
float pos[3];
float ang[3];
bool runto;
}
LocationMeta activeBotLocations[MAXPLAYERS];
#include <guesswho/gwcore>
public Plugin myinfo = {
name = "L4D2 Guess Who",
author = "jackzmc",
description = "",
version = PLUGIN_VERSION,
url = ""
};
public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) {
lateLoaded = late;
return APLRes_Success;
}
public void OnPluginStart() {
EngineVersion g_Game = GetEngineVersion();
if(g_Game != Engine_Left4Dead2) {
SetFailState("This plugin is for L4D2 only.");
}
validLocations = new ArrayList(sizeof(LocationMeta));
ConVar hGamemode = FindConVar("mp_gamemode");
hGamemode.AddChangeHook(Event_GamemodeChange);
Event_GamemodeChange(hGamemode, gamemode, gamemode);
cvar_survivorLimit = FindConVar("survivor_limit");
cvar_separationMinRange = FindConVar("sb_separation_danger_min_range");
cvar_separationMaxRange = FindConVar("sb_separation_danger_max_range");
cvar_abmAutoHard = FindConVar("abm_autohard");
cvar_sbFixEnabled = FindConVar("sb_fix_enabled");
cvar_seekerFailDamageAmount = CreateConVar("guesswho_seeker_damage", "20.0", "The amount of damage the seeker takes when they attack a bot.", FCVAR_NONE, true, 1.0);
RegAdminCmd("sm_guesswho", Command_GuessWho, ADMFLAG_KICK);
RegConsoleCmd("sm_joingame", Command_Join);
}
public void OnPluginEnd() {
Cleanup();
}
public void Event_GamemodeChange(ConVar cvar, const char[] oldValue, const char[] newValue) {
cvar.GetString(gamemode, sizeof(gamemode));
bool shouldEnable = StrEqual(gamemode, "guesswho", false);
if(isEnabled == shouldEnable) return;
if(spawningTimer != null) delete spawningTimer;
if(shouldEnable) {
SetCvars();
PrintToChatAll("[GuessWho] Gamemode is starting");
HookEvent("round_start", Event_RoundStart);
HookEvent("player_death", Event_PlayerDeath);
HookEvent("player_bot_replace", Event_PlayerToBot);
// HookEvent("player_team", Event_PlayerTeam);
/*HookEvent("round_end", Event_RoundEnd);
;
HookEvent("item_pickup", Event_ItemPickup);
HookEvent("player_death", Event_PlayerDeath);*/
// InitGamemode();
for(int i = 1; i <= MaxClients; i++) {
if(IsClientConnected(i) && IsClientInGame(i)) {
//ForcePlayerSuicide(i);
}
}
} else if(!lateLoaded) {
if(cvar_survivorLimit != null)
cvar_survivorLimit.IntValue = 4;
UnhookEvent("round_start", Event_RoundStart);
UnhookEvent("player_death", Event_PlayerDeath);
UnhookEvent("player_bot_replace", Event_PlayerToBot);
// UnhookEvent("player_team", Event_PlayerTeam);
/*UnhookEvent("round_end", Event_RoundEnd);
UnhookEvent("item_pickup", Event_ItemPickup);
UnhookEvent("player_death", Event_PlayerDeath);
UnhookEvent("player_spawn", Event_PlayerSpawn);*/
Cleanup();
}
isEnabled = shouldEnable;
}
void Event_PlayerToBot(Event event, const char[] name, bool dontBroadcast) {
int player = GetClientOfUserId(GetEventInt(event, "player"));
int bot = GetClientOfUserId(GetEventInt(event, "bot"));
// Do not kick bots being spawned in
if(spawningTimer == null) {
PrintToServer("kicking %d", bot);
ChangeClientTeam(player, 0);
L4D_SetHumanSpec(bot, player);
L4D_TakeOverBot(player);
// KickClient(bot);
}
}
void Event_PlayerDeath(Event event, const char[] name, bool dontBroadcast) {
int client = GetClientOfUserId(event.GetInt("userid"));
int attacker = GetClientOfUserId(event.GetInt("attacker"));
if(client > 0 && GetState() == State_Active) {
if(client == currentSeeker) {
PrintToChatAll("%N has died, hiders win.", currentSeeker);
SetState(State_Unknown);
CreateTimer(5.0, Timer_ResetAll);
for(int i = 1; i <= MaxClients; i++) {
if(IsClientConnected(i) && IsClientInGame(i) && IsFakeClient(i)) {
ClearInventory(i);
PrintToServer("PlayerDeath: Seeker kill %d", i);
KickClient(i);
}
}
} else if(!IsFakeClient(client)) {
if(attacker == currentSeeker) {
PrintToChatAll("%N was killed", client);
} else {
PrintToChatAll("%N died", client);
}
} else {
ClearInventory(client);
KickClient(client);
PrintToServer("PlayerDeath: Bot death %d", client);
}
}
if(GetPlayersLeftAlive() <= 1) {
if(GetState() == State_Active) {
PrintToChatAll("Everyone has died. %N wins!", currentSeeker);
}
SetState(State_Unknown);
CreateTimer(5.0, Timer_ResetAll);
}
}
Action Timer_ResetAll(Handle h) {
for(int i = 1; i <= MaxClients; i++) {
if(IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) == 2) {
ForcePlayerSuicide(i);
}
}
return Plugin_Handled;
}
bool isStarting;
void Event_RoundStart(Event event, const char[] name, bool dontBroadcast) {
for(int i = 1; i <= MaxClients; i++) {
if(IsClientConnected(i)) {
if(isPendingPlay[i]) {
ChangeClientTeam(i, 2);
} else if(IsFakeClient(i)) {
KickClient(i);
}
}
}
PrintToServer("RoundStart %b", isStarting);
if(!isStarting) {
isStarting = true;
CreateTimer(5.0, Timer_Start);
}
}
Action Timer_Start(Handle h) {
if(isStarting)
InitGamemode();
return Plugin_Handled;
}
public void OnMapStart() {
isStarting = false;
if(!isEnabled) return;
char map[128];
GetCurrentMap(map, sizeof(map));
if(!StrEqual(currentMap, map)) {
if(!StrEqual(currentMap, "")) {
if(!SaveMapData(currentMap)) {
LogError("Could not save map data to disk");
}
}
strcopy(currentMap, sizeof(currentMap), map);
LoadMapData(map);
}
g_iLaserIndex = PrecacheModel("materials/sprites/laserbeam.vmt");
if(isEnabled) {
SetCvars();
}
if(lateLoaded) {
SetupEntities();
int seeker = GetSeeker();
if(seeker > -1) {
currentSeeker = seeker;
PrintToServer("[GuessWho] Late load, found seeker %N", currentSeeker);
}
for(int i = 1; i <= MaxClients; i++) {
if(IsClientConnected(i) && IsClientInGame(i)) {
if(i == currentSeeker) {
CheatCommand(i, "give", "fireaxe");
} else {
CheatCommand(i, "give", "gnome");
}
SDKHook(i, SDKHook_WeaponDrop, OnWeaponDrop);
SDKHook(i, SDKHook_OnTakeDamageAlive, OnTakeDamageAlive);
}
}
CreateTimer(0.1, Timer_Start);
}
SetState(State_Unknown);
}
public void OnClientPutInServer(int client) {
if(isEnabled && !IsFakeClient(client)) {
ChangeClientTeam(client, 1);
isPendingPlay[client] = true;
PrintToChatAll("%N will play next round", client);
float pos[3];
GetSpawnPosition(pos);
TeleportEntity(client, pos, NULL_VECTOR, NULL_VECTOR);
}
}
void SetCvars() {
if(cvar_survivorLimit != null) {
cvar_survivorLimit.SetBounds(ConVarBound_Upper, true, 64.0);
cvar_survivorLimit.IntValue = MaxClients;
}
if(cvar_separationMinRange != null) {
cvar_separationMinRange.IntValue = 1000;
cvar_separationMaxRange.IntValue = 1200;
}
if(cvar_abmAutoHard != null)
cvar_abmAutoHard.IntValue = 0;
if(cvar_sbFixEnabled != null)
cvar_sbFixEnabled.IntValue = 0;
}
void InitGamemode() {
if(isStarting && GetState() != State_Unknown) {
PrintToServer("[GuessWho] Warn: InitGamemode() called in an incorrect state (%d)", GetState());
return;
}
PrintToChatAll("InitGamemode(): activating");
ArrayList validPlayerIds = new ArrayList();
for(int i = 1; i <= MaxClients; i++) {
if(IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) == 2) {
if(IsFakeClient(i)) KickClient(i);
else {
if(!IsPlayerAlive(i)) {
L4D_RespawnPlayer(i);
}
ChangeClientTeam(i, 2);
if(!hasBeenSeeker[i] || ignoreSeekerBalance)
validPlayerIds.Push(GetClientUserId(i));
}
}
}
if(validPlayerIds.Length == 0) {
PrintToServer("[GuessWho] Warn: Ignoring InitGamemode() with no valid survivors");
return;
}
ignoreSeekerBalance = false;
int newSeeker = GetClientOfUserId(validPlayerIds.Get(GetURandomInt() % validPlayerIds.Length));
delete validPlayerIds;
if(newSeeker > 0) {
hasBeenSeeker[newSeeker] = true;
PrintToChatAll("%N is the seeker", newSeeker);
SetTick(30);
SetState(State_Starting);
SetSeeker(newSeeker);
}
PrintToChatAll("SPAWNING BOTS AHHHHHHHHHHHHH");
spawningTimer = CreateTimer(0.2, Timer_SpawnBots, 16, TIMER_REPEAT);
}
Action Timer_SpawnBots(Handle h, int max) {
static int count;
if(count < max) {
if(AddSurvivor()) {
count++;
return Plugin_Continue;
} else {
PrintToChatAll("GUESS WHO: FATAL ERROR: AddSurvivor() failed");
LogError("Guess Who: Fatal Error: AddSurvivor() failed");
count = 0;
return Plugin_Stop;
}
}
count = 0;
CreateTimer(1.0, Timer_SpawnPost);
return Plugin_Stop;
}
Action Timer_StartSeeker(Handle h) {
PrintToChatAll("Timer_StartSeeker(): activating");
PrintToChatAll("%N is now active", currentSeeker);
SetState(State_Active);
SetTick(0);
SetMapTime(1000);
return Plugin_Continue;
}
Action Timer_SpawnPost(Handle h) {
PrintToChatAll("Timer_SpawnPost(): activating");
bool isL4D1 = L4D2_GetSurvivorSetMap() == 1;
int remainingSeekers;
for(int i = 1; i <= MaxClients; i++) {
if(i != currentSeeker && IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) == 2) {
if(!IsFakeClient(i)) {
if(!hasBeenSeeker[i]) {
remainingSeekers++;
}
}
SDKHook(i, SDKHook_OnTakeDamageAlive, OnTakeDamageAlive);
SDKHook(i, SDKHook_WeaponDrop, OnWeaponDrop);
ClearInventory(i);
int item = GivePlayerItem(i, "weapon_gnome");
EquipPlayerWeapon(i, item);
int survivor = GetRandomInt(isL4D1 ? 5 : 0, 7);
SetEntityModel(i, SURVIVOR_MODELS[survivor]);
SetEntProp(i, Prop_Send, "m_survivorCharacter", isL4D1 ? (survivor - 4) : survivor);
}
}
if(remainingSeekers == 0) {
PrintToChatAll("All players have been seekers once");
for(int i = 0; i <= MaxClients; i++) {
hasBeenSeeker[i] = false;
}
}
PrintToChatAll("waiting for safe area leave");
CreateTimer(1.0, Timer_WaitForStart, _, TIMER_FLAG_NO_MAPCHANGE | TIMER_REPEAT);
return Plugin_Handled;
}
Action Timer_WaitForStart(Handle h) {
if(L4D_HasAnySurvivorLeftSafeArea()) {
float pos[3];
GetClientAbsOrigin(L4D_GetHighestFlowSurvivor(), pos);
for(int i = 1; i <= MaxClients; i++) {
if(i != currentSeeker && IsClientConnected(i) && IsClientInGame(i)) {
TeleportEntity(i, pos, NULL_VECTOR, NULL_VECTOR);
}
}
CreateTimer(0.5, Timer_AcquireLocations, _, TIMER_FLAG_NO_MAPCHANGE | TIMER_REPEAT);
Handle t = CreateTimer(8.0, Timer_BotMove, _, TIMER_FLAG_NO_MAPCHANGE | TIMER_REPEAT);
TriggerTimer(t);
PrintToChatAll("[GuessWho] Player has left safe area, starting");
SetTick(RoundFloat(SEED_TIME));
CreateTimer(SEED_TIME, Timer_StartSeeker);
return Plugin_Stop;
}
return Plugin_Continue;
}
Action OnWeaponDrop(int client, int weapon) {
PrintToChatAll("No drop");
return Plugin_Handled;
}
Action OnTakeDamageAlive(int victim, int& attacker, int& inflictor, float& damage, int& damagetype) {
if(attacker == currentSeeker) {
damage = 100.0;
ClearInventory(victim);
if(IsFakeClient(victim)) {
PrintToChat(attacker, "That was a bot! -%f health", cvar_seekerFailDamageAmount.FloatValue);
SDKHooks_TakeDamage(attacker, 0, 0, cvar_seekerFailDamageAmount.FloatValue);
}
return Plugin_Changed;
} else if(attacker > 0 && attacker <= MaxClients) {
damage = 0.0;
return Plugin_Changed;
} else {
return Plugin_Continue;
}
}
static float vecLastLocation[MAXPLAYERS+1][3];
static float DEBUG_BOT_MOVER_MIN[3] = { -5.0, -5.0, 0.0 };
static float DEBUG_BOT_MOVER_MAX[3] = { 5.0, 5.0, 2.0 };
Action Timer_AcquireLocations(Handle h) {
bool ignoreSeeker = true;
#if defined DEBUG_SEEKER_PATH_CREATION
ignoreSeeker = false;
#endif
for(int i = 1; i <= MaxClients; i++) {
if((!ignoreSeeker || i != currentSeeker) && IsClientConnected(i) && IsClientInGame(i) && !IsFakeClient(i) && GetEntityFlags(i) & FL_ONGROUND ) {
LocationMeta meta;
GetClientAbsOrigin(i, meta.pos);
GetClientEyeAngles(i, meta.ang);
if(meta.pos[0] != vecLastLocation[i][0] || meta.pos[1] != vecLastLocation[i][1] || meta.pos[2] != vecLastLocation[i][2]) {
meta.runto = GetURandomFloat() > 0.9;
validLocations.PushArray(meta);
if(validLocations.Length > MAX_VALID_LOCATIONS) {
validLocations.Sort(Sort_Random, Sort_Float);
validLocations.Erase(MAX_VALID_LOCATIONS - 100);
}
Effect_DrawBeamBoxRotatableToAll(meta.pos, DEBUG_BOT_MOVER_MIN, DEBUG_BOT_MOVER_MAX, NULL_VECTOR, g_iLaserIndex, 0, 0, 0, 150.0, 0.1, 0.1, 0, 0.0, {255, 0, 0, 255}, 0);
vecLastLocation[i] = meta.pos;
}
}
}
return Plugin_Continue;
}
Action Timer_BotMove(Handle h) {
static float seekerPos[3];
if(!IsClientConnected(currentSeeker)) {
PrintToChatAll("The seeker has disconnected");
CreateTimer(1.0, Timer_ResetAll);
return Plugin_Stop;
}
float seekerFlow = L4D2Direct_GetFlowDistance(currentSeeker);
GetClientAbsOrigin(currentSeeker, seekerPos);
for(int i = 1; i <= MaxClients; i++) {
if(IsClientConnected(i) && IsClientInGame(i) && IsFakeClient(i)) {
float botFlow = L4D2Direct_GetFlowDistance(i);
if(botFlow - seekerFlow > 1000.0) {
TE_SetupBeamLaser(i, currentSeeker, g_iLaserIndex, 0, 0, 0, 8.0, 0.5, 0.1, 0, 1.0, {255, 255, 0, 125}, 1);
TE_SendToAll();
L4D2_RunScript("CommandABot({cmd=1,bot=GetPlayerFromUserID(%i),pos=Vector(%f,%f,%f)})", GetClientUserId(i), seekerPos[0], seekerPos[1], seekerPos[2]);
} else if(validLocations.Length > 0) {
validLocations.GetArray(GetURandomInt() % validLocations.Length, activeBotLocations[i]);
Effect_DrawBeamBoxRotatableToAll(activeBotLocations[i].pos, DEBUG_BOT_MOVER_MIN, DEBUG_BOT_MOVER_MAX, NULL_VECTOR, g_iLaserIndex, 0, 0, 0, 150.0, 0.1, 0.1, 0, 0.0, {255, 0, 255, 255}, 0);
L4D2_RunScript("CommandABot({cmd=1,bot=GetPlayerFromUserID(%i),pos=Vector(%f,%f,%f)})",
GetClientUserId(i),
activeBotLocations[i].pos[0], activeBotLocations[i].pos[1], activeBotLocations[i].pos[2]
);
}
}
}
return Plugin_Continue;
}
public Action OnPlayerRunCmd(int client, int& buttons, int& impulse, float vel[3], float angles[3], int& weapon, int& subtype, int& cmdnum, int& tickcount, int& seed, int mouse[2]) {
if(IsFakeClient(client)) {
float random = GetURandomFloat();
if(!activeBotLocations[client].runto)
buttons |= IN_SPEED;
if(random < 0.001) {
buttons |= IN_JUMP;
} else if(random < 0.0015) {
buttons |= IN_ATTACK2;
}
return Plugin_Changed;
}
return Plugin_Continue;
}
void ClearInventory(int client) {
for(int i = 0; i <= 5; i++) {
int item = GetPlayerWeaponSlot(client, i);
if(item > 0) {
AcceptEntityInput(item, "Kill");
}
}
}
bool AddSurvivor() {
if (GetClientCount(false) >= MaxClients - 1) {
return false;
}
int i = CreateFakeClient("GuessWhoBot");
bool result;
if (i > 0) {
if (DispatchKeyValue(i, "classname", "SurvivorBot")) {
ChangeClientTeam(i, 2);
if (DispatchSpawn(i)) {
result = true;
}
}
CreateTimer(0.2, Timer_Kick, i);
// KickClient(i);
}
return result;
}
Action Timer_Kick(Handle h, int i) {
KickClient(i);
}
stock void L4D2_RunScript(const char[] sCode, any ...) {
static int iScriptLogic = INVALID_ENT_REFERENCE;
if(iScriptLogic == INVALID_ENT_REFERENCE || !IsValidEntity(iScriptLogic)) {
iScriptLogic = EntIndexToEntRef(CreateEntityByName("logic_script"));
if(iScriptLogic == INVALID_ENT_REFERENCE|| !IsValidEntity(iScriptLogic))
SetFailState("Could not create 'logic_script'");
DispatchSpawn(iScriptLogic);
}
static char sBuffer[512];
VFormat(sBuffer, sizeof(sBuffer), sCode, 2);
SetVariantString(sBuffer);
AcceptEntityInput(iScriptLogic, "RunScriptCode");
}
public void OnSceneStageChanged(int scene, SceneStages stage) {
if(stage == SceneStage_Started) {
int activator = GetSceneInitiator(scene);
if(activator == 0) {
CancelScene(scene);
}
}
}
bool SaveMapData(const char[] map) {
char buffer[256];
BuildPath(Path_SM, buffer, sizeof(buffer), "data/guesswho");
CreateDirectory(buffer, 1402 /* 770 */);
BuildPath(Path_SM, buffer, sizeof(buffer), "data/guesswho/%s.txt", map);
PrintToServer("[GuessWho] Attempting to write to %s", buffer);
File file = OpenFile(buffer, "w+");
if(file != null) {
file.WriteLine("px\tpy\tpz\tax\tay\taz");
LocationMeta meta;
for(int i = 0; i < validLocations.Length; i++) {
validLocations.GetArray(i, meta);
file.WriteLine("%.1f %.1f %.1f %.1f %.1f %.1f", meta.pos[0], meta.pos[1], meta.pos[2], meta.ang[0], meta.ang[1], meta.ang[2]);
}
file.Flush();
delete file;
return true;
}
return false;
}
bool LoadMapData(const char[] map) {
validLocations.Clear();
char buffer[256];
BuildPath(Path_SM, buffer, sizeof(buffer), "data/guesswho/%s.txt", map);
PrintToServer("[GuessWho] Attempting to read %s", buffer);
File file = OpenFile(buffer, "r+");
if(file != null) {
PrintToServer("[GuessWho] Read map data file for %s", map);
char line[64];
char pieces[16][6];
file.ReadLine(line, sizeof(line)); // Skip header
while(file.ReadLine(line, sizeof(line))) {
ExplodeString(line, " ", pieces, 6, 16, false);
LocationMeta meta;
meta.pos[0] = StringToFloat(pieces[0]);
meta.pos[1] = StringToFloat(pieces[1]);
meta.pos[2] = StringToFloat(pieces[2]);
meta.ang[0] = StringToFloat(pieces[3]);
meta.ang[1] = StringToFloat(pieces[4]);
meta.ang[2] = StringToFloat(pieces[5]);
validLocations.PushArray(meta);
}
PrintToServer("[GuessWho] Loaded %d locations from disk", validLocations.Length);
delete file;
return true;
}
return false;
}