From 48e18da511ab0fb64628249a2976b453c8484159 Mon Sep 17 00:00:00 2001 From: Noah Petherbridge Date: Sat, 8 Jan 2022 18:27:37 -0800 Subject: [PATCH] Centralize cheats, detect cheated player character * If the player runs the PlayAsBird cheat they shouldn't be able to win a high score on a level, so at level startup it detects whether the DefaultPlayerCharacterDoodad has changed from default on a level that doesn't use the Start Flag to set a specific doodad - and immediately marks the session as cheated --- Changes.md | 2 +- Makefile | 7 ++++++- pkg/balance/cheats.go | 32 ++++++++++++++++++++++++++++++++ pkg/branding/branding.go | 2 +- pkg/cheats.go | 27 +++++++++++++++------------ pkg/fps.go | 13 ++++++------- pkg/main_scene.go | 3 ++- pkg/play_scene.go | 11 +++++++++++ pkg/savegame/savegame.go | 10 +++++----- pkg/windows/levelpack_open.go | 1 - 10 files changed, 79 insertions(+), 29 deletions(-) create mode 100644 pkg/balance/cheats.go diff --git a/Changes.md b/Changes.md index 5e11578..50a2106 100644 --- a/Changes.md +++ b/Changes.md @@ -1,6 +1,6 @@ # Changes -## v0.11.0 (TBD) +## v0.10.1 (TBD) New features: diff --git a/Makefile b/Makefile index 50abe82..b48cd5c 100644 --- a/Makefile +++ b/Makefile @@ -126,9 +126,14 @@ mingw32-release: doodads build mingw32 __dist-common release32 # CGO_ENABLED=1 CC=[path-to-osxcross]/target/bin/[arch]-apple-darwin[version]-clang GOOS=darwin GOARCH=[arch] go build -tags static -ldflags "-s -w" -a -# `make run` to run it in debug mode. +# `make run` to run it from source. .PHONY: run run: + go run cmd/doodle/main.go + +# `make debug` to run it in -debug mode. +.PHONY: debug +debug: go run cmd/doodle/main.go -debug # `make guitest` to run it in guitest mode. diff --git a/pkg/balance/cheats.go b/pkg/balance/cheats.go new file mode 100644 index 0000000..8c0016a --- /dev/null +++ b/pkg/balance/cheats.go @@ -0,0 +1,32 @@ +package balance + +// Store a copy of the PlayerCharacterDoodad original value. +var playerCharacterDefault string + +func init() { + playerCharacterDefault = PlayerCharacterDoodad +} + +// IsPlayerCharacterDefault returns whether the balance.PlayerCharacterDoodad +// has been modified at runtime away from its built-in default. This is a cheat +// detection method: high scores could be tainted if you `fly like a bird` right +// to the exit in a couple of seconds. +func IsPlayerCharacterDefault() bool { + return PlayerCharacterDoodad == playerCharacterDefault +} + +// The game's cheat codes +var ( + CheatUncapFPS = "unleash the beast" + CheatEditDuringPlay = "don't edit and drive" + CheatScrollDuringPlay = "scroll scroll scroll your boat" + CheatAntigravity = "import antigravity" + CheatNoclip = "ghost mode" + CheatShowAllActors = "show all actors" + CheatGiveKeys = "give all keys" + CheatDropItems = "drop all items" + CheatPlayAsBird = "fly like a bird" + CheatPlayAsBoy = "pinocchio" + CheatPlayAsAzuBlue = "the cell" + CheatPlayAsThief = "play as thief" +) diff --git a/pkg/branding/branding.go b/pkg/branding/branding.go index 32251a6..4cfab31 100644 --- a/pkg/branding/branding.go +++ b/pkg/branding/branding.go @@ -4,7 +4,7 @@ package branding const ( AppName = "Sketchy Maze" Summary = "A drawing-based maze game" - Version = "0.10.0" + Version = "0.10.1" Website = "https://www.sketchymaze.com" Copyright = "2021 Noah Petherbridge" Byline = "a game by Noah Petherbridge." diff --git a/pkg/cheats.go b/pkg/cheats.go index 7019ff9..a1ef05e 100644 --- a/pkg/cheats.go +++ b/pkg/cheats.go @@ -4,6 +4,9 @@ import ( "git.kirsle.net/apps/doodle/pkg/balance" ) +// IsDefaultPlayerCharacter checks whether the DefaultPlayerCharacter doodad has +// been modified + // cheatCommand is a subroutine of the Command.Run() method of the Doodle // developer shell (commands.go). It looks for special cheat codes entered // into the command shell and executes them. @@ -15,7 +18,7 @@ func (c Command) cheatCommand(d *Doodle) bool { // Cheat codes switch c.Raw { - case "unleash the beast": + case balance.CheatUncapFPS: if fpsDoNotCap { d.Flash("Reset frame rate throttle to factory default FPS") } else { @@ -23,7 +26,7 @@ func (c Command) cheatCommand(d *Doodle) bool { } fpsDoNotCap = !fpsDoNotCap - case "don't edit and drive": + case balance.CheatEditDuringPlay: if isPlay { playScene.drawing.Editable = true playScene.SetCheated() @@ -32,7 +35,7 @@ func (c Command) cheatCommand(d *Doodle) bool { d.FlashError("Use this cheat in Play Mode to make the level canvas editable.") } - case "scroll scroll scroll your boat": + case balance.CheatScrollDuringPlay: if isPlay { playScene.drawing.Scrollable = true playScene.SetCheated() @@ -41,7 +44,7 @@ func (c Command) cheatCommand(d *Doodle) bool { d.FlashError("Use this cheat in Play Mode to make the level scrollable.") } - case "import antigravity": + case balance.CheatAntigravity: if isPlay { playScene.SetCheated() @@ -57,7 +60,7 @@ func (c Command) cheatCommand(d *Doodle) bool { d.FlashError("Use this cheat in Play Mode to disable gravity for the player character.") } - case "ghost mode": + case balance.CheatNoclip: if isPlay { playScene.SetCheated() @@ -76,7 +79,7 @@ func (c Command) cheatCommand(d *Doodle) bool { d.FlashError("Use this cheat in Play Mode to disable clipping for the player character.") } - case "show all actors": + case balance.CheatShowAllActors: if isPlay { playScene.SetCheated() for _, actor := range playScene.drawing.Actors() { @@ -87,7 +90,7 @@ func (c Command) cheatCommand(d *Doodle) bool { d.FlashError("Use this cheat in Play Mode to show hidden actors, such as technical doodads.") } - case "give all keys": + case balance.CheatGiveKeys: if isPlay { playScene.SetCheated() playScene.Player.AddItem("key-red.doodad", 0) @@ -100,7 +103,7 @@ func (c Command) cheatCommand(d *Doodle) bool { d.FlashError("Use this cheat in Play Mode to get all colored keys.") } - case "drop all items": + case balance.CheatDropItems: if isPlay { playScene.SetCheated() playScene.Player.ClearInventory() @@ -109,19 +112,19 @@ func (c Command) cheatCommand(d *Doodle) bool { d.FlashError("Use this cheat in Play Mode to clear your inventory.") } - case "fly like a bird": + case balance.CheatPlayAsBird: balance.PlayerCharacterDoodad = "bird-red.doodad" d.Flash("Set default player character to Bird (red)") - case "pinocchio": + case balance.CheatPlayAsBoy: balance.PlayerCharacterDoodad = "boy.doodad" d.Flash("Set default player character to Boy") - case "the cell": + case balance.CheatPlayAsAzuBlue: balance.PlayerCharacterDoodad = "azu-blu.doodad" d.Flash("Set default player character to Blue Azulian") - case "play as thief": + case balance.CheatPlayAsThief: balance.PlayerCharacterDoodad = "thief.doodad" d.Flash("Set default player character to Thief") diff --git a/pkg/fps.go b/pkg/fps.go index 69afd37..a62ce22 100644 --- a/pkg/fps.go +++ b/pkg/fps.go @@ -142,8 +142,8 @@ func (d *Doodle) DrawCollisionBox(canvas *uix.Canvas, actor *uix.Actor) { } var ( - rect = collision.GetBoundingRect(actor) - box = collision.GetCollisionBox(rect) + rect = collision.GetBoundingRect(actor) + box = collision.GetCollisionBox(rect) hitbox = actor.Hitbox() ) @@ -154,8 +154,8 @@ func (d *Doodle) DrawCollisionBox(canvas *uix.Canvas, actor *uix.Actor) { // The stroke data for drawing the collision box "inside" the level Canvas, // so it scrolls and works in world units not screen units. - var strokes = []struct{ - Color render.Color + var strokes = []struct { + Color render.Color PointA render.Point PointB render.Point }{ @@ -191,7 +191,6 @@ func (d *Doodle) TrackFPS(skipped uint32) { fpsSkipped = skipped } - if d.Debug { - d.Engine.SetTitle(fmt.Sprintf("%s (%d FPS)", d.Title(), fpsCurrent)) - } + // FPS in the title bar. + d.Engine.SetTitle(fmt.Sprintf("%s (%d FPS)", d.Title(), fpsCurrent)) } diff --git a/pkg/main_scene.go b/pkg/main_scene.go index 8eab47a..d7facfd 100644 --- a/pkg/main_scene.go +++ b/pkg/main_scene.go @@ -150,7 +150,8 @@ func (s *MainScene) Setup(d *Doodle) error { } }, OnCloseWindow: func() { - s.winLevelPacks.Hide() + s.winLevelPacks.Destroy() + s.winLevelPacks = nil }, }) } diff --git a/pkg/play_scene.go b/pkg/play_scene.go index 939a793..15e0105 100644 --- a/pkg/play_scene.go +++ b/pkg/play_scene.go @@ -290,6 +290,7 @@ func (s *PlayScene) setupPlayer() { // "start-flag.doodad" var ( playerCharacterFilename = balance.PlayerCharacterDoodad + isStartFlagCharacter bool spawn render.Point flag = &level.Actor{} @@ -303,6 +304,7 @@ func (s *PlayScene) setupPlayer() { for _, linkID := range actor.Links { if linkedActor, ok := s.Level.Actors[linkID]; ok { playerCharacterFilename = linkedActor.Filename + isStartFlagCharacter = true log.Info("Playing as: %s", playerCharacterFilename) break } @@ -317,6 +319,15 @@ func (s *PlayScene) setupPlayer() { } } + // If the user is cheating for the player character, mark the + // session cheated already. e.g. "Play as Bird" cheat would let + // them just fly to the goal in levels that don't link their + // Start Flag to a specific character. + if !isStartFlagCharacter && !balance.IsPlayerCharacterDefault() { + log.Warn("Mark session as cheated: the player spawned as %s instead of default", playerCharacterFilename) + s.SetCheated() + } + // The Start Flag becomes the player's initial checkpoint. s.lastCheckpoint = flag.Point diff --git a/pkg/savegame/savegame.go b/pkg/savegame/savegame.go index 71591d9..e46903d 100644 --- a/pkg/savegame/savegame.go +++ b/pkg/savegame/savegame.go @@ -196,12 +196,12 @@ func (sg *SaveGame) CountCompleted(levelpack string) int { // FormatDuration pretty prints a time.Duration in MM:SS format. func FormatDuration(d time.Duration) string { - d = d.Round(time.Millisecond) var ( - hour = d / time.Hour - minute = d / time.Minute - second = d / time.Second - ms = fmt.Sprintf("%d", d/time.Millisecond%1000) + millisecond = d.Milliseconds() + second = (millisecond / 1000) % 60 + minute = (millisecond / (1000 * 60)) % 60 + hour = (millisecond / (1000 * 60 * 60)) % 24 + ms = fmt.Sprintf("%d", millisecond%1000) ) // Limit milliseconds to 2 digits. diff --git a/pkg/windows/levelpack_open.go b/pkg/windows/levelpack_open.go index b63295a..9f82a52 100644 --- a/pkg/windows/levelpack_open.go +++ b/pkg/windows/levelpack_open.go @@ -99,7 +99,6 @@ func NewLevelPackWindow(config LevelPack) *ui.Window { config.makeIndexScreen(indexTab, width, height, lpFiles, packmap, func(screen string) { // Callback for user choosing a level pack. // Hide the index screen and show the screen for this pack. - log.Info("Called for tab: %s", screen) tabFrame.SetTab(screen) }) for _, filename := range lpFiles {