Reset Timer Doodad + Various Fixes

* Bird is not solid when colliding with other birds.
* If the dev shell is used to run JavaScript during Play Mode, consider
  it cheating (so player can't `$ d.Scene.ResetTimer()` for example)
* On Survival Mode levels, DieByFire immediately opens the End Level
  (silver score) modal rather than respawn from checkpoint, so levels
  don't need checkpoint contraptions to end the level.
* During level loading screens, wait and call doodads' main() function
  until the very end.
This commit is contained in:
Noah 2022-03-27 11:51:14 -07:00
parent af6b8625d6
commit 38a23f00b2
11 changed files with 126 additions and 17 deletions

View File

@ -2,15 +2,26 @@
## v0.12.0 (TBD) ## v0.12.0 (TBD)
New features: This update adds several new features to gameplay and the Level Editor.
* **Level Difficulty Setting:** in the Level Properties you can choose A **Game Rules** feature has been added to the Level Editor which allows
from Peaceful, Normal or Hard for your level. customizing certain gameplay features while that level is being played.
* On Peaceful, Azulians and Birds don't attack the player, acting like These settings are available in the Level Properties window of the editor:
pre-0.11.0 versions that ignored the player character.
* On Hard difficulty, Azulians have an infinite aggro radius (they'll * The **Difficulty** rule can modify the behavior of enemy doodads
immediately hunt the player from any distance on level start) and when the level is played. Choose between Peaceful, Normal, or Hard.
they are hostile to all player creatures. * On Peaceful, Azulians and Birds don't attack the player, acting
like pre-0.11.0 versions that ignored the player character.
* On Hard difficulty, Azulians have an infinite aggro radius
(they'll immediately hunt the player from any distance on
level start) and they are hostile to _all_ player creatures.
* **Survival Mode** changes the definition of "high score" for levels
where the player is very likely to die at least once.
* The silver high score (respawned at checkpoint) will be for the
_longest_ time survived on the level rather than the fastest time
to complete it.
* The gold high score (got to an Exit Flag without dying once) is
still rewarded to the fastest time completing the level.
An update to the Level Editor's toolbar: An update to the Level Editor's toolbar:
@ -18,11 +29,20 @@ An update to the Level Editor's toolbar:
from the game's built-in fonts. from the game's built-in fonts.
* New **Pan Tool** to be able to scroll the level safely by dragging with * New **Pan Tool** to be able to scroll the level safely by dragging with
your mouse or finger. your mouse or finger.
* New **Flood Tool** (or paint bucket tool) can be used to replace
contiguous areas of your level from one color to another.
* The toolbar buttons are smaller and rearranged. On medium-size screens * The toolbar buttons are smaller and rearranged. On medium-size screens
or larger, the toolbar buttons are drawn side-by-side in two columns. or larger, the toolbar buttons are drawn side-by-side in two columns.
On narrower screens with less real estate, it will use a single column On narrower screens with less real estate, it will use a single column
when it fits better. when it fits better.
New doodads:
* A technical doodad for **Reset Level Timer** resets the timer to zero,
one time, when touched by the player. If the doodad receives a power
signal from a linked doodad, it can reset the level timer again if the
player touches it once more.
Updates to the JavaScript API for custom doodads: Updates to the JavaScript API for custom doodads:
* New global integer `Level.Difficulty` is available to doodad scripts to * New global integer `Level.Difficulty` is available to doodad scripts to
@ -30,10 +50,13 @@ Updates to the JavaScript API for custom doodads:
* Peaceful (-1): `Level.Difficulty < 0` * Peaceful (-1): `Level.Difficulty < 0`
* Normal (0): `Level.Difficulty == 0` * Normal (0): `Level.Difficulty == 0`
* Hard (1): `Level.Difficulty > 1` * Hard (1): `Level.Difficulty > 1`
* New function `Level.ResetTimer()` resets the in-game timer to zero.
New cheat codes: New cheat codes:
* `test load screen` tests the loading screen UI for a few seconds. * `test load screen` tests the loading screen UI for a few seconds.
* `master key` allows playing locked Story Mode levels without unlocking
them first by completing the earlier levels.
Other changes: Other changes:
@ -42,6 +65,17 @@ Other changes:
* Fixed a bug where making the app window bigger during a loading screen * Fixed a bug where making the app window bigger during a loading screen
caused the Editor to not adapt to the larger window. caused the Editor to not adapt to the larger window.
* Don't show the _autosave.doodad in the Doodad Dropper. * Don't show the _autosave.doodad in the Doodad Dropper.
* The Azulians have had their jump heights buffed slightly.
* Birds no longer register as solid when colliding with other birds (or
more generally, characters unaffected by gravity).
Bugs fixed:
* When modifying your Palette to rename a color or add an additional
color, it wasn't possible to draw with that new color without fully
exiting and reloading the editor - this is now resolved.
* The palette editor will try and prevent the user from giving the same
name to different colors.
## v0.11.0 (Feb 21 2022) ## v0.11.0 (Feb 21 2022)

View File

@ -31,7 +31,7 @@ function main() {
return; return;
} }
if (e.Actor.IsMobile() && e.InHitbox) { if (e.Actor.IsMobile() && e.Actor.HasGravity() && e.InHitbox) {
return false; return false;
} }
}); });

