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:
parent
24c47d1e3f
commit
48e18da511
|
@ -1,6 +1,6 @@
|
||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## v0.11.0 (TBD)
|
## v0.10.1 (TBD)
|
||||||
|
|
||||||
New features:
|
New features:
|
||||||
|
|
||||||
|
|
7
Makefile
7
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
|
# 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
32
pkg/balance/cheats.go
Normal 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"
|
||||||
|
)
|
|
@ -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."
|
||||||
|
|
|
@ -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")
|
||||||
|
|
||||||
|
|
|
@ -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))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -150,7 +150,8 @@ func (s *MainScene) Setup(d *Doodle) error {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
OnCloseWindow: func() {
|
OnCloseWindow: func() {
|
||||||
s.winLevelPacks.Hide()
|
s.winLevelPacks.Destroy()
|
||||||
|
s.winLevelPacks = nil
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user