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
This commit is contained in:
Noah 2022-01-08 18:27:37 -08:00
parent 24c47d1e3f
commit 48e18da511
10 changed files with 79 additions and 29 deletions

View File

@ -1,6 +1,6 @@
# Changes # Changes
## v0.11.0 (TBD) ## v0.10.1 (TBD)
New features: New features:

View File

@ -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 # 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 .PHONY: run
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 go run cmd/doodle/main.go -debug
# `make guitest` to run it in guitest mode. # `make guitest` to run it in guitest mode.

32
pkg/balance/cheats.go Normal file
View File

@ -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"
)

View File

@ -4,7 +4,7 @@ package branding
const ( const (
AppName = "Sketchy Maze" AppName = "Sketchy Maze"
Summary = "A drawing-based maze game" Summary = "A drawing-based maze game"
Version = "0.10.0" Version = "0.10.1"
Website = "https://www.sketchymaze.com" Website = "https://www.sketchymaze.com"
Copyright = "2021 Noah Petherbridge" Copyright = "2021 Noah Petherbridge"
Byline = "a game by Noah Petherbridge." Byline = "a game by Noah Petherbridge."

View File

@ -4,6 +4,9 @@ import (
"git.kirsle.net/apps/doodle/pkg/balance" "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 // cheatCommand is a subroutine of the Command.Run() method of the Doodle
// developer shell (commands.go). It looks for special cheat codes entered // developer shell (commands.go). It looks for special cheat codes entered
// into the command shell and executes them. // into the command shell and executes them.
@ -15,7 +18,7 @@ func (c Command) cheatCommand(d *Doodle) bool {
// Cheat codes // Cheat codes
switch c.Raw { switch c.Raw {
case "unleash the beast": case balance.CheatUncapFPS:
if fpsDoNotCap { if fpsDoNotCap {
d.Flash("Reset frame rate throttle to factory default FPS") d.Flash("Reset frame rate throttle to factory default FPS")
} else { } else {
@ -23,7 +26,7 @@ func (c Command) cheatCommand(d *Doodle) bool {
} }
fpsDoNotCap = !fpsDoNotCap fpsDoNotCap = !fpsDoNotCap
case "don't edit and drive": case balance.CheatEditDuringPlay:
if isPlay { if isPlay {
playScene.drawing.Editable = true playScene.drawing.Editable = true
playScene.SetCheated() 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.") 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 { if isPlay {
playScene.drawing.Scrollable = true playScene.drawing.Scrollable = true
playScene.SetCheated() 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.") d.FlashError("Use this cheat in Play Mode to make the level scrollable.")
} }
case "import antigravity": case balance.CheatAntigravity:
if isPlay { if isPlay {
playScene.SetCheated() 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.") d.FlashError("Use this cheat in Play Mode to disable gravity for the player character.")
} }
case "ghost mode": case balance.CheatNoclip:
if isPlay { if isPlay {
playScene.SetCheated() 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.") d.FlashError("Use this cheat in Play Mode to disable clipping for the player character.")
} }
case "show all actors": case balance.CheatShowAllActors:
if isPlay { if isPlay {
playScene.SetCheated() playScene.SetCheated()
for _, actor := range playScene.drawing.Actors() { 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.") 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 { if isPlay {
playScene.SetCheated() playScene.SetCheated()
playScene.Player.AddItem("key-red.doodad", 0) 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.") d.FlashError("Use this cheat in Play Mode to get all colored keys.")
} }
case "drop all items": case balance.CheatDropItems:
if isPlay { if isPlay {
playScene.SetCheated() playScene.SetCheated()
playScene.Player.ClearInventory() 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.") 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" balance.PlayerCharacterDoodad = "bird-red.doodad"
d.Flash("Set default player character to Bird (red)") d.Flash("Set default player character to Bird (red)")
case "pinocchio": case balance.CheatPlayAsBoy:
balance.PlayerCharacterDoodad = "boy.doodad" balance.PlayerCharacterDoodad = "boy.doodad"
d.Flash("Set default player character to Boy") d.Flash("Set default player character to Boy")
case "the cell": case balance.CheatPlayAsAzuBlue:
balance.PlayerCharacterDoodad = "azu-blu.doodad" balance.PlayerCharacterDoodad = "azu-blu.doodad"
d.Flash("Set default player character to Blue Azulian") d.Flash("Set default player character to Blue Azulian")
case "play as thief": case balance.CheatPlayAsThief:
balance.PlayerCharacterDoodad = "thief.doodad" balance.PlayerCharacterDoodad = "thief.doodad"
d.Flash("Set default player character to Thief") d.Flash("Set default player character to Thief")

View File

@ -154,7 +154,7 @@ func (d *Doodle) DrawCollisionBox(canvas *uix.Canvas, actor *uix.Actor) {
// The stroke data for drawing the collision box "inside" the level Canvas, // The stroke data for drawing the collision box "inside" the level Canvas,
// so it scrolls and works in world units not screen units. // so it scrolls and works in world units not screen units.
var strokes = []struct{ var strokes = []struct {
Color render.Color Color render.Color
PointA render.Point PointA render.Point
PointB render.Point PointB render.Point
@ -191,7 +191,6 @@ func (d *Doodle) TrackFPS(skipped uint32) {
fpsSkipped = skipped fpsSkipped = skipped
} }
if d.Debug { // FPS in the title bar.
d.Engine.SetTitle(fmt.Sprintf("%s (%d FPS)", d.Title(), fpsCurrent)) d.Engine.SetTitle(fmt.Sprintf("%s (%d FPS)", d.Title(), fpsCurrent))
}
} }

View File

@ -150,7 +150,8 @@ func (s *MainScene) Setup(d *Doodle) error {
} }
}, },
OnCloseWindow: func() { OnCloseWindow: func() {
s.winLevelPacks.Hide() s.winLevelPacks.Destroy()
s.winLevelPacks = nil
}, },
}) })
} }

View File

@ -290,6 +290,7 @@ func (s *PlayScene) setupPlayer() {
// "start-flag.doodad" // "start-flag.doodad"
var ( var (
playerCharacterFilename = balance.PlayerCharacterDoodad playerCharacterFilename = balance.PlayerCharacterDoodad
isStartFlagCharacter bool
spawn render.Point spawn render.Point
flag = &level.Actor{} flag = &level.Actor{}
@ -303,6 +304,7 @@ func (s *PlayScene) setupPlayer() {
for _, linkID := range actor.Links { for _, linkID := range actor.Links {
if linkedActor, ok := s.Level.Actors[linkID]; ok { if linkedActor, ok := s.Level.Actors[linkID]; ok {
playerCharacterFilename = linkedActor.Filename playerCharacterFilename = linkedActor.Filename
isStartFlagCharacter = true
log.Info("Playing as: %s", playerCharacterFilename) log.Info("Playing as: %s", playerCharacterFilename)
break 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. // The Start Flag becomes the player's initial checkpoint.
s.lastCheckpoint = flag.Point s.lastCheckpoint = flag.Point

View File

@ -196,12 +196,12 @@ func (sg *SaveGame) CountCompleted(levelpack string) int {
// FormatDuration pretty prints a time.Duration in MM:SS format. // FormatDuration pretty prints a time.Duration in MM:SS format.
func FormatDuration(d time.Duration) string { func FormatDuration(d time.Duration) string {
d = d.Round(time.Millisecond)
var ( var (
hour = d / time.Hour millisecond = d.Milliseconds()
minute = d / time.Minute second = (millisecond / 1000) % 60
second = d / time.Second minute = (millisecond / (1000 * 60)) % 60
ms = fmt.Sprintf("%d", d/time.Millisecond%1000) hour = (millisecond / (1000 * 60 * 60)) % 24
ms = fmt.Sprintf("%d", millisecond%1000)
) )
// Limit milliseconds to 2 digits. // Limit milliseconds to 2 digits.

View File

@ -99,7 +99,6 @@ func NewLevelPackWindow(config LevelPack) *ui.Window {
config.makeIndexScreen(indexTab, width, height, lpFiles, packmap, func(screen string) { config.makeIndexScreen(indexTab, width, height, lpFiles, packmap, func(screen string) {
// Callback for user choosing a level pack. // Callback for user choosing a level pack.
// Hide the index screen and show the screen for this pack. // Hide the index screen and show the screen for this pack.
log.Info("Called for tab: %s", screen)
tabFrame.SetTab(screen) tabFrame.SetTab(screen)
}) })
for _, filename := range lpFiles { for _, filename := range lpFiles {