pred@2077: /** pred@2077: * vim: set ts=4 : pred@2077: * ============================================================================= pred@2077: * SourceMod pred@2077: * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. pred@2077: * ============================================================================= pred@2077: * pred@2077: * This program is free software; you can redistribute it and/or modify it under pred@2077: * the terms of the GNU General Public License, version 3.0, as published by the pred@2077: * Free Software Foundation. pred@2077: * pred@2077: * This program is distributed in the hope that it will be useful, but WITHOUT pred@2077: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS pred@2077: * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more pred@2077: * details. pred@2077: * pred@2077: * You should have received a copy of the GNU General Public License along with pred@2077: * this program. If not, see . pred@2077: * pred@2077: * As a special exception, AlliedModders LLC gives you permission to link the pred@2077: * code of this program (as well as its derivative works) to "Half-Life 2," the pred@2077: * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software pred@2077: * by the Valve Corporation. You must obey the GNU General Public License in pred@2077: * all respects for all other code used. Additionally, AlliedModders LLC grants pred@2077: * this exception to all derivative works. AlliedModders LLC defines further pred@2077: * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), pred@2077: * or . pred@2077: * pred@2077: * Version: $Id$ pred@2077: */ pred@2077: pred@2077: #include "NextMap.h" pred@2077: #include "Logger.h" skamonkey@3789: #include "HalfLife2.h" pred@2077: #include "sourcemm_api.h" pred@2077: #include "sm_stringutil.h" pred@2203: #include "sourcehook.h" pred@2203: #include "sm_srvcmds.h" pred@2077: pred@2077: NextMapManager g_NextMap; pred@2077: ds@2583: #if SOURCE_ENGINE != SE_DARKMESSIAH pred@2077: SH_DECL_HOOK2_void(IVEngineServer, ChangeLevel, SH_NOATTRIB, 0, const char *, const char *); ds@2583: #else ds@2583: SH_DECL_HOOK4_void(IVEngineServer, ChangeLevel, SH_NOATTRIB, 0, const char *, const char *, const char *, bool); ds@2583: #endif pred@2077: skamonkey@3899: #if SOURCE_ENGINE == SE_DOTA skamonkey@3942: SH_DECL_EXTERN2_void(ConCommand, Dispatch, SH_NOATTRIB, false, const CCommandContext &, const CCommand &); skamonkey@3899: #elif SOURCE_ENGINE >= SE_ORANGEBOX pred@2203: SH_DECL_EXTERN1_void(ConCommand, Dispatch, SH_NOATTRIB, false, const CCommand &); ds@2583: #elif SOURCE_ENGINE == SE_DARKMESSIAH ds@2583: SH_DECL_EXTERN0_void(ConCommand, Dispatch, SH_NOATTRIB, false); pred@2203: #else dvander@2843: # if SH_IMPL_VERSION >= 4 dvander@2843: extern int __SourceHook_FHAddConCommandDispatch(void *,bool,class fastdelegate::FastDelegate0); dvander@2843: # else dvander@2843: extern bool __SourceHook_FHAddConCommandDispatch(void *,bool,class fastdelegate::FastDelegate0); dvander@2843: #endif pred@2203: extern bool __SourceHook_FHRemoveConCommandDispatch(void *,bool,class fastdelegate::FastDelegate0); pred@2203: #endif pred@2203: pred@2203: ConCommand *changeLevelCmd = NULL; pred@2203: pred@2077: ConVar sm_nextmap("sm_nextmap", "", FCVAR_NOTIFY); pred@2077: pred@2203: bool g_forcedChange = false; pred@2203: pred@2077: void NextMapManager::OnSourceModAllInitialized_Post() pred@2077: { ds@2400: #if SOURCE_ENGINE >= SE_ORANGEBOX pred@2077: SH_ADD_HOOK(IVEngineServer, ChangeLevel, engine, SH_MEMBER(this, &NextMapManager::HookChangeLevel), false); pred@2077: #else kyle@3819: SH_ADD_HOOK(IVEngineServer, ChangeLevel, engine, SH_MEMBER(this, &NextMapManager::HookChangeLevel), false); pred@2077: #endif pred@2203: ds@2400: ConCommand *pCmd = FindCommand("changelevel"); pred@2203: if (pCmd != NULL) pred@2203: { kyle@3819: SH_ADD_HOOK(ConCommand, Dispatch, pCmd, SH_STATIC(CmdChangeLevelCallback), false); pred@2203: changeLevelCmd = pCmd; pred@2203: } pred@2077: } pred@2077: pred@2077: void NextMapManager::OnSourceModShutdown() pred@2077: { ds@2400: #if SOURCE_ENGINE >= SE_ORANGEBOX pred@2077: SH_REMOVE_HOOK(IVEngineServer, ChangeLevel, engine, SH_MEMBER(this, &NextMapManager::HookChangeLevel), false); pred@2077: #else kyle@3819: SH_REMOVE_HOOK(IVEngineServer, ChangeLevel, engine, SH_MEMBER(this, &NextMapManager::HookChangeLevel), false); pred@2077: #endif pred@2203: pred@2203: if (changeLevelCmd != NULL) pred@2203: { kyle@3819: SH_REMOVE_HOOK(ConCommand, Dispatch, changeLevelCmd, SH_STATIC(CmdChangeLevelCallback), false); pred@2203: } pred@2203: pred@2203: SourceHook::List::iterator iter; pred@2203: iter = m_mapHistory.begin(); pred@2203: pred@2203: while (iter != m_mapHistory.end()) pred@2203: { pred@2203: delete (MapChangeData *)*iter; pred@2203: iter = m_mapHistory.erase(iter); pred@2203: } pred@2077: } pred@2077: pred@2077: const char *NextMapManager::GetNextMap() pred@2077: { pred@2077: return sm_nextmap.GetString(); pred@2077: } pred@2077: pred@2077: bool NextMapManager::SetNextMap(const char *map) pred@2077: { skamonkey@3789: if (!g_HL2.IsMapValid(map)) pred@2077: { pred@2077: return false; pred@2077: } pred@2077: pred@2077: sm_nextmap.SetValue(map); pred@2077: pred@2077: return true; pred@2077: } pred@2077: ds@2583: #if SOURCE_ENGINE != SE_DARKMESSIAH pred@2077: void NextMapManager::HookChangeLevel(const char *map, const char *unknown) ds@2583: #else ds@2583: void NextMapManager::HookChangeLevel(const char *map, const char *unknown, const char *video, bool bLongLoading) ds@2583: #endif pred@2077: { pred@2203: if (g_forcedChange) pred@2203: { pred@2203: g_Logger.LogMessage("[SM] Changed map to \"%s\"", map); pred@2203: RETURN_META(MRES_IGNORED); pred@2203: } pred@2203: pred@2077: const char *newmap = sm_nextmap.GetString(); pred@2077: skamonkey@3789: if (newmap[0] == 0 || !g_HL2.IsMapValid(newmap)) pred@2077: { pred@2077: RETURN_META(MRES_IGNORED); pred@2077: } pred@2077: pred@2077: g_Logger.LogMessage("[SM] Changed map to \"%s\"", newmap); pred@2077: pred@2203: UTIL_Format(m_tempChangeInfo.m_mapName, sizeof(m_tempChangeInfo.m_mapName), newmap); pred@2203: UTIL_Format(m_tempChangeInfo.m_changeReason, sizeof(m_tempChangeInfo.m_changeReason), "Normal level change"); pred@2203: ds@2583: #if SOURCE_ENGINE != SE_DARKMESSIAH pred@2077: RETURN_META_NEWPARAMS(MRES_IGNORED, &IVEngineServer::ChangeLevel, (newmap, unknown)); ds@2583: #else ds@2583: RETURN_META_NEWPARAMS(MRES_IGNORED, &IVEngineServer::ChangeLevel, (newmap, unknown, video, bLongLoading)); ds@2583: #endif pred@2077: } pred@2203: pred@2203: void NextMapManager::OnSourceModLevelChange( const char *mapName ) pred@2203: { pred@2203: /* Skip the first 'mapchange' when the server starts up */ pred@2203: if (m_tempChangeInfo.startTime != 0) pred@2203: { pred@2203: if (strcmp(mapName, m_tempChangeInfo.m_mapName) == 0) pred@2203: { pred@2203: /* The map change was as we expected */ pred@2203: m_mapHistory.push_back(new MapChangeData(lastMap, m_tempChangeInfo.m_changeReason, m_tempChangeInfo.startTime)); pred@2203: } pred@2203: else pred@2203: { pred@2203: /* Something intercepted the mapchange */ pred@2203: char newReason[255]; pred@2203: UTIL_Format(newReason, sizeof(newReason), "%s (Map overridden)", m_tempChangeInfo.m_changeReason); pred@2203: m_mapHistory.push_back(new MapChangeData(lastMap, newReason, m_tempChangeInfo.startTime)); pred@2203: } pred@2203: pred@2203: /* TODO: Should this be customizable? */ pred@2203: if (m_mapHistory.size() > 20) pred@2203: { pred@2203: SourceHook::List::iterator iter; pred@2203: iter = m_mapHistory.begin(); pred@2203: pred@2203: delete (MapChangeData *)*iter; pred@2203: pred@2203: m_mapHistory.erase(iter); pred@2203: } pred@2203: } pred@2203: pred@2203: m_tempChangeInfo.m_mapName[0] ='\0'; pred@2203: m_tempChangeInfo.m_changeReason[0] = '\0'; pred@2203: m_tempChangeInfo.startTime = time(NULL); pred@2203: UTIL_Format(lastMap, sizeof(lastMap), mapName); pred@2203: } pred@2203: pred@2203: void NextMapManager::ForceChangeLevel( const char *mapName, const char* changeReason ) pred@2203: { pred@2203: /* Store the mapname and reason */ pred@2203: UTIL_Format(m_tempChangeInfo.m_mapName, sizeof(m_tempChangeInfo.m_mapName), mapName); pred@2203: UTIL_Format(m_tempChangeInfo.m_changeReason, sizeof(m_tempChangeInfo.m_changeReason), changeReason); pred@2203: pred@2203: /* Change level and skip our hook */ pred@2203: g_forcedChange = true; pred@2203: engine->ChangeLevel(mapName, NULL); pred@2203: g_forcedChange = false; pred@2203: } pred@2203: pred@2203: NextMapManager::NextMapManager() pred@2203: { pred@2203: m_tempChangeInfo = MapChangeData(); pred@2203: m_mapHistory = SourceHook::List(); pred@2203: } pred@2203: skamonkey@3899: #if SOURCE_ENGINE == SE_DOTA skamonkey@3942: void CmdChangeLevelCallback(const CCommandContext &context, const CCommand &command) skamonkey@3899: { skamonkey@3899: #elif SOURCE_ENGINE >= SE_ORANGEBOX pred@2203: void CmdChangeLevelCallback(const CCommand &command) pred@2203: { pred@2203: #else pred@2203: void CmdChangeLevelCallback() pred@2203: { pred@2203: CCommand command; pred@2203: #endif pred@2203: pred@2203: if (command.ArgC() < 2) pred@2203: { pred@2203: return; pred@2203: } pred@2203: pred@2203: if (g_NextMap.m_tempChangeInfo.m_mapName[0] == '\0') pred@2203: { pred@2203: UTIL_Format(g_NextMap.m_tempChangeInfo.m_mapName, sizeof(g_NextMap.m_tempChangeInfo.m_mapName), command.Arg(1)); pred@2203: UTIL_Format(g_NextMap.m_tempChangeInfo.m_changeReason, sizeof(g_NextMap.m_tempChangeInfo.m_changeReason), "changelevel Command"); pred@2203: } pred@2205: }