/* ** ** **--------------------------------------------------------------------------- ** Copyright 1999-2016 Randy Heit ** Copyright 2005-2016 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 OF ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. **--------------------------------------------------------------------------- ** */ // DESCRIPTION: // Cheat sequence checking. // #include #include "m_cheat.h" #include "d_player.h" #include "gstrings.h" #include "p_local.h" #include "gi.h" #include "p_enemy.h" #include "sbar.h" #include "c_dispatch.h" #include "a_keys.h" #include "d_net.h" #include "serializer.h" #include "serialize_obj.h" #include "r_utility.h" #include "a_morph.h" #include "g_levellocals.h" #include "vm.h" #include "d_main.h" uint8_t globalfreeze, globalchangefreeze; // user's freeze state. // [RH] Actually handle the cheat. The cheat code in st_stuff.c now just // writes some bytes to the network data stream, and the network code // later calls us. void cht_DoMDK(player_t *player, const char *mod) { if (player->mo == NULL) { Printf("%s\n", GStrings("TXT_WHAT_KILL")); } else if (!deathmatch) { // Don't allow this in deathmatch even with cheats enabled, because it's // a very very cheap kill. P_LineAttack(player->mo, player->mo->Angles.Yaw, PLAYERMISSILERANGE, P_AimLineAttack(player->mo, player->mo->Angles.Yaw, PLAYERMISSILERANGE), TELEFRAG_DAMAGE, mod, NAME_BulletPuff); } } void cht_DoCheat (player_t *player, int cheat) { static const char * const BeholdPowers[9] = { "PowerInvulnerable", "PowerStrength", "PowerInvisibility", "PowerIronFeet", "MapRevealer", "PowerLightAmp", "PowerShadow", "PowerMask", "PowerTargeter", }; PClassActor *type; AActor *item; FString smsg; const char *msg = ""; int i; // No cheating when not having a pawn attached. if (player->mo == nullptr) { return; } switch (cheat) { case CHT_IDDQD: if (!(player->cheats & CF_GODMODE) && player->playerstate == PST_LIVE) { if (player->mo) player->mo->health = deh.GodHealth; player->health = deh.GodHealth; } // fall through to CHT_GOD [[fallthrough]]; case CHT_GOD: player->cheats ^= CF_GODMODE; if (player->cheats & CF_GODMODE) msg = GStrings("STSTR_DQDON"); else msg = GStrings("STSTR_DQDOFF"); break; case CHT_BUDDHA: player->cheats ^= CF_BUDDHA; if (player->cheats & CF_BUDDHA) msg = GStrings("TXT_BUDDHAON"); else msg = GStrings("TXT_BUDDHAOFF"); break; case CHT_GOD2: player->cheats ^= CF_GODMODE2; if (player->cheats & CF_GODMODE2) msg = GStrings("STSTR_DQD2ON"); else msg = GStrings("STSTR_DQD2OFF"); break; case CHT_BUDDHA2: player->cheats ^= CF_BUDDHA2; if (player->cheats & CF_BUDDHA2) msg = GStrings("TXT_BUDDHA2ON"); else msg = GStrings("TXT_BUDDHA2OFF"); break; case CHT_NOCLIP: player->cheats ^= CF_NOCLIP; if (player->cheats & CF_NOCLIP) msg = GStrings("STSTR_NCON"); else msg = GStrings("STSTR_NCOFF"); break; case CHT_NOCLIP2: player->cheats ^= CF_NOCLIP2; if (player->cheats & CF_NOCLIP2) { player->cheats |= CF_NOCLIP; msg = GStrings("STSTR_NC2ON"); } else { player->cheats &= ~CF_NOCLIP; msg = GStrings("STSTR_NC2OFF"); } if (player->mo->Vel.X == 0) player->mo->Vel.X = MinVel; // force some lateral movement so that internal variables are up to date break; case CHT_NOVELOCITY: player->cheats ^= CF_NOVELOCITY; if (player->cheats & CF_NOVELOCITY) msg = GStrings("TXT_LEADBOOTSON"); else msg = GStrings("TXT_LEADBOOTSOFF"); break; case CHT_FLY: if (player->mo != NULL) { player->mo->flags7 ^= MF7_FLYCHEAT; if (player->mo->flags7 & MF7_FLYCHEAT) { player->mo->flags |= MF_NOGRAVITY; player->mo->flags2 |= MF2_FLY; msg = GStrings("TXT_LIGHTER"); } else { player->mo->flags &= ~MF_NOGRAVITY; player->mo->flags2 &= ~MF2_FLY; msg = GStrings("TXT_GRAVITY"); } } break; case CHT_MORPH: smsg = cht_Morph (player, PClass::FindActor (gameinfo.gametype == GAME_Heretic ? NAME_ChickenPlayer : NAME_PigPlayer), true); msg = smsg.GetChars(); break; case CHT_NOTARGET: player->cheats ^= CF_NOTARGET; if (player->cheats & CF_NOTARGET) msg = GStrings("TXT_NOTARGET_ON"); else msg = GStrings("TXT_NOTARGET_OFF"); break; case CHT_ANUBIS: player->cheats ^= CF_FRIGHTENING; if (player->cheats & CF_FRIGHTENING) msg = GStrings("TXT_ANUBIS_ON"); else msg = GStrings("TXT_ANUBIS_OFF"); break; case CHT_CHASECAM: player->cheats ^= CF_CHASECAM; if (player->cheats & CF_CHASECAM) msg = GStrings("TXT_CHASECAM_ON"); else msg = GStrings("TXT_CHASECAM_OFF"); R_ResetViewInterpolation (); break; case CHT_CHAINSAW: if (player->mo != NULL && player->health >= 0) { type = PClass::FindActor("Chainsaw"); if (player->mo->FindInventory (type) == NULL) { player->mo->GiveInventoryType (type); } msg = GStrings("STSTR_CHOPPERS"); } // [RH] The original cheat also set powers[pw_invulnerability] to true. // Since this is a timer and not a boolean, it effectively turned off // the invulnerability powerup, although it looks like it was meant to // turn it on. break; case CHT_POWER: if (player->mo != NULL && player->health >= 0) { item = player->mo->FindInventory (PClass::FindActor(NAME_PowerWeaponLevel2), true); if (item != NULL) { item->Destroy (); msg = GStrings("TXT_CHEATPOWEROFF"); } else { player->mo->GiveInventoryType (PClass::FindActor(NAME_PowerWeaponLevel2)); msg = GStrings("TXT_CHEATPOWERON"); } } break; case CHT_IDKFA: cht_Give (player, "backpack"); cht_Give (player, "weapons"); cht_Give (player, "ammo"); cht_Give (player, "keys"); cht_Give (player, "armor"); msg = GStrings("STSTR_KFAADDED"); break; case CHT_IDFA: cht_Give (player, "backpack"); cht_Give (player, "weapons"); cht_Give (player, "ammo"); cht_Give (player, "armor"); msg = GStrings("STSTR_FAADDED"); break; case CHT_BEHOLDV: case CHT_BEHOLDS: case CHT_BEHOLDI: case CHT_BEHOLDR: case CHT_BEHOLDA: case CHT_BEHOLDL: case CHT_PUMPUPI: case CHT_PUMPUPM: case CHT_PUMPUPT: i = cheat - CHT_BEHOLDV; if (i == 4) { for (auto Level : AllLevels()) { Level->flags2 ^= LEVEL2_ALLMAP; } } else if (player->mo != NULL && player->health >= 0) { item = player->mo->FindInventory(BeholdPowers[i]); if (item == NULL) { if (i != 0) { cht_Give(player, BeholdPowers[i]); if (cheat == CHT_BEHOLDS) { P_GiveBody (player->mo, -100); } } else { // Let's give the item here so that the power doesn't need colormap information. cht_Give(player, "InvulnerabilitySphere"); } } else { item->Destroy (); } } msg = GStrings("STSTR_BEHOLDX"); break; case CHT_MASSACRE: case CHT_MASSACRE2: { int killcount = primaryLevel->Massacre (cheat == CHT_MASSACRE2); // killough 3/22/98: make more intelligent about plural if (killcount == 1) { msg = GStrings(cheat == CHT_MASSACRE? "TXT_MONSTER_KILLED" : "TXT_BADDIE_KILLED"); } else { // Note: Do not use the language string directly as a format template! smsg = GStrings(cheat == CHT_MASSACRE? "TXT_MONSTERS_KILLED" : "TXT_BADDIES_KILLED"); FStringf countstr("%d", killcount); smsg.Substitute("%d", countstr); msg = smsg.GetChars(); } } break; case CHT_HEALTH: if (player->mo != NULL && player->playerstate == PST_LIVE) { player->health = player->mo->health = player->mo->GetDefault()->health; msg = GStrings("TXT_CHEATHEALTH"); } break; case CHT_KEYS: cht_Give (player, "keys"); msg = GStrings("TXT_CHEATKEYS"); break; // [GRB] case CHT_RESSURECT: if (player->playerstate != PST_LIVE && player->mo != nullptr) { if (player->mo->IsKindOf("PlayerChunk")) { Printf("%s\n", GStrings("TXT_NO_RESURRECT")); return; } else { player->Resurrect(); } } break; case CHT_GIMMIEA: cht_Give (player, "ArtiInvulnerability"); msg = GStrings("TAG_ARTIINVULNERABILITY"); break; case CHT_GIMMIEB: cht_Give (player, "ArtiInvisibility"); msg = GStrings("TAG_ARTIINVISIBILITY"); break; case CHT_GIMMIEC: cht_Give (player, "ArtiHealth"); msg = GStrings("TAG_ARTIHEALTH"); break; case CHT_GIMMIED: cht_Give (player, "ArtiSuperHealth"); msg = GStrings("TAG_ARTISUPERHEALTH"); break; case CHT_GIMMIEE: cht_Give (player, "ArtiTomeOfPower"); msg = GStrings("TAG_ARTITOMEOFPOWER"); break; case CHT_GIMMIEF: cht_Give (player, "ArtiTorch"); msg = GStrings("TAG_ARTITORCH"); break; case CHT_GIMMIEG: cht_Give (player, "ArtiTimeBomb"); msg = GStrings("TAG_ARTIFIREBOMB"); break; case CHT_GIMMIEH: cht_Give (player, "ArtiEgg"); msg = GStrings("TAG_ARTIEGG"); break; case CHT_GIMMIEI: cht_Give (player, "ArtiFly"); msg = GStrings("TAG_ARTIFLY"); break; case CHT_GIMMIEJ: cht_Give (player, "ArtiTeleport"); msg = GStrings("TAG_ARTITELEPORT"); break; case CHT_GIMMIEZ: for (int i=0;i<16;i++) { cht_Give (player, "artifacts"); } msg = GStrings("TAG_ALL_ARTIFACTS"); break; case CHT_TAKEWEAPS: cht_Takeweaps(player); msg = GStrings("TXT_CHEATIDKFA"); break; case CHT_NOWUDIE: cht_Suicide (player); msg = GStrings("TXT_CHEATIDDQD"); break; case CHT_ALLARTI: for (int i=0;i<25;i++) { cht_Give (player, "artifacts"); } msg = GStrings("TXT_CHEATARTIFACTS3"); break; case CHT_PUZZLE: cht_Give (player, "puzzlepieces"); msg = GStrings("TXT_CHEATARTIFACTS3"); break; case CHT_MDK: if (player->mo == nullptr) { Printf ("%s\n", GStrings("TXT_WHAT_KILL")); return; } else if (!deathmatch) { // Don't allow this in deathmatch even with cheats enabled, because it's // a very very cheap kill. P_LineAttack (player->mo, player->mo->Angles.Yaw, PLAYERMISSILERANGE, P_AimLineAttack (player->mo, player->mo->Angles.Yaw, PLAYERMISSILERANGE), TELEFRAG_DAMAGE, NAME_MDK, NAME_BulletPuff); } break; case CHT_DONNYTRUMP: cht_Give (player, "HealthTraining"); msg = GStrings("TXT_MIDASTOUCH"); break; case CHT_LEGO: if (player->mo != NULL && player->health >= 0) { static VMFunction *gsp = nullptr; if (gsp == nullptr) PClass::FindFunction(&gsp, NAME_Sigil, NAME_GiveSigilPiece); if (gsp) { VMValue params[1] = { player->mo }; VMReturn ret; int oldpieces = 1; ret.IntAt(&oldpieces); VMCall(gsp, params, 1, &ret, 1); item = player->mo->FindInventory(NAME_Sigil); if (item != NULL) { if (oldpieces == 5) { item->Destroy(); } else { player->PendingWeapon = item; } } } } break; case CHT_PUMPUPH: cht_Give (player, "MedPatch"); cht_Give (player, "MedicalKit"); cht_Give (player, "SurgeryKit"); msg = GStrings("TXT_GOTSTUFF"); break; case CHT_PUMPUPP: cht_Give (player, "AmmoSatchel"); msg = GStrings("TXT_GOTSTUFF"); break; case CHT_PUMPUPS: cht_Give (player, "UpgradeStamina", 10); cht_Give (player, "UpgradeAccuracy"); msg = GStrings("TXT_GOTSTUFF"); break; case CHT_CLEARFROZENPROPS: player->cheats &= ~(CF_FROZEN|CF_TOTALLYFROZEN); msg = GStrings("TXT_NOT_FROZEN"); break; case CHT_FREEZE: globalchangefreeze ^= 1; if (globalfreeze ^ globalchangefreeze) { msg = GStrings("TXT_FREEZEON"); } else { msg = GStrings("TXT_FREEZEOFF"); } break; } if (!msg || !*msg) // [SO] Don't print blank lines! return; if (player == &players[consoleplayer]) Printf ("%s\n", msg); else if (cheat != CHT_CHASECAM) { FString message = GStrings("TXT_X_CHEATS"); message.Substitute("%s", player->userinfo.GetName()); Printf("%s: %s\n", message.GetChars(), msg); } } FString cht_Morph(player_t *player, PClassActor *morphclass, bool quickundo) { if (player->mo == nullptr) return ""; IFVIRTUALPTRNAME(player->mo, NAME_PlayerPawn, CheatMorph) { FString message; VMReturn msgret(&message); VMValue params[3] = { player->mo, morphclass, quickundo }; VMCall(func, params, 3, &msgret, 1); return message; } return ""; } void cht_SetInv(player_t *player, const char *string, int amount, bool beyond) { if (!player->mo) return; IFVIRTUALPTRNAME(player->mo, NAME_PlayerPawn, CheatTakeInv) { FString message = string; VMValue params[] = { player->mo, &message, amount, beyond }; VMCall(func, params, 4, nullptr, 0); } } void cht_Give (player_t *player, const char *name, int amount) { if (!player->mo) return; IFVIRTUALPTRNAME(player->mo, NAME_PlayerPawn, CheatGive) { FString namestr = name; VMValue params[3] = { player->mo, &namestr, amount }; VMCall(func, params, 3, nullptr, 0); } } void cht_Take (player_t *player, const char *name, int amount) { if (!player->mo) return; IFVIRTUALPTRNAME(player->mo, NAME_PlayerPawn, CheatTake) { FString namestr = name; VMValue params[3] = { player->mo, &namestr, amount }; VMCall(func, params, 3, nullptr, 0); } } void cht_Takeweaps(player_t *player) { if (!player->mo) return; IFVIRTUALPTRNAME(player->mo, NAME_PlayerPawn, CheatTakeWeaps) { VMValue params[3] = { player->mo }; VMCall(func, params, 1, nullptr, 0); } } class DSuicider : public DThinker { DECLARE_CLASS(DSuicider, DThinker) HAS_OBJECT_POINTERS; public: TObjPtr Pawn; void Construct() {} void Tick() { Pawn->flags |= MF_SHOOTABLE; Pawn->flags2 &= ~MF2_INVULNERABLE; // Store the player's current damage factor, to restore it later. double plyrdmgfact = Pawn->DamageFactor; Pawn->DamageFactor = 1.; P_DamageMobj (Pawn, Pawn, Pawn, TELEFRAG_DAMAGE, NAME_Suicide); Pawn->DamageFactor = plyrdmgfact; if (Pawn->health <= 0) { Pawn->flags &= ~MF_SHOOTABLE; } Destroy(); } // You'll probably never be able to catch this in a save game, but // just in case, add a proper serializer. void Serialize(FSerializer &arc) { Super::Serialize(arc); arc("pawn", Pawn); } }; IMPLEMENT_CLASS(DSuicider, false, true) IMPLEMENT_POINTERS_START(DSuicider) IMPLEMENT_POINTER(Pawn) IMPLEMENT_POINTERS_END void cht_Suicide (player_t *plyr) { // If this cheat was initiated by the suicide ccmd, and this is a single // player game, the CHT_SUICIDE will be processed before the tic is run, // so the console has not gone up yet. Use a temporary thinker to delay // the suicide until the game ticks so that death noises can be heard on // the initial tick. if (plyr->mo != NULL) { DSuicider *suicide = plyr->mo->Level->CreateThinker(); suicide->Pawn = plyr->mo; GC::WriteBarrier(suicide, suicide->Pawn); } } DEFINE_ACTION_FUNCTION(APlayerPawn, CheatSuicide) { PARAM_SELF_PROLOGUE(AActor); cht_Suicide(self->player); return 0; } CCMD (mdk) { if (CheckCheatmode ()) return; const char *name = argv.argc() > 1 ? argv[1] : ""; Net_WriteByte (DEM_MDK); Net_WriteString(name); }