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:
parent
af6b8625d6
commit
38a23f00b2
50
Changes.md
50
Changes.md
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -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
|
||||||
|
|
30
dev-assets/doodads/regions/reset-timer.js
Normal file
30
dev-assets/doodads/regions/reset-timer.js
Normal 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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
BIN
dev-assets/doodads/regions/timer-64.png
Normal file
BIN
dev-assets/doodads/regions/timer-64.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 901 B |
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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()
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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!",
|
||||||
|
|
|
@ -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.
|
||||||
********/
|
********/
|
||||||
|
|
|
@ -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")
|
||||||
|
}
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user