View File

@ -28,6 +28,10 @@ build:
doodad edit-doodad --tag "color=invisible" reg-warp-door.doodad doodad edit-doodad --tag "color=invisible" reg-warp-door.doodad
doodad install-script ../warp-door/warp-door.js reg-warp-door.doodad doodad install-script ../warp-door/warp-door.js reg-warp-door.doodad
# Reset Level Timer
doodad convert -t "Reset Level Timer" timer-64.png reg-reset-timer.doodad
doodad install-script reset-timer.js reg-reset-timer.doodad
for i in *.doodad; do\ for i in *.doodad; do\
doodad edit-doodad --tag "category=technical" $${i};\ doodad edit-doodad --tag "category=technical" $${i};\
done done

View File

@ -0,0 +1,30 @@
// Reset Level Timer.
function main() {
Self.Hide();
// Reset the level timer only once.
let hasReset = false;
Events.OnCollide((e) => {
if (!e.Settled) {
return;
}
// Only care if it's the player.
if (!e.Actor.IsPlayer()) {
return;
}
if (e.InHitbox && !hasReset) {
Level.ResetTimer();
hasReset = true;
}
});
// Receive a power signal resets the doodad.
Message.Subscribe("power", (powered) => {
if (powered) {
hasReset = true;
}
});
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 901 B

View File

@ -336,6 +336,13 @@ func (c Command) RunScript(d *Doodle, code string) (goja.Value, error) {
d.FlashError("Command.RunScript: Panic: %s", err) d.FlashError("Command.RunScript: Panic: %s", err)
} }
}() }()
// If we're in Play Mode, consider it cheating if the player is
// messing with any in-game structures.
if scene, ok := d.Scene.(*PlayScene); ok {
scene.SetCheated()
}
out, err := d.shell.js.RunString(code) out, err := d.shell.js.RunString(code)
return out, err return out, err
} }

View File

@ -175,8 +175,8 @@ func makeEndLevel(m *Modal, cfg ConfigEndLevel) *ui.Window {
Font: balance.MenuFont, Font: balance.MenuFont,
})) }))
button.Handle(ui.Click, func(ed ui.EventData) error { button.Handle(ui.Click, func(ed ui.EventData) error {
btn.F()
m.Dismiss(false) m.Dismiss(false)
btn.F()
return nil return nil
}) })
button.Compute(engine) button.Compute(engine)

View File

