/* ** cheats.cpp ** Common cheat code ** **--------------------------------------------------------------------------- // Copyright 1999-2016 Randy Heit // Copyright 2002-2020 Christoph Oelckers ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions ** are met: ** ** 1. Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** 2. Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in the ** documentation and/or other materials provided with the distribution. ** 3. The name of the author may not be used to endorse or promote products ** derived from this software without specific prior written permission. ** ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OFf ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. **--------------------------------------------------------------------------- ** */ #include "gamestruct.h" #include "printf.h" #include "c_cvars.h" #include "cheathandler.h" #include "c_dispatch.h" #include "d_net.h" #include "gamestate.h" #include "gstrings.h" #include "gamecontrol.h" #include "screenjob.h" #include "mapinfo.h" #include "statistics.h" #include "filesystem.h" CVAR(Bool, sv_cheats, false, CVAR_ARCHIVE|CVAR_SERVERINFO) CVAR(Bool, cl_blockcheats, false, 0) //--------------------------------------------------------------------------- // // // //--------------------------------------------------------------------------- bool CheckCheatmode (bool printmsg, bool sponly) { if ((sponly && netgame) || gamestate != GS_LEVEL) { if (printmsg) Printf ("Not in a singleplayer game.\n"); return true; } else if ((netgame /*|| deathmatch*/) && (!sv_cheats)) { if (printmsg) Printf ("sv_cheats must be true to enable this command.\n"); return true; } else if (cl_blockcheats != 0) { if (printmsg && cl_blockcheats == 1) Printf ("cl_blockcheats is turned on and disabled this command.\n"); return true; } else { const char *gamemsg = gi->CheckCheatMode(); // give the game anopportuity to add its own blocks. if (printmsg && gamemsg) { Printf("%s\n", gamemsg); return true; } return false; } } //--------------------------------------------------------------------------- // // // //--------------------------------------------------------------------------- void genericCheat(int player, uint8_t** stream, bool skip) { int cheat = ReadByte(stream); if (skip) return; const char *msg = gi->GenericCheat(player, cheat); if (!msg || !*msg) // Don't print blank lines. return; if (player == myconnectindex) Printf(PRINT_NOTIFY, "%s\n", msg); else { FString message = GStrings("TXT_X_CHEATS"); //message.Substitute("%s", player->userinfo.GetName()); // fixme - make globally accessible Printf(PRINT_NOTIFY, "%s: %s\n", message.GetChars(), msg); } } //--------------------------------------------------------------------------- // // // //--------------------------------------------------------------------------- CCMD(fly) { if (!CheckCheatmode(true, true)) { Net_WriteByte(DEM_GENERICCHEAT); Net_WriteByte(CHT_FLY); } } //--------------------------------------------------------------------------- // // // //--------------------------------------------------------------------------- CCMD(god) { if (!CheckCheatmode(true, true)) // Right now the god cheat is a global setting in some games and not a player property. This should be changed. { Net_WriteByte(DEM_GENERICCHEAT); Net_WriteByte(CHT_GOD); } } CCMD(godon) { if (!CheckCheatmode(true, true)) { Net_WriteByte(DEM_GENERICCHEAT); Net_WriteByte(CHT_GODON); } } CCMD(godoff) { if (!CheckCheatmode(true, true)) { Net_WriteByte(DEM_GENERICCHEAT); Net_WriteByte(CHT_GODOFF); } } CCMD(noclip) { if (!CheckCheatmode(true, true)) { Net_WriteByte(DEM_GENERICCHEAT); Net_WriteByte(CHT_NOCLIP); } } //--------------------------------------------------------------------------- // // // //--------------------------------------------------------------------------- CCMD(give) { static const char* type[] = { "ALL","AMMO","ARMOR","HEALTH","INVENTORY","ITEMS","KEYS","WEAPONS" }; if (argv.argc() < 2) { Printf("give : gives requested item\n"); return; } for (size_t i = 0; i < countof(type); i++) { if (!stricmp(argv[1], type[i])) { if (!CheckCheatmode(true, true)) { Net_WriteByte(DEM_GIVE); Net_WriteByte((uint8_t)i); } return; } } Printf("Unable to give %s\n", argv[1]); } //--------------------------------------------------------------------------- // // // //--------------------------------------------------------------------------- void CompleteLevel(MapRecord* map) { gameaction = ga_completed; g_nextmap = !currentLevel || !(currentLevel->flags & MI_FORCEEOG)? map : nullptr; g_nextskill = -1; // This does not change the skill g_bossexit = false; } //--------------------------------------------------------------------------- // // // //--------------------------------------------------------------------------- void changeMap(int player, uint8_t** stream, bool skip) { int skill = (int8_t)ReadByte(stream); int bossexit = (int8_t)ReadByte(stream); auto mapname = ReadStringConst(stream); if (skip) return; auto map = FindMapByName(mapname); if (map || *mapname == 0) // mapname = "" signals end of episode { gameaction = ga_completed; g_nextmap = map; g_nextskill = skill; g_bossexit = bossexit; } } //--------------------------------------------------------------------------- // // // //--------------------------------------------------------------------------- void endScreenJob(int player, uint8_t** stream, bool skip) { if (!skip) gameaction = ga_endscreenjob; } //--------------------------------------------------------------------------- // // // //--------------------------------------------------------------------------- void ChangeLevel(MapRecord* map, int skill, bool bossexit) { Net_WriteByte(DEM_CHANGEMAP); Net_WriteByte(skill); Net_WriteByte(bossexit); Net_WriteString(map? map->labelName.GetChars() : nullptr); } //--------------------------------------------------------------------------- // // // //--------------------------------------------------------------------------- void DeferredStartGame(MapRecord* map, int skill, bool nostopsound) { g_nextmap = map; g_nextskill = skill; g_bossexit = false; gameaction = nostopsound? ga_newgamenostopsound : ga_newgame; } //--------------------------------------------------------------------------- // // // //--------------------------------------------------------------------------- static MapRecord* levelwarp_common(FCommandLine& argv, const char *cmdname, const char *t2) { int numparm = g_gameType & (GAMEFLAG_SW | GAMEFLAG_PSEXHUMED) ? 1 : 2; // Handle games with episodic and non-episodic level order. if (numparm == 2 && argv.argc() == 2) numparm = 1; if (argv.argc() <= numparm) { if (numparm == 2) Printf(PRINT_BOLD, "%s : %s episode 'e' and map 'm'\n", cmdname, t2); else Printf(PRINT_BOLD, "%s : %s map 'm'\n", cmdname, t2); return nullptr; } // Values are one-based. int e = numparm == 2 ? atoi(argv[1]) : 1; int m = atoi(numparm == 2 ? argv[2] : argv[1]); if (e <= 0 || m <= 0) { Printf(PRINT_BOLD, "Invalid level! Numbers must be > 0\n"); return nullptr; } auto map = FindMapByIndex(e, m); if (!map) { if (numparm == 2) Printf(PRINT_BOLD, "Level E%s L%s not found!\n", argv[1], argv[2]); else Printf(PRINT_BOLD, "Level %s not found!\n", argv[1]); return nullptr; } if (fileSystem.FindFile(map->fileName.GetChars()) < 0) { Printf(PRINT_BOLD, "%s: map file not found\n", map->fileName.GetChars()); } return map; } //--------------------------------------------------------------------------- // // // //--------------------------------------------------------------------------- CCMD(levelwarp) { if (!gi->CanSave()) { Printf("Use the startgame command when not in a game.\n"); return; } #if 0 if (/*!players[consoleplayer].settings_controller &&*/ netgame) { Printf("Only setting controllers can change the map.\n"); return; } #endif auto map = levelwarp_common(argv, "levelwarp", "warp to"); if (map) { ChangeLevel(map, g_nextskill); } } //--------------------------------------------------------------------------- // // // //--------------------------------------------------------------------------- CCMD(levelstart) { if (netgame) { Printf("Use " TEXTCOLOR_BOLD "levelwarp" TEXTCOLOR_NORMAL " instead. " TEXTCOLOR_BOLD "levelstart" TEXTCOLOR_NORMAL " is for single-player only.\n"); return; } auto map = levelwarp_common(argv, "start game", "start new game at"); if (map) { DeferredStartGame(map, g_nextskill); } } //--------------------------------------------------------------------------- // // // //--------------------------------------------------------------------------- CCMD(changemap) { if (argv.argc() < 2) { Printf(PRINT_BOLD, "changemap : warp to the given map, identified by its name.\n"); return; } if (!gi->CanSave()) { Printf("Use the map command when not in a game.\n"); return; } #if 0 if (/*!players[consoleplayer].settings_controller &&*/ netgame) { Printf("Only setting controllers can change the map.\n"); return; } #endif FString mapname = argv[1]; auto map = FindMapByName(mapname.GetChars()); if (map == nullptr) { // got a user map Printf(PRINT_BOLD, "%s: Map not defined.\n", mapname.GetChars()); return; } if (map->flags & MI_USERMAP) { // got a user map Printf(PRINT_BOLD, "%s: Cannot warp to user maps.\n", mapname.GetChars()); return; } if (fileSystem.FindFile(map->fileName.GetChars()) < 0) { Printf(PRINT_BOLD, "%s: map file not found\n", map->fileName.GetChars()); } ChangeLevel(map, g_nextskill); } //--------------------------------------------------------------------------- // // // //--------------------------------------------------------------------------- CCMD(map) { if (argv.argc() < 2) { Printf(PRINT_BOLD, "map : start new game at the given map, identified by its name.\n"); return; } if (netgame) { Printf("Use " TEXTCOLOR_BOLD "changemap" TEXTCOLOR_NORMAL " instead. " TEXTCOLOR_BOLD "map" TEXTCOLOR_NORMAL " is for single-player only.\n"); return; } FString mapfilename = argv[1]; DefaultExtension(mapfilename, ".map"); // Check if the map is already defined. auto map = FindMapByName(argv[1]); if (map == nullptr) { map = SetupUserMap(mapfilename.GetChars(), g_gameType & GAMEFLAG_DUKE ? "dethtoll.mid" : nullptr); } if (map) { if (fileSystem.FindFile(map->fileName.GetChars()) < 0) { Printf(PRINT_BOLD, "%s: map file not found\n", map->fileName.GetChars()); } DeferredStartGame(map, g_nextskill); } } //--------------------------------------------------------------------------- // // // //--------------------------------------------------------------------------- CCMD(restartmap) { if (gamestate != GS_LEVEL || currentLevel == nullptr) { Printf("Must be in a game to restart a level.\n"); return; } ChangeLevel(currentLevel, g_nextskill); } //--------------------------------------------------------------------------- // // // //--------------------------------------------------------------------------- CUSTOM_CVAR(Float, i_timescale, 1.0f, CVAR_NOINITCALL) { if (netgame) { Printf("Time scale cannot be changed in net games.\n"); self = 1.0f; } else if (self >= 0.05f) { I_FreezeTime(true); TimeScale = self; I_FreezeTime(false); } else { Printf("Time scale must be at least 0.05!\n"); } } CCMD(endofgame) { STAT_Update(true); ChangeLevel(nullptr, g_nextskill); } //--------------------------------------------------------------------------- // // // //--------------------------------------------------------------------------- CCMD(skill) { if (gamestate == GS_LEVEL) { auto argsCount = argv.argc(); if (argsCount < 2) { auto currentSkill = gi->GetCurrentSkill(); if (currentSkill >= 0) { Printf("Current skill is %d (%s)\n", currentSkill, GStrings.localize(gSkillNames[currentSkill].GetChars())); } else if (currentSkill == -1) { Printf("Current skill is not set\n"); } else if (currentSkill == -2) { Printf("This game has no skill settings.\n"); } else { Printf("Current skill is an unknown/unsupported value (%d)\n", currentSkill); } } else if (argsCount == 2) { // Get maximum valid skills for loaded game. auto maxvalidskills = 0; for (auto i = 0; i < MAXSKILLS; i++) { if (gSkillNames[i].IsNotEmpty()) { maxvalidskills++; } } // Test and set skill if its legal. auto newSkill = atoi(argv[1]); if (newSkill >= 0 && newSkill < maxvalidskills) { g_nextskill = newSkill; Printf("Skill will be changed for next game.\n"); } else { Printf("Please specify a skill level between 0 and %d\n", maxvalidskills - 1); for (auto i = 0; i < maxvalidskills; i++) { Printf("%d = '%s'\n", i, GStrings.localize(gSkillNames[i].GetChars())); } } } else if (argsCount > 2) { Printf(PRINT_BOLD, "skill : returns the current skill level, and optionally sets the skill level for the next game if provided and is valid.\n"); } } else { Printf("Currently not in a game.\n"); } }