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
## v0.11.0 (TBD)
## v0.10.1 (TBD)
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
# `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.

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 (
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."

View File

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

View File

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

View File

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

View File

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

View File

@ -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.

View File

@ -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 {