@ -138,8 +138,8 @@ func (m *Modal) Then(f func()) *Modal {
// Dismiss the modal and optionally call the callback function. // Dismiss the modal and optionally call the callback function.
func (m *Modal) Dismiss(call bool) { func (m *Modal) Dismiss(call bool) {
Reset()
if call && m.callback != nil { if call && m.callback != nil {
m.callback() m.callback()
} }
Reset()
} }

View File

@ -224,6 +224,7 @@ func (s *PlayScene) setupAsync(d *Doodle) error {
// Handle a doodad changing the player character. // Handle a doodad changing the player character.
s.drawing.OnSetPlayerCharacter = s.SetPlayerCharacter s.drawing.OnSetPlayerCharacter = s.SetPlayerCharacter
s.drawing.OnResetTimer = s.ResetTimer
// Given a filename or map data to play? // Given a filename or map data to play?
if s.Level != nil { if s.Level != nil {
@ -265,11 +266,6 @@ func (s *PlayScene) setupAsync(d *Doodle) error {
// Load in the player character. // Load in the player character.
s.setupPlayer(balance.PlayerCharacterDoodad) s.setupPlayer(balance.PlayerCharacterDoodad)
// Run all the actor scripts' main() functions.
if err := s.drawing.InstallScripts(); err != nil {
log.Error("PlayScene.Setup: failed to drawing.InstallScripts: %s", err)
}
if s.CanEdit { if s.CanEdit {
d.Flash("Entered Play Mode. Press 'E' to edit this map.") d.Flash("Entered Play Mode. Press 'E' to edit this map.")
} else { } else {
@ -286,6 +282,11 @@ func (s *PlayScene) setupAsync(d *Doodle) error {
// Gamepad: put into GameplayMode. // Gamepad: put into GameplayMode.
gamepad.SetMode(gamepad.GameplayMode) gamepad.SetMode(gamepad.GameplayMode)
// Run all the actor scripts' main() functions.
if err := s.drawing.InstallScripts(); err != nil {
log.Error("PlayScene.Setup: failed to drawing.InstallScripts: %s", err)
}
s.startTime = time.Now() s.startTime = time.Now()
s.perfectRun = true s.perfectRun = true
s.running = true s.running = true
@ -321,6 +322,11 @@ func (s *PlayScene) SetPlayerCharacter(filename string) {
} }
} }
// ResetTimer sets the level elapsed timer back to zero.
func (s *PlayScene) ResetTimer() {
s.startTime = time.Now()
}
// setupPlayer creates and configures the Player Character in the level. // setupPlayer creates and configures the Player Character in the level.
func (s *PlayScene) setupPlayer(playerCharacterFilename string) { func (s *PlayScene) setupPlayer(playerCharacterFilename string) {
// Find the spawn point of the player. Search the level for the // Find the spawn point of the player. Search the level for the
@ -492,7 +498,12 @@ func (s *PlayScene) BeatLevel() {
) )
} }
// FailLevel handles a level failure triggered by a doodad. /*
FailLevel handles a level failure triggered by a doodad or fire pixel.
If the Survival GameRule is set, this ends the level with a note on how long the
player had survived for and they get a silver rating.
*/
func (s *PlayScene) FailLevel(message string) { func (s *PlayScene) FailLevel(message string) {
if s.Player.Invulnerable() || s.godMode || s.godModeUntil.After(time.Now()) { if s.Player.Invulnerable() || s.godMode || s.godModeUntil.After(time.Now()) {
return return
@ -500,6 +511,19 @@ func (s *PlayScene) FailLevel(message string) {
s.SetImperfect() s.SetImperfect()
s.d.FlashError(message) s.d.FlashError(message)
if s.Level.GameRule.Survival {
s.ShowEndLevelModal(
true,
"Level Completed",
fmt.Sprintf(
"%s\nCongrats on surviving for %s!",
message,
savegame.FormatDuration(time.Since(s.startTime)),
),
)
return
}
s.ShowEndLevelModal( s.ShowEndLevelModal(
false, false,
"You've died!", "You've died!",

View File

@ -95,6 +95,9 @@ type Canvas struct {
// The filename.doodad is given. // The filename.doodad is given.
OnSetPlayerCharacter func(filename string) OnSetPlayerCharacter func(filename string)
// Handler for when a doodad script calls Level.ResetTimer().
OnResetTimer func()
/******** /********
* Editable canvas private variables. * Editable canvas private variables.
********/ ********/

View File

@ -67,6 +67,13 @@ func (w *Canvas) MakeScriptAPI(vm *scripting.VM) {
vm.Set("Level", map[string]interface{}{ vm.Set("Level", map[string]interface{}{
"Difficulty": w.level.GameRule.Difficulty, "Difficulty": w.level.GameRule.Difficulty,
"ResetTimer": func() {
if w.OnResetTimer != nil {
w.OnResetTimer()
} else {
log.Error("Level.ResetTimer: caller was not ready")
}
},
}) })
} }