raze/source/core/cheats.cpp
Christoph Oelckers 43f4962561 - got rid of the global compatibility modes and made 'precise' a parameter for clipmove.
This better reflects how this stuff gets used.
2023-11-12 14:45:24 +01:00

567 lines
14 KiB
C++

/*
** 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 <all|health|weapons|ammo|armor|keys|inventory>: 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 <e> <m>: %s episode 'e' and map 'm'\n", cmdname, t2);
else Printf(PRINT_BOLD, "%s <m>: %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 <mapname>: 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 <mapname>: 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 <level>: 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");
}
}