Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /**
- * Timer Record Replay
- * by Peace-Maker
- *
- * Record best times on map and have bots play them back.
- *
- * Changelog:
- * 18.02.2013 1.0: Initial release
- * 18.02.2013 1.0.1: Fixed bots not instantly switching to new record, if the best time is beaten.
- *
- */
- // todo:
- // add a max limit of record time, so afk players won't eat up memory with nonsense record.
- // keep recording for x seconds after player finishes round
- // stop recording, if a player exceeds current best time
- // make sure the bhop plugin's !hide feature doesn't hide the selected bot!
- // required settings
- // bot_quota_mode normal
- // mp_limitteams 0
- // mp_autoteambalance 0
- //
- // You need a .nav file for the map.
- // If the server fails to create one, you'll have to create a listen server with that map in your client, spawn, watch the floor and:
- // 1. sv_cheats 1
- // 2. nav_edit 1
- // 3. nav_mark_walkable
- // 4. nav_generate
- // 5. nav_edit 0
- // 6. sv_cheats 0
- // Copy the generated .nav file to your dedicated server.
- #pragma semicolon 1
- #include <sourcemod>
- #include <sdktools>
- #include <cstrike>
- #include <sdkhooks> // https://forums.alliedmods.net/showthread.php?t=106748
- //#include <collisionhook> // https://forums.alliedmods.net/showthread.php?t=197815
- #include <botmimic> // https://forums.alliedmods.net/showthread.php?t=164148
- #include <timer> // https://forums.alliedmods.net/showthread.php?t=189751
- #include <timer-mapzones>
- #include <timer-physics>
- #include <timer-stocks>
- #include <timer-worldrecord>
- #include <timer-config_loader.sp>
- #define PLUGIN_VERSION "1.0.1"
- #define TIMER_BOTMIMIC_CATEGORY "timer"
- #define TOP_LENGTH MAX_RECORD_NAME_LENGTH + MAX_NAME_LENGTH + 2
- enum TopPlayer {
- String:TP_demoFile[MAX_RECORD_NAME_LENGTH],
- String:TP_playerName[MAX_NAME_LENGTH],
- Float:TP_time,
- bool:TP_fileNotFound
- }
- //new Handle:g_hDatabase;
- // List of all Styles, where a record exists on this map
- new Handle:g_hStylesOnMap;
- // Corresponding TopPlayer enums to the Styles
- new Handle:g_hTopsPerStyle;
- // Remember, if we processed the OnTimerWorldRecordCacheLoaded forward already this map.
- // It get's called way too often.
- new bool:g_bWRFetchedThisMap;
- // Remember which Style this player finished the map in
- new g_iPlayerRecordStyle[MAXPLAYERS+1] = {-1,...};
- // Remember what time he managed to get through the map
- new Float:g_fPlayerRecordTime[MAXPLAYERS+1];
- // Don't stop recording right away, after the timer stopped, since the FinishRound callback may be called afterwards -.-
- new Handle:g_hDelayedStopRecording[MAXPLAYERS+1];
- // The name of the record this bot currently mimics
- new String:g_sBotMimicsRecord[MAXPLAYERS+1][MAX_RECORD_NAME_LENGTH];
- // The Style the record this bot currently mimics was done in
- new g_iBotMimicsStyle[MAXPLAYERS+1];
- // The next bot joining this server is going to mimic this record
- new String:g_sNextBotMimicsThis[MAX_RECORD_NAME_LENGTH];
- new g_iBotAddTime = -1;
- // Bot names are cached in the CCSBotManager. Instead of changing it there too and require more gamedata, we just remember what the bot was called when he joined.
- new String:g_sRealBotName[MAXPLAYERS+1][MAX_NAME_LENGTH];
- // Sets, which Style the player wants to see
- new Handle:g_hShowBot[MAXPLAYERS+1];
- new g_iPlayerStyle[MAXPLAYERS+1] = {-1,...};
- // Handle collisions with brush entities.
- // We don't want them to collide with some stuff like doors.
- new bool:g_bShouldNotCollide[8097];
- // If you add a entity's classname here, make sure to also add it to the g_bNeedsTargetname array.
- new String:g_sIgnoreEntities[][64] = {"func_door", "func_wall_toggle", "func_brush", "func_button", "func_breakable"};
- // Some entities shouldn't be avoided if they don't have a targetname set.
- // This applies usually to bhop maps where the platforms on the floor are func_doors, and we want collision with them,
- // but we want bots to walk through real doors. Due to the nature of bhop maps, the doors open only on a certain event,
- // so they got targetnames, while the platforms don't do anything in interaction with other entities.
- new bool:g_bNeedsTargetname[] = {true, true, true, false, false};
- public Plugin:myinfo =
- {
- name = "Timer Record Replay",
- author = "Jannik \"Peace-Maker\" Hartung",
- description = "Have bots replay the best times from the map. (Private)",
- version = PLUGIN_VERSION,
- url = "http://www.wcfan.de/"
- }
- public OnPluginStart()
- {
- g_hStylesOnMap = CreateArray();
- g_hTopsPerStyle = CreateArray(TOP_LENGTH);
- // Hook some events to hide chat spam for our mimicing bots.
- HookEvent("player_connect", Event_OnPlayerConnect, EventHookMode_Pre);
- HookEvent("player_team", Event_OnBlock, EventHookMode_Pre);
- HookEvent("player_disconnect", Event_OnBlock, EventHookMode_Pre);
- HookUserMessage(GetUserMessageId("SayText2"), UsrMsg_NameChange, true);
- HookEvent("player_spawn", Event_OnPlayerSpawn);
- HookEvent("round_start", Event_OnRoundStart);
- // avoid that annoying spam..
- new Handle:hBotQuota = FindConVar("bot_quota");
- SetConVarFlags(hBotQuota, GetConVarFlags(hBotQuota)&~FCVAR_NOTIFY);
- RegAdminCmd("sm_resetreplay", Cmd_ResetReplay, ADMFLAG_CONFIG, "Reset a bot and let him start his playback from the beginning. Usage: sm_resetreplay <Style>");
- RegConsoleCmd("sm_replay", Cmd_Replay, "Select which replaying bot to show.");
- // Don't need to set failstate here. timer-core will do for us.
- /*if(!SQL_CheckConfig("timer"))
- return;
- SQL_TConnect(SQL_OnConnect, "timer");*/
- }
- // Kick all bots we added!
- public OnPluginEnd()
- {
- for(new i=1;i<=MaxClients;i++)
- {
- if(g_sBotMimicsRecord[i][0] != '\0')
- ServerCommand("bot_kick \"%s\"", g_sRealBotName[i]);
- }
- }
- public OnAllPluginsLoaded()
- {
- for(new i=1;i<=MaxClients;i++)
- {
- if(IsClientInGame(i) && IsFakeClient(i) && !IsClientSourceTV(i) && !IsClientReplay(i))
- {
- SDKHook(i, SDKHook_SetTransmit, Hook_SetTransmit);
- }
- }
- }
- /**
- * Public Core forwards
- */
- public OnMapStart()
- {
- // Keep checking, that we're playing the correct records
- CreateTimer(5.0, Timer_PlayRecords, _, TIMER_FLAG_NO_MAPCHANGE|TIMER_REPEAT);
- // Reload the best times from the timer every 5 minutes to keep up with changes, that we aren't notified about.
- CreateTimer(600.0, Timer_ReloadBestTimes, _, TIMER_FLAG_NO_MAPCHANGE|TIMER_REPEAT);
- LoadPhysics();
- LoadTimerSettings();
- // Query the best times for each Style for the current map
- FetchBestTimes();
- // Search for entities bots shouldn't collide with and disable buttons for them
- AddFilterToTriggers();
- }
- public OnMapEnd()
- {
- // Allow the processing of the OnTimerWorldRecordCacheLoaded forward once again
- g_bWRFetchedThisMap = false;
- g_sNextBotMimicsThis[0] = '\0';
- g_iBotAddTime = -1;
- }
- public bool:OnClientConnect(client, String:rejectmsg[], maxlen)
- {
- // Get the current bots name before we rename him.
- // bot_kick caches this name and doesn't recognize the renamed name of the bot.
- // We can't just kick bots with KickClient, since they're maintained by a botmanager in CSS which is readding them
- if(IsFakeClient(client))
- GetClientName(client, g_sRealBotName[client], sizeof(g_sRealBotName[]));
- // We want the next joining bot to mimic some record?
- if(g_sNextBotMimicsThis[0] != '\0' && IsFakeClient(client) && !IsClientSourceTV(client) && !IsClientReplay(client))
- {
- strcopy(g_sBotMimicsRecord[client], sizeof(g_sBotMimicsRecord[]), g_sNextBotMimicsThis);
- g_sNextBotMimicsThis[0] = '\0';
- g_iBotAddTime = -1;
- // Rename the bot
- new iTop[TOP_LENGTH];
- new iSize = GetArraySize(g_hTopsPerStyle);
- for(new i=0;i<iSize;i++)
- {
- GetArrayArray(g_hTopsPerStyle, i, iTop, TOP_LENGTH);
- // He's not playing that record..
- if(!StrEqual(iTop[TP_demoFile], g_sBotMimicsRecord[client]))
- continue;
- // Remember what Style he's supposed to mimic.
- g_iBotMimicsStyle[client] = GetArrayCell(g_hStylesOnMap, i);
- RenameBot(client, i);
- }
- }
- return true;
- }
- public OnClientPutInServer(client)
- {
- // Hook fake clients.
- if(IsFakeClient(client) && !IsClientSourceTV(client) && !IsClientReplay(client))
- {
- SDKHook(client, SDKHook_SetTransmit, Hook_SetTransmit);
- DispatchKeyValue(client, "targetname", "mimic_bot");
- }
- // The list of Styles this player wants to see
- g_hShowBot[client] = CreateArray();
- // This player isn't supposed to mimic anything
- if(g_sBotMimicsRecord[client][0] == '\0')
- return;
- // Let this bot mimic the record he was meant for
- new BMError:error = BotMimic_PlayRecordByName(client, g_sBotMimicsRecord[client]);
- if(error != BM_NoError)
- {
- decl String:sError[64];
- BotMimic_GetErrorString(error, sError, sizeof(sError));
- LogError("Error playing record %s: %s", g_sBotMimicsRecord[client], sError);
- // Remember that this record failed to play and don't try again later.
- new iSize = GetArraySize(g_hTopsPerStyle);
- new iTop[TOP_LENGTH];
- for(new i=0;i<iSize;i++)
- {
- GetArrayArray(g_hTopsPerStyle, i, iTop, TOP_LENGTH);
- if(StrEqual(iTop[TP_demoFile], g_sBotMimicsRecord[client]))
- {
- iTop[TP_fileNotFound] = true;
- SetArrayArray(g_hTopsPerStyle, i, iTop, TOP_LENGTH);
- break;
- }
- }
- // This bot isn't needed anymore.
- g_sBotMimicsRecord[client][0] = '\0';
- g_iBotMimicsStyle[client] = -1;
- ServerCommand("bot_kick \"%s\"", g_sRealBotName[client]);
- }
- }
- public OnClientDisconnect(client)
- {
- ClearHandle(g_hDelayedStopRecording[client]);
- g_sBotMimicsRecord[client][0] = '\0';
- g_iPlayerRecordStyle[client] = -1;
- g_fPlayerRecordTime[client] = 0.0;
- g_iBotMimicsStyle[client] = -1;
- g_sRealBotName[client][0] = '\0';
- g_iPlayerStyle[client] = -1;
- ClearHandle(g_hShowBot[client]);
- }
- // Since there is no defined order in which SM calls this callback in the loaded plugins, it might happen,
- // that this plugin gets here before the botmimic one, which will overwrite it again.
- // The order seems to be alphabetical, so try renaming this plugin to something after botmimic ;)
- public Action:OnPlayerRunCmd(client, &buttons, &impulse, Float:vel[3], Float:angles[3], &weapon, &subtype, &cmdnum, &tickcount, &seed, mouse[2])
- {
- // Don't allow bots to use buttons or attack
- if(g_sBotMimicsRecord[client][0] != '\0')
- {
- buttons &= ~(IN_USE|IN_ATTACK|IN_ATTACK2);
- return Plugin_Changed;
- }
- return Plugin_Continue;
- }
- /**
- * Command Handlers
- */
- // Have a bot start his playback from the beginning.
- public Action:Cmd_ResetReplay(client, args)
- {
- if(args < 1)
- {
- ReplyToCommand(client, "[BotMimic] Usage: sm_resetreplay <Style>");
- return Plugin_Handled;
- }
- decl String:sStyle[64];
- GetCmdArgString(sStyle, sizeof(sStyle));
- // Find the right Style
- new iSize = GetArraySize(g_hStylesOnMap);
- decl String:sBuffer[64];
- new iTop[TOP_LENGTH], iBotsReset = -1;
- for(new i=0;i<iSize;i++)
- {
- Timer_GetStyleName(GetArrayCell(g_hStylesOnMap, i), sBuffer, sizeof(sBuffer));
- // That's not the one he's looking for
- if(!StrEqual(sStyle, sBuffer, false))
- continue;
- GetArrayArray(g_hTopsPerStyle, i, iTop, TOP_LENGTH);
- iBotsReset = 0;
- // Check which bot is currently playing the record for that Style
- for(new c=1;c<=MaxClients;c++)
- {
- if(StrEqual(g_sBotMimicsRecord[c], iTop[TP_demoFile]))
- {
- if(BotMimic_IsPlayerMimicing(c))
- BotMimic_ResetPlayback(c);
- iBotsReset++;
- }
- }
- }
- if(iBotsReset == -1)
- {
- ReplyToCommand(client, "[BotMimic] There is no Style named \"%s\" or there is no record for that Style on the current map.", sBuffer);
- }
- else
- {
- ReplyToCommand(client, "[BotMimic] Reset %d bot%s playing Style \"%s\".", iBotsReset, (iBotsReset!=1?"s":""), sBuffer);
- }
- return Plugin_Handled;
- }
- // Show a menu to select which bots to show.
- public Action:Cmd_Replay(client, args)
- {
- DisplayBotShowSelectionMenu(client);
- return Plugin_Handled;
- }
- /**
- * SDKHooks Callbacks
- */
- public Action:Hook_SetTransmit(entity, client)
- {
- if(entity < 1 || entity > MaxClients)
- return Plugin_Continue;
- // Dead players and spectators are always shown all bots.
- if(client < 1 || client > MaxClients || !IsClientInGame(client) || !IsPlayerAlive(client) || IsClientObserver(client) || GetClientTeam(client) < 2)
- return Plugin_Continue;
- if(g_sBotMimicsRecord[entity][0] == '\0')
- return Plugin_Continue;
- // Don't show bots to players, if they don't want to see them
- CheckForStyleChange(client);
- if(FindValueInArray(g_hShowBot[client], g_iBotMimicsStyle[entity]) == -1)
- return Plugin_Handled;
- return Plugin_Continue;
- }
- /**
- * CollideHook Callbacks
- */
- // Allow mimicing bots to pass through closed doors.
- /*
- public Action:CH_ShouldCollide( ent1, ent2, &bool:result )
- {
- new iPlayer, iEnt;
- if(ent1 > 0 && ent1 <= MaxClients)
- {
- iPlayer = ent1;
- iEnt = ent2;
- }
- else if(ent2 > 0 && ent2 <= MaxClients)
- {
- iPlayer = ent2;
- iEnt = ent1;
- }
- else
- return Plugin_Continue;
- if(g_sBotMimicsRecord[iPlayer][0] != '\0' && g_bShouldNotCollide[iEnt])
- {
- result = false;
- return Plugin_Handled;
- }
- return Plugin_Continue;
- }
- public Action:CH_PassFilter( ent1, ent2, &bool:result )
- {
- new iPlayer, iEnt;
- if(ent1 > 0 && ent1 <= MaxClients)
- {
- iPlayer = ent1;
- iEnt = ent2;
- }
- else if(ent2 > 0 && ent2 <= MaxClients)
- {
- iPlayer = ent2;
- iEnt = ent1;
- }
- else
- return Plugin_Continue;
- if(g_sBotMimicsRecord[iPlayer][0] != '\0' && g_bShouldNotCollide[iEnt])
- {
- result = false;
- return Plugin_Handled;
- }
- return Plugin_Continue;
- }
- */
- /**
- * Timer Callbacks
- */
- public Action:Timer_PlayRecords(Handle:timer)
- {
- // Keep the correct number of bots playing the right thing on the server
- HandleBotPlaybackLogic();
- return Plugin_Continue;
- }
- // This player didn't finish the round in best time, so discard his record.
- public Action:Timer_StopRecording(Handle:timer, any:userid)
- {
- new client = GetClientOfUserId(userid);
- if(!client)
- return Plugin_Handled;
- g_hDelayedStopRecording[client] = INVALID_HANDLE;
- if(BotMimic_IsPlayerRecording(client))
- BotMimic_StopRecording(client, false);
- return Plugin_Handled;
- }
- public Action:Timer_ReloadBestTimes(Handle:timer)
- {
- FetchBestTimes();
- return Plugin_Continue;
- }
- /**
- * Event Handlers
- */
- // Block the connect chat message for mimicing bots
- public Action:Event_OnPlayerConnect(Handle:event, const String:name[], bool:dontBroadcast)
- {
- decl String:sNetworkID[32];
- GetEventString(event, "networkid", sNetworkID, sizeof(sNetworkID));
- if(!StrEqual(sNetworkID, "BOT") || g_sNextBotMimicsThis[0] == '\0')
- return Plugin_Continue;
- dontBroadcast = true;
- SetEventBroadcast(event, true);
- return Plugin_Continue;
- }
- // Block the teamchange and disconnect chat message for mimicing bots
- public Action:Event_OnBlock(Handle:event, const String:name[], bool:dontBroadcast)
- {
- new client = GetClientOfUserId(GetEventInt(event, "userid"));
- if(!client)
- return Plugin_Continue;
- if(!IsFakeClient(client) || IsClientSourceTV(client) || IsClientReplay(client))
- return Plugin_Continue;
- // This bot doesn't mimic anything and the next bot neither.
- if(g_sBotMimicsRecord[client][0] == '\0' && g_sNextBotMimicsThis[0] == '\0')
- return Plugin_Continue;
- dontBroadcast = true;
- SetEventBroadcast(event, true);
- return Plugin_Continue;
- }
- public Event_OnPlayerSpawn(Handle:event, const String:name[], bool:dontBroadcast)
- {
- new client = GetClientOfUserId(GetEventInt(event, "userid"));
- if(!client)
- return;
- if(IsFakeClient(client))
- {
- // This is important for the filter_activator_name, so bots won't trigger buttons on the ground.
- DispatchKeyValue(client, "targetname", "mimic_bot");
- }
- CheckForStyleChange(client);
- }
- public Event_OnRoundStart(Handle:event, const String:name[], bool:dontBroadcast)
- {
- AddFilterToTriggers();
- }
- // By thetwistedpanda
- // https://forums.alliedmods.net/showthread.php?t=139760
- // Don't show bots changing their name in chat at all.
- public Action:UsrMsg_NameChange(UserMsg:msg_hd, Handle:bf, const players[], playersNum, bool:reliable, bool:init)
- {
- if(GetEngineVersion() == Engine_CSS)
- {
- decl String:sBuffer[64], String:sName[MAX_NAME_LENGTH];
- BfReadString(bf, sBuffer, sizeof(sBuffer));
- BfReadString(bf, sBuffer, sizeof(sBuffer));
- if(StrContains(sBuffer, "Name_Change") != -1)
- {
- BfReadString(bf, sBuffer, sizeof(sBuffer));
- for(new i = 1; i <= MaxClients; i++)
- {
- if(IsClientConnected(i) && IsFakeClient(i))
- {
- GetClientName(i, sName, sizeof(sName));
- if(StrEqual(sBuffer, sName))
- {
- return Plugin_Handled;
- }
- }
- }
- }
- }
- else if (GetEngineVersion() == Engine_CSGO)
- {
- decl String:sBuffer[64];
- decl String:sName[64];
- new iRepeat = PbGetRepeatedFieldCount(bf, "params");
- for(new i = 0; i < iRepeat; i++)
- {
- PbReadString(bf, "params", sBuffer, sizeof(sBuffer), i);
- if (StrContains(sBuffer, "Name_Change") != -1)
- {
- PbReadString(bf, "params", sBuffer, sizeof(sBuffer), i);
- for(new j = 1; j <= MaxClients; j++)
- {
- if(IsClientConnected(j) && IsFakeClient(j))
- {
- GetClientName(j, sName, sizeof(sName));
- if(StrEqual(sBuffer, sName))
- {
- return Plugin_Handled;
- }
- }
- }
- }
- }
- }
- return Plugin_Continue;
- }
- /**
- * Bot Mimic Callbacks
- */
- public Action:BotMimic_OnStartRecording(client, String:name[], String:category[], String:subdir[], String:path[])
- {
- if(!StrEqual(category, TIMER_BOTMIMIC_CATEGORY))
- return Plugin_Continue;
- // We don't want bots to record anything in our category.
- if(IsFakeClient(client))
- return Plugin_Handled;
- return Plugin_Continue;
- }
- public Action:BotMimic_OnStopRecording(client, String:name[], String:category[], String:subdir[], String:path[], &bool:save)
- {
- // This isn't a recording we started from here.
- // We don't care.
- if(!StrEqual(category, TIMER_BOTMIMIC_CATEGORY))
- return Plugin_Continue;
- // This player actually did a new best time. Let him pass
- if(g_iPlayerRecordStyle[client] != -1)
- return Plugin_Continue;
- // Don't save the recording, if it's not acutally a best time!
- save = false;
- return Plugin_Changed;
- }
- // Player might have done a new record!
- public BotMimic_OnRecordSaved(client, String:name[], String:category[], String:subdir[], String:file[])
- {
- if(!StrEqual(category, TIMER_BOTMIMIC_CATEGORY))
- return;
- if(g_iPlayerRecordStyle[client] == -1)
- return;
- // That player managed to beat the 1st!
- new iTop[TOP_LENGTH];
- strcopy(iTop[TP_demoFile], MAX_RECORD_NAME_LENGTH, name);
- iTop[TP_time] = g_fPlayerRecordTime[client];
- GetClientName(client, iTop[TP_playerName], MAX_NAME_LENGTH);
- // Replace old record
- new iIndex = FindValueInArray(g_hStylesOnMap, g_iPlayerRecordStyle[client]);
- if(iIndex != -1)
- {
- new iOldTop[TOP_LENGTH];
- GetArrayArray(g_hTopsPerStyle, iIndex, iOldTop, TOP_LENGTH);
- SetArrayArray(g_hTopsPerStyle, iIndex, iTop, TOP_LENGTH);
- // Stop any bots from mimicing the old record and switch to this new one.
- for(new i=1;i<=MaxClients;i++)
- {
- if(StrEqual(g_sBotMimicsRecord[i], iOldTop[TP_demoFile]))
- {
- if(BotMimic_IsPlayerMimicing(i))
- BotMimic_StopPlayerMimic(i);
- strcopy(g_sBotMimicsRecord[i], sizeof(g_sBotMimicsRecord[]), name);
- g_iBotMimicsStyle[i] = g_iPlayerRecordStyle[client];
- BotMimic_PlayRecordFromFile(i, file);
- RenameBot(i, iIndex);
- }
- }
- }
- // Or add new one for this Style
- else
- {
- PushArrayCell(g_hStylesOnMap, g_iPlayerRecordStyle[client]);
- PushArrayArray(g_hTopsPerStyle, iTop, TOP_LENGTH);
- }
- g_iPlayerRecordStyle[client] = -1;
- g_fPlayerRecordTime[client] = 0.0;
- }
- // Called everytime the player starts his playback from the beginning
- public BotMimic_OnPlayerMimicLoops(client)
- {
- if(g_sBotMimicsRecord[client][0] != '\0')
- {
- // Make the bot invulnerable and render him slightly red
- SetEntProp(client, Prop_Data, "m_takedamage", 0);
- SetEntityRenderMode(client, RENDER_TRANSALPHA);
- SetEntityRenderColor(client, 192, 0, 0, 192);
- //PrintToChatAll("%N starts mimicing %d (should be %d)", client, Timer_GetClientStyle(client), g_iBotMimicsStyle[client]);
- }
- }
- public Action:BotMimic_OnPlayerStartsMimicing(client, String:name[], String:category[], String:path[])
- {
- // He's not mimicing something we recorded.
- if(!StrEqual(category, TIMER_BOTMIMIC_CATEGORY))
- return Plugin_Continue;
- // We didn't tell him to mimic that one.
- if(g_sBotMimicsRecord[client][0] == '\0')
- return Plugin_Continue;
- // Set the Style of this bot to the one this demo was recorded with.
- // This makes sure the conditions are the same for e.g. gravity and stamina settings.
- //Timer_SetClientStyle(client, g_iBotMimicsStyle[client]);
- Timer_SetStyle(client, g_iBotMimicsStyle[client]);
- return Plugin_Continue;
- }
- public BotMimic_OnPlayerStopsMimicing(client, String:name[], String:category[], String:path[])
- {
- // That bot doesn't mimic anything anymore :(
- g_sBotMimicsRecord[client][0] = '\0';
- g_iBotMimicsStyle[client] = -1;
- }
- /**
- * Timer Plugin Callbacks
- */
- public OnTimerStarted(client)
- {
- StartRecording(client);
- }
- StartRecording(client)
- {
- // Don't allow bots to use the timer at all.
- if(IsFakeClient(client))
- {
- Timer_Stop(client);
- return;
- }
- // Stop the old record, if this player has been recording before.
- if(BotMimic_IsPlayerRecording(client))
- BotMimic_StopRecording(client, false);
- ClearHandle(g_hDelayedStopRecording[client]);
- decl String:sStyle[64];
- new style = Timer_GetStyle(client);
- new track = Timer_GetTrack(client);
- Timer_GetStyleName(style, sStyle, sizeof(sStyle));
- // Name the record like STEAMID_Style and save it to our "timer" category in a subfolder named after the style.
- // e.g. data/botmimic/timer/map_name/Style/demo.rec
- decl String:sRecordName[MAX_RECORD_NAME_LENGTH], String:sSteamID[40], String:sSubCat[32];
- GetClientAuthString(client, sSteamID, sizeof(sSteamID));
- ReplaceString(sSteamID, sizeof(sSteamID), ":", "_");
- Format(sSubCat, sizeof(sSubCat), "%d_%d", style, track);
- Format(sRecordName, sizeof(sRecordName), "%d_%d_%s", style, track, sSteamID);
- BotMimic_StartRecording(client, sRecordName, TIMER_BOTMIMIC_CATEGORY, sSubCat);
- /*
- // e.g. data/botmimic/timer/map_name/Style/demo.rec
- decl String:sRecordName[MAX_RECORD_NAME_LENGTH], String:sSteamID[40];
- GetClientAuthString(client, sSteamID, sizeof(sSteamID));
- ReplaceString(sSteamID, sizeof(sSteamID), ":", "_");
- Format(sRecordName, sizeof(sRecordName), "%s_%s", sSteamID, sStyle);
- BotMimic_StartRecording(client, sRecordName, TIMER_BOTMIMIC_CATEGORY, sStyle);
- */
- }
- public OnTimerStopped(client)
- {
- // Don't care if a bot's timer stopped.
- if(IsFakeClient(client))
- return;
- // We've been recording this guy!
- if(BotMimic_IsPlayerRecording(client))
- {
- ClearHandle(g_hDelayedStopRecording[client]);
- // OnTimerStopped is called BEFORE OnTimerWorldRecord... >.<
- // We can't just stop recording here, since we might want to save the recording!
- g_hDelayedStopRecording[client] = CreateTimer(1.0, Timer_StopRecording, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE);
- }
- }
- public OnTimerPaused(client)
- {
- // Don't care if a bot's timer stopped.
- if(IsFakeClient(client))
- return;
- // We've been recording this guy!
- if(BotMimic_IsPlayerRecording(client))
- BotMimic_PauseRecording(client);
- }
- public OnTimerResumed(client)
- {
- // Don't care if a bot's timer stopped.
- if(IsFakeClient(client))
- return;
- // We've been recording this guy!
- if(BotMimic_IsRecordingPaused(client))
- BotMimic_ResumeRecording(client);
- }
- public OnClientStartTouchLevel(client, level, lastlevel)
- {
- if(level < lastlevel)
- return;
- decl String:buffer[32];
- Format(buffer, sizeof(buffer), "Level_%d", level);
- BotMimic_SaveBookmark(client, buffer);
- }
- public OnClientStartTouchBonusLevel(client, level, lastlevel)
- {
- if(level < lastlevel)
- return;
- decl String:buffer[32];
- Format(buffer, sizeof(buffer), "Level_%d", level);
- BotMimic_SaveBookmark(client, buffer);
- }
- public OnTimerWorldRecord(client, track, mode, Float:time, Float:lasttime, currentrank, newrank)
- {
- // Bots should never finish a round, since we don't allow timers, but just in case..
- if(IsFakeClient(client))
- return;
- if(BotMimic_IsPlayerRecording(client))
- {
- ClearHandle(g_hDelayedStopRecording[client]);
- // Only save the record for the 1st on the list.
- g_iPlayerRecordStyle[client] = mode;
- g_fPlayerRecordTime[client] = time;
- BotMimic_StopRecording(client, true);
- }
- }
- public OnTimerDeleteOneRecord(client, Float:time, const String:map[], jumps, Style, flashbangs)
- {
- decl String:sCurrentMap[64];
- // Deleting stuff from other maps - don't care.
- GetCurrentMap(sCurrentMap, sizeof(sCurrentMap));
- if(!StrEqual(sCurrentMap, map))
- return;
- // We don't have a record for this Style?
- new iIndex = FindValueInArray(g_hStylesOnMap, Style);
- if(iIndex == -1)
- return;
- // This wasn't the best time our bot currently mimics.
- new iTop[TOP_LENGTH];
- GetArrayArray(g_hTopsPerStyle, iIndex, iTop, TOP_LENGTH);
- if(iTop[TP_time] != time)
- return;
- RemoveFromArray(g_hStylesOnMap, iIndex);
- RemoveFromArray(g_hTopsPerStyle, iIndex);
- // Stop bots from playing this one
- for(new i=1;i<=MaxClients;i++)
- {
- if(!StrEqual(g_sBotMimicsRecord[i], iTop[TP_demoFile]))
- continue;
- g_sBotMimicsRecord[i][0] = '\0';
- g_iBotMimicsStyle[i] = -1;
- if(BotMimic_IsPlayerMimicing(i))
- BotMimic_StopPlayerMimic(i);
- }
- // Have a different file played.
- HandleBotPlaybackLogic();
- // See if there is a second place who's now 1st :)
- FetchBestTimes();
- }
- public OnTimerWorldRecordCacheLoaded()
- {
- // Only process this callback once a map.
- if(g_bWRFetchedThisMap)
- return;
- g_bWRFetchedThisMap = true;
- FetchBestTimes();
- }
- public Timer_OnClientStyleChanged(client, oldStyle, newStyle)
- {
- PrintToChatAll("%N changed Style from %d to %d (should be %d)", client, oldStyle, newStyle, g_iBotMimicsStyle[client]);
- }
- /**
- * Menu creation and handling
- */
- DisplayBotShowSelectionMenu(client)
- {
- new Handle:hMenu = CreateMenu(Menu_SelectShowBot);
- SetMenuExitButton(hMenu, true);
- decl String:sStyle[64];
- Timer_GetStyleName(Timer_GetStyle(client), sStyle, sizeof(sStyle));
- SetMenuTitle(hMenu, "Timer Replay: Show replay\nYour Style: %s", sStyle);
- // Add all mimicing bots to the menu
- decl String:sIndex[10], String:sBuffer[128], iStyle;
- new iSize = GetArraySize(g_hStylesOnMap);
- for(new d=0;d<iSize;d++)
- {
- iStyle = GetArrayCell(g_hStylesOnMap, d);
- Timer_GetStyleName(iStyle, sStyle, sizeof(sStyle));
- // Check the box, if he wants to see that bot already.
- if(FindValueInArray(g_hShowBot[client], iStyle) != -1)
- Format(sBuffer, sizeof(sBuffer), "[x] %s", sStyle);
- else
- Format(sBuffer, sizeof(sBuffer), "[ ] %s", sStyle);
- IntToString(iStyle, sIndex, sizeof(sIndex));
- AddMenuItem(hMenu, sIndex, sBuffer);
- }
- DisplayMenu(hMenu, client, MENU_TIME_FOREVER);
- }
- public Menu_SelectShowBot(Handle:menu, MenuAction:action, param1, param2)
- {
- switch(action)
- {
- case MenuAction_End:
- {
- CloseHandle(menu);
- }
- case MenuAction_Select:
- {
- decl String:sStyle[10];
- GetMenuItem(menu, param2, sStyle, sizeof(sStyle));
- new iStyle = StringToInt(sStyle);
- // He wants to see that bot now?
- // Toggle
- new iIndex = FindValueInArray(g_hShowBot[param1], iStyle);
- if(iIndex == -1)
- {
- PushArrayCell(g_hShowBot[param1], iStyle);
- }
- else
- {
- RemoveFromArray(g_hShowBot[param1], iIndex);
- }
- DisplayBotShowSelectionMenu(param1);
- }
- }
- }
- /**
- * Helpers
- */
- FetchBestTimes()
- {
- ClearArray(g_hStylesOnMap);
- ClearArray(g_hTopsPerStyle);
- new styleCacheId, Float:fTime, styleTotal, styleTop[TOP_LENGTH];
- decl String:sStyle[64], String:sAuth[MAX_NAME_LENGTH], String:sName[MAX_NAME_LENGTH], String:sBuffer[128];
- new track = 0;
- for(new style=0;style<g_StyleCount;style++)
- {
- // This style is disabled.
- if(!g_Physics[style][StyleEnable])
- continue;
- // Only play ranked best times.
- if(g_Physics[style][StyleCategory] != MCategory_Ranked)
- continue;
- Timer_GetStyleRecordWRStats(style, TRACK_NORMAL, styleCacheId, fTime, styleTotal);
- // No record for this style yet..
- if(fTime <= 0.0)
- continue;
- Timer_GetRecordHolderName(style, TRACK_NORMAL, 1, sName, sizeof(sName));
- Timer_GetRecordHolderAuth(style, TRACK_NORMAL, 1, sAuth, sizeof(sAuth));
- // Build the demo name "STEAMID_Style"
- Timer_GetStyleName(style, sStyle, sizeof(sStyle));
- ReplaceString(sAuth, sizeof(sAuth), ":", "_");
- //Format(sBuffer, sizeof(sBuffer), "%s_%s", sAuth, sStyle);
- Format(sBuffer, sizeof(sBuffer), "%d_%d_%s", style, track, sAuth);
- // Save the details
- strcopy(styleTop[TP_demoFile], MAX_RECORD_NAME_LENGTH, sBuffer);
- strcopy(styleTop[TP_playerName], MAX_NAME_LENGTH, sName);
- styleTop[TP_time] = fTime;
- PushArrayCell(g_hStylesOnMap, style);
- PushArrayArray(g_hTopsPerStyle, styleTop, TOP_LENGTH);
- }
- /*
- decl String:sRecordName[MAX_RECORD_NAME_LENGTH], String:sSteamID[40], String:sSubCat[32];
- GetClientAuthString(client, sSteamID, sizeof(sSteamID));
- ReplaceString(sSteamID, sizeof(sSteamID), ":", "_");
- Format(sSubCat, sizeof(sSubCat), "%d_%d", style, track);
- Format(sRecordName, sizeof(sRecordName), "%d_%d_%s", style, track, sSteamID);
- */
- /*new iCount = Timer_GetStyleCount();
- new iDetails[PhysicsStyle], iRecord[RecordCache], iTop[TOP_LENGTH];
- decl String:sStyle[64], String:sBuffer[128];
- // Go through all Styles, the Timer plugin has in its config.
- // We only save the ones where there actually is a best time
- for(new i=0;i<iCount;i++)
- {
- Timer_GetStyleDetailsByIndex(i, iDetails);
- // Is there a record for that Style?
- if(!Timer_GetWorldRecordForStyle(iDetails[Id], iRecord))
- continue;
- // Build the demo name "STEAMID_Style"
- ReplaceString(iRecord[Auth], sizeof(iRecord[Auth]), ":", "_");
- Timer_GetStyleName(iDetails[Id], sStyle, sizeof(sStyle));
- Format(sBuffer, sizeof(sBuffer), "%s_%s", iRecord[Auth], sStyle);
- // Save the details
- strcopy(iTop[TP_demoFile], MAX_RECORD_NAME_LENGTH, sBuffer);
- strcopy(iTop[TP_playerName], MAX_NAME_LENGTH, iRecord[Name]);
- iTop[TP_time] = iRecord[Time];
- PushArrayCell(g_hStylesOnMap, iDetails[Id]);
- PushArrayArray(g_hTopsPerStyle, iTop, TOP_LENGTH);
- }*/
- }
- // Players always see the bot for their own Style by default.
- CheckForStyleChange(client)
- {
- new iStyle = Timer_GetStyle(client);
- // This player didn't change his Style.
- if(iStyle == g_iPlayerStyle[client])
- return;
- g_iPlayerStyle[client] = iStyle;
- // Only add him, if the player doesn't want to seem him already.
- if(FindValueInArray(g_hShowBot[client], iStyle) == -1)
- PushArrayCell(g_hShowBot[client], iStyle);
- }
- RenameBot(client, index)
- {
- new iTop[TOP_LENGTH];
- GetArrayArray(g_hTopsPerStyle, index, iTop, TOP_LENGTH);
- // Set the clantag to the Style
- decl String:sNewName[MAX_NAME_LENGTH], String:sTime[32], String:sStyle[32];
- Timer_GetStyleName(GetArrayCell(g_hStylesOnMap, index), sStyle, sizeof(sStyle));
- CS_SetClientClanTag(client, sStyle);
- // Name the bot after the player who did the record including the time.
- Timer_SecondsToTime(iTop[TP_time], sTime, sizeof(sTime), 2);
- Format(sNewName, sizeof(sNewName), "%s - %s", iTop[TP_playerName], sTime);
- SetClientInfo(client, "name", sNewName);
- }
- ClearBots()
- {
- // Don't kick anyone, if we might want him to mimic stuff!
- if(g_sNextBotMimicsThis[0] != '\0')
- return;
- // Kick any bots, that don't mimic anything!
- for(new i=1;i<=MaxClients;i++)
- {
- // Only consider bots
- if(!IsClientInGame(i) || !IsFakeClient(i) || IsClientSourceTV(i) || IsClientReplay(i))
- continue;
- // This bot mimics something.
- if(g_sBotMimicsRecord[i][0] != '\0')
- continue;
- // Don't kick bots, which mimic something else.
- if(BotMimic_IsPlayerMimicing(i))
- continue;
- ServerCommand("bot_kick \"%s\"", g_sRealBotName[i]);
- }
- }
- HandleBotPlaybackLogic()
- {
- // That bot takes his time..
- if(g_iBotAddTime != -1 && (GetTime() - g_iBotAddTime) > 5)
- {
- // Try to add another one.
- g_iBotAddTime = GetTime();
- ServerCommand("bot_add");
- return;
- }
- // Don't do anything while we wait for a bot to join.
- if(g_sNextBotMimicsThis[0] != '\0')
- return;
- // Kick all bots, which don't mimic something currently, if we're currently having someone mimicing something.
- new iSize = GetArraySize(g_hStylesOnMap);
- if(IsRecordPlayed() || !iSize)
- ClearBots();
- // No records to play for this map
- if(!iSize)
- return;
- // Check that we've got a bot mimicing the best time for each Style
- new iTop[TOP_LENGTH];
- new BMError:error, bool:bIsPlayed;
- for(new d=0;d<iSize;d++)
- {
- GetArrayArray(g_hTopsPerStyle, d, iTop, TOP_LENGTH);
- // We didn't find any bot who mimics that one yet.
- bIsPlayed = false;
- // That one already errored..
- if(iTop[TP_fileNotFound])
- continue;
- // Is some bot currently mimicing that one?
- for(new i=1;i<=MaxClients;i++)
- {
- // Only consider bots
- if(!IsClientInGame(i) || !IsFakeClient(i) || IsClientSourceTV(i) || IsClientReplay(i))
- continue;
- // Yep, no need to do anything
- if(StrEqual(g_sBotMimicsRecord[i], iTop[TP_demoFile]))
- {
- // This record is already played by a different bot!!
- if(bIsPlayed)
- {
- g_iBotMimicsStyle[i] = -1;
- g_sBotMimicsRecord[i][0] = '\0';
- if(BotMimic_IsPlayerMimicing(i))
- BotMimic_StopPlayerMimic(i);
- continue;
- }
- // Remember that some bot is mimicing that record -> don't have two bots mimicing the same.
- bIsPlayed = true;
- // Is he actually still mimicing our record?
- if(BotMimic_IsPlayerMimicing(i))
- continue;
- // That bot wasn't mimicing the record anymore?!
- error = BotMimic_PlayRecordByName(i, g_sBotMimicsRecord[i]);
- if(error != BM_NoError)
- {
- decl String:sError[64];
- BotMimic_GetErrorString(error, sError, sizeof(sError));
- LogError("Error playing record %s: %s", g_sBotMimicsRecord[i], sError);
- g_iBotMimicsStyle[i] = -1;
- g_sBotMimicsRecord[i][0] = '\0';
- iTop[TP_fileNotFound] = true;
- SetArrayArray(g_hTopsPerStyle, d, iTop, TOP_LENGTH);
- // This one isn't mimicing :(
- bIsPlayed = false;
- }
- }
- }
- // This one is covered.
- if(bIsPlayed)
- continue;
- // No bot mimics this best time?
- // Check if there is a spare bot to use on the server
- for(new i=1;i<=MaxClients;i++)
- {
- // That guy is already playing something
- if(g_sBotMimicsRecord[i][0] != '\0')
- continue;
- // Only consider bots
- if(!IsClientInGame(i) || !IsFakeClient(i) || IsClientSourceTV(i) || IsClientReplay(i))
- continue;
- // He's already mimicing something else?
- if(BotMimic_IsPlayerMimicing(i))
- continue;
- // Have him play the record!
- strcopy(g_sBotMimicsRecord[i], sizeof(g_sBotMimicsRecord[]), iTop[TP_demoFile]);
- g_iBotMimicsStyle[i] = GetArrayCell(g_hStylesOnMap, d);
- error = BotMimic_PlayRecordByName(i, iTop[TP_demoFile]);
- if(error == BM_NoError)
- {
- bIsPlayed = true;
- RenameBot(i, d);
- break;
- }
- else
- {
- decl String:sError[64];
- BotMimic_GetErrorString(error, sError, sizeof(sError));
- LogError("Error playing record %s: %s", iTop[TP_demoFile], sError);
- g_iBotMimicsStyle[i] = -1;
- g_sBotMimicsRecord[i][0] = '\0';
- // Remember that this record failed so we don't try again.
- iTop[TP_fileNotFound] = true;
- SetArrayArray(g_hTopsPerStyle, d, iTop, TOP_LENGTH);
- }
- }
- // Still no bot found?!
- if(!bIsPlayed && !iTop[TP_fileNotFound])
- {
- // Add one.
- strcopy(g_sNextBotMimicsThis, sizeof(g_sNextBotMimicsThis), iTop[TP_demoFile]);
- g_iBotAddTime = GetTime();
- ServerCommand("bot_add");
- // We need to wait until the player joined..
- return;
- }
- }
- }
- bool:IsRecordPlayed()
- {
- for(new i=1;i<=MaxClients;i++)
- {
- if(g_sBotMimicsRecord[i][0] != '\0')
- return true;
- }
- return false;
- }
- stock ClearHandle(&Handle:hndl)
- {
- if(hndl != INVALID_HANDLE)
- {
- CloseHandle(hndl);
- hndl = INVALID_HANDLE;
- }
- }
- AddFilterToTriggers()
- {
- // Reset all previously ignored entities
- // GetMaxEntities()*2 due to logical not networked entities.
- for(new i=0;i<8097;i++)
- g_bShouldNotCollide[i] = false;
- decl String:sBuffer[64];
- new iEnt;
- // Let bots pass through all ignored entities
- for(new i=0;i<sizeof(g_sIgnoreEntities);i++)
- {
- iEnt = -1;
- while((iEnt = FindEntityByClassname(iEnt, g_sIgnoreEntities[i])) != -1)
- {
- GetEntPropString(iEnt, Prop_Data, "m_iName", sBuffer, sizeof(sBuffer));
- // Check if this entity got a targetname set or ignore, if not needed.
- if(!g_bNeedsTargetname[i] || sBuffer[0] != 0)
- {
- // mimicing bots shouldn't collide with this entity.
- g_bShouldNotCollide[iEnt] = true;
- }
- }
- }
- // Add a filter to trigger_multiples
- static iFilter = -1;
- if(iFilter == -1 || !IsValidEntity(iFilter))
- {
- iFilter = CreateEntityByName("filter_activator_name");
- if(iFilter == -1)
- return;
- DispatchKeyValue(iFilter, "filtername", "mimic_bot");
- DispatchKeyValue(iFilter, "Negated", "1");
- DispatchKeyValue(iFilter, "targetname", "mimic_filter");
- // Activate the filter to fetch his filtername.
- ActivateEntity(iFilter);
- }
- // Add the filter to all trigger_multiple
- iEnt = -1;
- while((iEnt = FindEntityByClassname(iEnt, "trigger_multiple")) != -1)
- {
- GetEntPropString(iEnt, Prop_Data, "m_iFilterName", sBuffer, sizeof(sBuffer));
- if(sBuffer[0] != '\0')
- {
- LogError("trigger_multiple %d already has a filter set: %s", iEnt, sBuffer);
- continue;
- }
- DispatchKeyValue(iEnt, "filtername", "mimic_filter");
- ActivateEntity(iEnt);
- }
- }
- stock Timer_GetStyleName(style, String:sName[], maxlen)
- {
- strcopy(sName, maxlen, g_Physics[style][StyleName]);
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement