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:
ds@2400: #if 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
pred@2077: SH_ADD_HOOK_MEMFUNC(IVEngineServer, ChangeLevel, engine, this, &NextMapManager::HookChangeLevel, false);
pred@2077: #endif
pred@2203:
ds@2400: ConCommand *pCmd = FindCommand("changelevel");
pred@2203: if (pCmd != NULL)
pred@2203: {
pred@2203: SH_ADD_HOOK_STATICFUNC(ConCommand, Dispatch, pCmd, 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
pred@2077: SH_REMOVE_HOOK_MEMFUNC(IVEngineServer, ChangeLevel, engine, this, &NextMapManager::HookChangeLevel, false);
pred@2077: #endif
pred@2203:
pred@2203: if (changeLevelCmd != NULL)
pred@2203: {
pred@2203: SH_REMOVE_HOOK_STATICFUNC(ConCommand, Dispatch, changeLevelCmd, 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:
ds@2400: #if 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: }