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
|
||||
|
||||
## v0.11.0 (TBD)
|
||||
## v0.10.1 (TBD)
|
||||
|
||||
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
|
||||
|
||||
|
||||
# `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
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 (
|
||||
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."
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
13
pkg/fps.go
13
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))
|
||||
}
|
||||
|
|
|
@ -150,7 +150,8 @@ func (s *MainScene) Setup(d *Doodle) error {
|
|||
}
|
||||
},
|
||||
OnCloseWindow: func() {
|
||||
s.winLevelPacks.Hide()
|
||||
s.winLevelPacks.Destroy()
|
||||
s.winLevelPacks = nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue
Block a user