2018-06-21 02:00:46 +00:00
|
|
|
package doodle
|
|
|
|
|
|
|
|
import (
|
2018-09-25 16:40:34 +00:00
|
|
|
"fmt"
|
|
|
|
|
2019-04-10 00:35:44 +00:00
|
|
|
"git.kirsle.net/apps/doodle/pkg/balance"
|
Add Switches, Fire/Water Collision and Play Menu
* New doodads: Switches.
* They come in four varieties: wall switch (background element, with
"ON/OFF" text) and three side-profile switches for the floor, left
or right walls.
* On collision with the player, they flip their state from "OFF" to
"ON" or vice versa. If the player walks away and then collides
again, the switch flips again.
* Can be used to open/close Electric Doors when turned on/off. Their
default state is "off"
* If a switch receives a power signal from another linked switch, it
sets its own state to match. So, two "on/off" switches that are
connected to a door AND to each other will both flip on/off when one
of them flips.
* Update the Level Collision logic to support Decoration, Fire and Water
pixel collisions.
* Previously, ALL pixels in the level were acting as though solid.
* Non-solid pixels don't count for collision detection, but their
attributes (fire and water) are collected and returned.
* Updated the MenuScene to support loading a map file in Play Mode
instead of Edit Mode. Updated the title screen menu to add a button
for playing levels instead of editing them.
* Wrote some documentation.
2019-07-07 01:30:03 +00:00
|
|
|
"git.kirsle.net/apps/doodle/pkg/collision"
|
2019-05-02 01:30:30 +00:00
|
|
|
"git.kirsle.net/apps/doodle/pkg/doodads"
|
2020-11-18 02:22:48 +00:00
|
|
|
"git.kirsle.net/apps/doodle/pkg/keybind"
|
2019-04-10 00:35:44 +00:00
|
|
|
"git.kirsle.net/apps/doodle/pkg/level"
|
|
|
|
"git.kirsle.net/apps/doodle/pkg/log"
|
2021-07-19 03:04:24 +00:00
|
|
|
"git.kirsle.net/apps/doodle/pkg/modal/loadscreen"
|
2020-04-05 04:00:32 +00:00
|
|
|
"git.kirsle.net/apps/doodle/pkg/physics"
|
2019-04-16 06:07:15 +00:00
|
|
|
"git.kirsle.net/apps/doodle/pkg/scripting"
|
2019-04-10 00:35:44 +00:00
|
|
|
"git.kirsle.net/apps/doodle/pkg/uix"
|
2019-12-28 03:16:34 +00:00
|
|
|
"git.kirsle.net/go/render"
|
|
|
|
"git.kirsle.net/go/render/event"
|
|
|
|
"git.kirsle.net/go/ui"
|
2018-06-21 02:00:46 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// PlayScene manages the "Edit Level" game mode.
|
|
|
|
type PlayScene struct {
|
2018-07-24 03:10:53 +00:00
|
|
|
// Configuration attributes.
|
|
|
|
Filename string
|
2018-09-25 16:40:34 +00:00
|
|
|
Level *level.Level
|
2019-07-02 22:24:46 +00:00
|
|
|
CanEdit bool // i.e. you came from the Editor Mode
|
|
|
|
HasNext bool // has a next level to load next
|
2018-07-24 03:10:53 +00:00
|
|
|
|
|
|
|
// Private variables.
|
2019-04-16 06:07:15 +00:00
|
|
|
d *Doodle
|
|
|
|
drawing *uix.Canvas
|
|
|
|
scripting *scripting.Supervisor
|
2019-07-02 22:24:46 +00:00
|
|
|
running bool
|
2018-06-21 02:00:46 +00:00
|
|
|
|
2019-06-26 01:36:53 +00:00
|
|
|
// UI widgets.
|
|
|
|
supervisor *ui.Supervisor
|
2020-04-03 06:09:46 +00:00
|
|
|
screen *ui.Frame // A window sized invisible frame to position UI elements.
|
2019-06-26 01:36:53 +00:00
|
|
|
editButton *ui.Button
|
|
|
|
|
2019-07-02 22:24:46 +00:00
|
|
|
// The alert box shows up when the level goal is reached and includes
|
|
|
|
// buttons what to do next.
|
|
|
|
alertBox *ui.Window
|
2019-07-07 03:31:50 +00:00
|
|
|
alertBoxLabel *ui.Label
|
2019-07-02 22:24:46 +00:00
|
|
|
alertReplayButton *ui.Button // Replay level
|
|
|
|
alertEditButton *ui.Button // Edit Level
|
|
|
|
alertNextButton *ui.Button // Next Level
|
|
|
|
alertExitButton *ui.Button // Exit to menu
|
|
|
|
|
2019-04-10 01:28:08 +00:00
|
|
|
// Custom debug labels.
|
|
|
|
debPosition *string
|
|
|
|
debViewport *string
|
|
|
|
debScroll *string
|
|
|
|
debWorldIndex *string
|
|
|
|
|
2018-07-24 03:10:53 +00:00
|
|
|
// Player character
|
2019-07-05 22:02:22 +00:00
|
|
|
Player *uix.Actor
|
2020-04-05 04:00:32 +00:00
|
|
|
playerPhysics *physics.Mover
|
2019-12-31 02:13:28 +00:00
|
|
|
antigravity bool // Cheat: disable player gravity
|
2020-01-03 04:23:27 +00:00
|
|
|
noclip bool // Cheat: disable player clipping
|
2019-12-31 02:13:28 +00:00
|
|
|
playerJumpCounter int // limit jump length
|
2020-04-03 06:09:46 +00:00
|
|
|
|
|
|
|
// Inventory HUD. Impl. in play_inventory.go
|
|
|
|
invenFrame *ui.Frame
|
|
|
|
invenItems []string // item list
|
|
|
|
invenDoodads map[string]*uix.Canvas
|
2018-06-21 02:00:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Name of the scene.
|
|
|
|
func (s *PlayScene) Name() string {
|
|
|
|
return "Play"
|
|
|
|
}
|
|
|
|
|
|
|
|
// Setup the play scene.
|
|
|
|
func (s *PlayScene) Setup(d *Doodle) error {
|
2018-10-28 05:22:13 +00:00
|
|
|
s.d = d
|
2019-04-16 06:07:15 +00:00
|
|
|
s.scripting = scripting.NewSupervisor()
|
2019-06-26 01:36:53 +00:00
|
|
|
s.supervisor = ui.NewSupervisor()
|
2019-04-10 01:28:08 +00:00
|
|
|
|
2021-07-19 03:04:24 +00:00
|
|
|
// Show the loading screen.
|
|
|
|
loadscreen.ShowWithProgress()
|
|
|
|
go func() {
|
|
|
|
if err := s.setupAsync(d); err != nil {
|
|
|
|
log.Error("PlayScene.setupAsync: %s", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
loadscreen.Hide()
|
|
|
|
}()
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// setupAsync initializes the play screen in the background, underneath
|
|
|
|
// a Loading screen.
|
|
|
|
func (s *PlayScene) setupAsync(d *Doodle) error {
|
2020-04-03 06:09:46 +00:00
|
|
|
// Create an invisible 'screen' frame for UI elements to use for positioning.
|
|
|
|
s.screen = ui.NewFrame("Screen")
|
|
|
|
s.screen.Resize(render.NewRect(d.width, d.height))
|
|
|
|
|
2019-07-02 22:24:46 +00:00
|
|
|
// Level Exit handler.
|
|
|
|
s.SetupAlertbox()
|
|
|
|
s.scripting.OnLevelExit(func() {
|
|
|
|
d.Flash("Hurray!")
|
|
|
|
|
|
|
|
// Pause the simulation.
|
|
|
|
s.running = false
|
|
|
|
|
|
|
|
// Toggle the relevant buttons on.
|
|
|
|
if s.CanEdit {
|
|
|
|
s.alertEditButton.Show()
|
|
|
|
}
|
|
|
|
if s.HasNext {
|
|
|
|
s.alertNextButton.Show()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Always-visible buttons.
|
|
|
|
s.alertReplayButton.Show()
|
|
|
|
s.alertExitButton.Show()
|
|
|
|
|
|
|
|
// Show the alert box.
|
|
|
|
s.alertBox.Show()
|
|
|
|
})
|
|
|
|
|
2019-04-10 01:28:08 +00:00
|
|
|
// Initialize debug overlay values.
|
|
|
|
s.debPosition = new(string)
|
|
|
|
s.debViewport = new(string)
|
|
|
|
s.debScroll = new(string)
|
|
|
|
s.debWorldIndex = new(string)
|
|
|
|
customDebugLabels = []debugLabel{
|
|
|
|
{"Pixel:", s.debWorldIndex},
|
|
|
|
{"Player:", s.debPosition},
|
|
|
|
{"Viewport:", s.debViewport},
|
|
|
|
{"Scroll:", s.debScroll},
|
|
|
|
}
|
|
|
|
|
2019-06-26 01:36:53 +00:00
|
|
|
// Initialize the "Edit Map" button.
|
|
|
|
s.editButton = ui.NewButton("Edit", ui.NewLabel(ui.Label{
|
|
|
|
Text: "Edit (E)",
|
|
|
|
Font: balance.PlayButtonFont,
|
|
|
|
}))
|
2020-04-07 06:21:17 +00:00
|
|
|
s.editButton.Handle(ui.Click, func(ed ui.EventData) error {
|
2019-06-26 01:36:53 +00:00
|
|
|
s.EditLevel()
|
2020-04-07 06:21:17 +00:00
|
|
|
return nil
|
2019-06-26 01:36:53 +00:00
|
|
|
})
|
|
|
|
s.supervisor.Add(s.editButton)
|
|
|
|
|
2020-04-03 06:09:46 +00:00
|
|
|
// Set up the inventory HUD.
|
|
|
|
s.setupInventoryHud()
|
|
|
|
|
2019-04-10 01:28:08 +00:00
|
|
|
// Initialize the drawing canvas.
|
2018-09-26 17:04:46 +00:00
|
|
|
s.drawing = uix.NewCanvas(balance.ChunkSize, false)
|
2019-04-14 22:25:03 +00:00
|
|
|
s.drawing.Name = "play-canvas"
|
2018-09-25 16:40:34 +00:00
|
|
|
s.drawing.MoveTo(render.Origin)
|
2019-12-28 03:16:34 +00:00
|
|
|
s.drawing.Resize(render.NewRect(d.width, d.height))
|
2018-09-25 16:40:34 +00:00
|
|
|
s.drawing.Compute(d.Engine)
|
2018-07-24 03:10:53 +00:00
|
|
|
|
Add Switches, Fire/Water Collision and Play Menu
* New doodads: Switches.
* They come in four varieties: wall switch (background element, with
"ON/OFF" text) and three side-profile switches for the floor, left
or right walls.
* On collision with the player, they flip their state from "OFF" to
"ON" or vice versa. If the player walks away and then collides
again, the switch flips again.
* Can be used to open/close Electric Doors when turned on/off. Their
default state is "off"
* If a switch receives a power signal from another linked switch, it
sets its own state to match. So, two "on/off" switches that are
connected to a door AND to each other will both flip on/off when one
of them flips.
* Update the Level Collision logic to support Decoration, Fire and Water
pixel collisions.
* Previously, ALL pixels in the level were acting as though solid.
* Non-solid pixels don't count for collision detection, but their
attributes (fire and water) are collected and returned.
* Updated the MenuScene to support loading a map file in Play Mode
instead of Edit Mode. Updated the title screen menu to add a button
for playing levels instead of editing them.
* Wrote some documentation.
2019-07-07 01:30:03 +00:00
|
|
|
// Handler when an actor touches water or fire.
|
|
|
|
s.drawing.OnLevelCollision = func(a *uix.Actor, col *collision.Collide) {
|
2021-03-31 06:33:25 +00:00
|
|
|
if col.InFire != "" {
|
Add Switches, Fire/Water Collision and Play Menu
* New doodads: Switches.
* They come in four varieties: wall switch (background element, with
"ON/OFF" text) and three side-profile switches for the floor, left
or right walls.
* On collision with the player, they flip their state from "OFF" to
"ON" or vice versa. If the player walks away and then collides
again, the switch flips again.
* Can be used to open/close Electric Doors when turned on/off. Their
default state is "off"
* If a switch receives a power signal from another linked switch, it
sets its own state to match. So, two "on/off" switches that are
connected to a door AND to each other will both flip on/off when one
of them flips.
* Update the Level Collision logic to support Decoration, Fire and Water
pixel collisions.
* Previously, ALL pixels in the level were acting as though solid.
* Non-solid pixels don't count for collision detection, but their
attributes (fire and water) are collected and returned.
* Updated the MenuScene to support loading a map file in Play Mode
instead of Edit Mode. Updated the title screen menu to add a button
for playing levels instead of editing them.
* Wrote some documentation.
2019-07-07 01:30:03 +00:00
|
|
|
a.Canvas.MaskColor = render.Black
|
2021-03-31 06:33:25 +00:00
|
|
|
if a.ID() == "PLAYER" { // only the player dies in fire.
|
|
|
|
s.DieByFire(col.InFire)
|
|
|
|
}
|
Add Switches, Fire/Water Collision and Play Menu
* New doodads: Switches.
* They come in four varieties: wall switch (background element, with
"ON/OFF" text) and three side-profile switches for the floor, left
or right walls.
* On collision with the player, they flip their state from "OFF" to
"ON" or vice versa. If the player walks away and then collides
again, the switch flips again.
* Can be used to open/close Electric Doors when turned on/off. Their
default state is "off"
* If a switch receives a power signal from another linked switch, it
sets its own state to match. So, two "on/off" switches that are
connected to a door AND to each other will both flip on/off when one
of them flips.
* Update the Level Collision logic to support Decoration, Fire and Water
pixel collisions.
* Previously, ALL pixels in the level were acting as though solid.
* Non-solid pixels don't count for collision detection, but their
attributes (fire and water) are collected and returned.
* Updated the MenuScene to support loading a map file in Play Mode
instead of Edit Mode. Updated the title screen menu to add a button
for playing levels instead of editing them.
* Wrote some documentation.
2019-07-07 01:30:03 +00:00
|
|
|
} else if col.InWater {
|
|
|
|
a.Canvas.MaskColor = render.DarkBlue
|
|
|
|
} else {
|
|
|
|
a.Canvas.MaskColor = render.Invisible
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-25 16:40:34 +00:00
|
|
|
// Given a filename or map data to play?
|
|
|
|
if s.Level != nil {
|
|
|
|
log.Debug("PlayScene.Setup: received level from scene caller")
|
2018-10-28 05:22:13 +00:00
|
|
|
s.drawing.LoadLevel(d.Engine, s.Level)
|
2019-04-10 02:17:56 +00:00
|
|
|
s.drawing.InstallActors(s.Level.Actors)
|
2018-07-24 03:10:53 +00:00
|
|
|
} else if s.Filename != "" {
|
2021-07-19 03:04:24 +00:00
|
|
|
loadscreen.SetSubtitle("Opening: " + s.Filename)
|
2018-07-24 03:10:53 +00:00
|
|
|
log.Debug("PlayScene.Setup: loading map from file %s", s.Filename)
|
2019-04-16 06:07:15 +00:00
|
|
|
// NOTE: s.LoadLevel also calls s.drawing.InstallActors
|
2018-07-24 03:10:53 +00:00
|
|
|
s.LoadLevel(s.Filename)
|
|
|
|
}
|
|
|
|
|
2018-09-25 16:40:34 +00:00
|
|
|
if s.Level == nil {
|
2018-07-24 03:10:53 +00:00
|
|
|
log.Debug("PlayScene.Setup: no grid given, initializing empty grid")
|
2018-09-25 16:40:34 +00:00
|
|
|
s.Level = level.New()
|
2018-10-28 05:22:13 +00:00
|
|
|
s.drawing.LoadLevel(d.Engine, s.Level)
|
2019-04-10 02:17:56 +00:00
|
|
|
s.drawing.InstallActors(s.Level.Actors)
|
2018-06-21 02:00:46 +00:00
|
|
|
}
|
2018-07-24 03:10:53 +00:00
|
|
|
|
2021-07-19 03:04:24 +00:00
|
|
|
// Set the loading screen text with the level metadata.
|
|
|
|
loadscreen.SetSubtitle(
|
|
|
|
s.Level.Title,
|
|
|
|
"by "+s.Level.Author,
|
|
|
|
)
|
|
|
|
|
2019-04-16 06:07:15 +00:00
|
|
|
// Load all actor scripts.
|
|
|
|
s.drawing.SetScriptSupervisor(s.scripting)
|
|
|
|
if err := s.scripting.InstallScripts(s.Level); err != nil {
|
|
|
|
log.Error("PlayScene.Setup: failed to InstallScripts: %s", err)
|
|
|
|
}
|
2019-05-02 01:30:30 +00:00
|
|
|
|
|
|
|
// Load in the player character.
|
2019-12-31 02:13:28 +00:00
|
|
|
s.setupPlayer()
|
2019-05-02 01:30:30 +00:00
|
|
|
|
|
|
|
// Run all the actor scripts' main() functions.
|
|
|
|
if err := s.drawing.InstallScripts(); err != nil {
|
|
|
|
log.Error("PlayScene.Setup: failed to drawing.InstallScripts: %s", err)
|
|
|
|
}
|
|
|
|
|
2019-07-07 06:50:38 +00:00
|
|
|
if s.CanEdit {
|
|
|
|
d.Flash("Entered Play Mode. Press 'E' to edit this map.")
|
|
|
|
} else {
|
|
|
|
d.Flash("%s", s.Level.Title)
|
|
|
|
}
|
|
|
|
|
2021-07-19 03:04:24 +00:00
|
|
|
// Pre-cache all bitmap images from the level chunks.
|
|
|
|
// Note: we are not running on the main thread, so SDL2 Textures
|
|
|
|
// don't get created yet, but we do the full work of caching bitmap
|
|
|
|
// images which later get fed directly into SDL2 saving speed at
|
|
|
|
// runtime, + the bitmap generation is pretty wicked fast anyway.
|
|
|
|
loadscreen.PreloadAllChunkBitmaps(s.Level.Chunker)
|
|
|
|
|
2019-07-02 22:24:46 +00:00
|
|
|
s.running = true
|
2018-07-24 03:10:53 +00:00
|
|
|
|
2018-06-21 02:00:46 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-12-31 02:13:28 +00:00
|
|
|
// setupPlayer creates and configures the Player Character in the level.
|
|
|
|
func (s *PlayScene) setupPlayer() {
|
|
|
|
// Load in the player character.
|
2020-09-19 05:35:43 +00:00
|
|
|
player, err := doodads.LoadFile(balance.PlayerCharacterDoodad)
|
2019-12-31 02:13:28 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Error("PlayScene.Setup: failed to load player doodad: %s", err)
|
|
|
|
player = doodads.NewDummy(32)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Find the spawn point of the player. Search the level for the
|
|
|
|
// "start-flag.doodad"
|
|
|
|
var (
|
|
|
|
spawn render.Point
|
|
|
|
flagCount int
|
|
|
|
)
|
|
|
|
for actorID, actor := range s.Level.Actors {
|
|
|
|
if actor.Filename == "start-flag.doodad" {
|
|
|
|
if flagCount > 1 {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: start-flag.doodad is 86x86 pixels but we can't tell that
|
|
|
|
// from right here.
|
|
|
|
size := render.NewRect(86, 86)
|
|
|
|
log.Info("Found start-flag.doodad at %s (ID %s)", actor.Point, actorID)
|
|
|
|
spawn = render.NewPoint(
|
|
|
|
// X: centered inside the flag.
|
|
|
|
actor.Point.X+(size.W/2)-(player.Layers[0].Chunker.Size/2),
|
|
|
|
|
|
|
|
// Y: the bottom of the flag, 4 pixels from the floor.
|
|
|
|
actor.Point.Y+size.H-4-(player.Layers[0].Chunker.Size),
|
|
|
|
)
|
|
|
|
flagCount++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Surface warnings around the spawn flag.
|
|
|
|
if flagCount == 0 {
|
|
|
|
s.d.Flash("Warning: this level contained no Start Flag.")
|
|
|
|
} else if flagCount > 1 {
|
|
|
|
s.d.Flash("Warning: this level contains multiple Start Flags. Player spawn point is ambiguous.")
|
|
|
|
}
|
|
|
|
|
|
|
|
s.Player = uix.NewActor("PLAYER", &level.Actor{}, player)
|
|
|
|
s.Player.MoveTo(spawn)
|
|
|
|
s.drawing.AddActor(s.Player)
|
|
|
|
s.drawing.FollowActor = s.Player.ID()
|
|
|
|
|
2020-04-05 04:00:32 +00:00
|
|
|
// Set up the movement physics for the player.
|
|
|
|
s.playerPhysics = &physics.Mover{
|
|
|
|
MaxSpeed: physics.NewVector(balance.PlayerMaxVelocity, balance.PlayerMaxVelocity),
|
|
|
|
// Gravity: physics.NewVector(balance.Gravity, balance.Gravity),
|
|
|
|
Acceleration: 0.025,
|
|
|
|
Friction: 0.1,
|
|
|
|
}
|
|
|
|
|
2019-12-31 02:13:28 +00:00
|
|
|
// Set up the player character's script in the VM.
|
|
|
|
if err := s.scripting.AddLevelScript(s.Player.ID()); err != nil {
|
|
|
|
log.Error("PlayScene.Setup: scripting.InstallActor(player) failed: %s", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-02 22:24:46 +00:00
|
|
|
// SetupAlertbox configures the alert box UI.
|
|
|
|
func (s *PlayScene) SetupAlertbox() {
|
|
|
|
window := ui.NewWindow("Level Completed")
|
|
|
|
window.Configure(ui.Config{
|
|
|
|
Width: 320,
|
|
|
|
Height: 160,
|
|
|
|
Background: render.Grey,
|
|
|
|
})
|
|
|
|
window.Compute(s.d.Engine)
|
|
|
|
|
|
|
|
{
|
|
|
|
frame := ui.NewFrame("Open Drawing Frame")
|
|
|
|
window.Pack(frame, ui.Pack{
|
2019-12-29 08:01:47 +00:00
|
|
|
Side: ui.N,
|
2019-07-02 22:24:46 +00:00
|
|
|
Fill: true,
|
|
|
|
Expand: true,
|
|
|
|
})
|
|
|
|
|
|
|
|
/******************
|
|
|
|
* Frame for selecting User Levels
|
|
|
|
******************/
|
|
|
|
|
2019-07-07 03:31:50 +00:00
|
|
|
s.alertBoxLabel = ui.NewLabel(ui.Label{
|
2019-07-02 22:24:46 +00:00
|
|
|
Text: "Congratulations on clearing the level!",
|
|
|
|
Font: balance.LabelFont,
|
|
|
|
})
|
2019-07-07 03:31:50 +00:00
|
|
|
frame.Pack(s.alertBoxLabel, ui.Pack{
|
2019-12-29 08:01:47 +00:00
|
|
|
Side: ui.N,
|
|
|
|
FillX: true,
|
|
|
|
PadY: 16,
|
2019-07-02 22:24:46 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
/******************
|
|
|
|
* Confirm/cancel buttons.
|
|
|
|
******************/
|
|
|
|
|
|
|
|
bottomFrame := ui.NewFrame("Button Frame")
|
|
|
|
frame.Pack(bottomFrame, ui.Pack{
|
2019-12-29 08:01:47 +00:00
|
|
|
Side: ui.N,
|
|
|
|
FillX: true,
|
|
|
|
PadY: 8,
|
2019-07-02 22:24:46 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
// Button factory for the various options.
|
|
|
|
makeButton := func(text string, handler func()) *ui.Button {
|
|
|
|
btn := ui.NewButton(text, ui.NewLabel(ui.Label{
|
|
|
|
Font: balance.LabelFont,
|
|
|
|
Text: text,
|
|
|
|
}))
|
2020-04-07 06:21:17 +00:00
|
|
|
btn.Handle(ui.Click, func(ed ui.EventData) error {
|
2019-07-02 22:24:46 +00:00
|
|
|
handler()
|
2020-04-07 06:21:17 +00:00
|
|
|
return nil
|
2019-07-02 22:24:46 +00:00
|
|
|
})
|
|
|
|
bottomFrame.Pack(btn, ui.Pack{
|
2019-12-29 05:48:49 +00:00
|
|
|
Side: ui.W,
|
2019-12-29 08:01:47 +00:00
|
|
|
PadX: 2,
|
2019-07-02 22:24:46 +00:00
|
|
|
})
|
|
|
|
s.supervisor.Add(btn)
|
|
|
|
btn.Hide() // all buttons hidden by default
|
|
|
|
return btn
|
|
|
|
}
|
|
|
|
|
|
|
|
s.alertReplayButton = makeButton("Play Again", func() {
|
|
|
|
s.RestartLevel()
|
|
|
|
})
|
|
|
|
s.alertEditButton = makeButton("Edit Level", func() {
|
|
|
|
s.EditLevel()
|
|
|
|
})
|
|
|
|
s.alertNextButton = makeButton("Next Level", func() {
|
|
|
|
s.d.Flash("Not Implemented")
|
|
|
|
})
|
|
|
|
s.alertExitButton = makeButton("Exit to Menu", func() {
|
|
|
|
s.d.Goto(&MainScene{})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
s.alertBox = window
|
|
|
|
s.alertBox.Hide()
|
|
|
|
}
|
|
|
|
|
2019-06-26 01:36:53 +00:00
|
|
|
// EditLevel toggles out of Play Mode to edit the level.
|
|
|
|
func (s *PlayScene) EditLevel() {
|
|
|
|
log.Info("Edit Mode, Go!")
|
|
|
|
s.d.Goto(&EditorScene{
|
|
|
|
Filename: s.Filename,
|
|
|
|
Level: s.Level,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-07-02 22:24:46 +00:00
|
|
|
// RestartLevel starts the level over again.
|
|
|
|
func (s *PlayScene) RestartLevel() {
|
|
|
|
log.Info("Restart Level")
|
|
|
|
s.d.Goto(&PlayScene{
|
|
|
|
Filename: s.Filename,
|
|
|
|
Level: s.Level,
|
|
|
|
CanEdit: s.CanEdit,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-03-31 06:33:25 +00:00
|
|
|
// DieByFire ends the level by "fire", or w/e the swatch is named.
|
|
|
|
func (s *PlayScene) DieByFire(name string) {
|
|
|
|
log.Info("Watch out for %s!", name)
|
2019-07-07 03:31:50 +00:00
|
|
|
s.alertBox.Title = "You've died!"
|
2021-03-31 06:33:25 +00:00
|
|
|
s.alertBoxLabel.Text = fmt.Sprintf("Watch out for %s!", name)
|
2019-07-07 03:31:50 +00:00
|
|
|
|
|
|
|
s.alertReplayButton.Show()
|
|
|
|
if s.CanEdit {
|
|
|
|
s.alertEditButton.Show()
|
|
|
|
}
|
|
|
|
s.alertExitButton.Show()
|
|
|
|
|
|
|
|
s.alertBox.Show()
|
|
|
|
|
|
|
|
// Stop the simulation.
|
|
|
|
s.running = false
|
|
|
|
}
|
|
|
|
|
2018-06-21 02:00:46 +00:00
|
|
|
// Loop the editor scene.
|
2019-12-22 22:11:01 +00:00
|
|
|
func (s *PlayScene) Loop(d *Doodle, ev *event.State) error {
|
2021-07-19 03:04:24 +00:00
|
|
|
// Skip if still loading.
|
|
|
|
if loadscreen.IsActive() {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-04-10 01:28:08 +00:00
|
|
|
// Update debug overlay values.
|
2019-12-28 03:16:34 +00:00
|
|
|
*s.debWorldIndex = s.drawing.WorldIndexAt(render.NewPoint(ev.CursorX, ev.CursorY)).String()
|
2019-04-14 22:25:03 +00:00
|
|
|
*s.debPosition = s.Player.Position().String() + " vel " + s.Player.Velocity().String()
|
2019-04-10 01:28:08 +00:00
|
|
|
*s.debViewport = s.drawing.Viewport().String()
|
|
|
|
*s.debScroll = s.drawing.Scroll.String()
|
|
|
|
|
2019-06-26 01:36:53 +00:00
|
|
|
s.supervisor.Loop(ev)
|
|
|
|
|
2019-04-10 01:28:08 +00:00
|
|
|
// Has the window been resized?
|
2019-12-22 22:11:01 +00:00
|
|
|
if ev.WindowResized {
|
2019-04-10 01:28:08 +00:00
|
|
|
w, h := d.Engine.WindowSize()
|
|
|
|
if w != d.width || h != d.height {
|
|
|
|
d.width = w
|
|
|
|
d.height = h
|
2019-12-28 03:16:34 +00:00
|
|
|
s.drawing.Resize(render.NewRect(d.width, d.height))
|
2019-04-10 01:28:08 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-24 03:10:53 +00:00
|
|
|
// Switching to Edit Mode?
|
2020-11-18 02:22:48 +00:00
|
|
|
if s.CanEdit && keybind.GotoEdit(ev) {
|
2019-06-26 01:36:53 +00:00
|
|
|
s.EditLevel()
|
2018-07-24 03:10:53 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-07-02 22:24:46 +00:00
|
|
|
// Is the simulation still running?
|
|
|
|
if s.running {
|
|
|
|
// Loop the script supervisor so timeouts/intervals can fire in scripts.
|
|
|
|
if err := s.scripting.Loop(); err != nil {
|
|
|
|
log.Error("PlayScene.Loop: scripting.Loop: %s", err)
|
|
|
|
}
|
2019-04-19 01:15:05 +00:00
|
|
|
|
2019-07-02 22:24:46 +00:00
|
|
|
s.movePlayer(ev)
|
|
|
|
if err := s.drawing.Loop(ev); err != nil {
|
|
|
|
log.Error("Drawing loop error: %s", err.Error())
|
|
|
|
}
|
2020-04-03 06:09:46 +00:00
|
|
|
|
|
|
|
// Update the inventory HUD.
|
|
|
|
s.computeInventory()
|
2019-04-10 02:17:56 +00:00
|
|
|
}
|
|
|
|
|
2018-07-22 03:43:01 +00:00
|
|
|
return nil
|
2018-06-21 02:00:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Draw the pixels on this frame.
|
|
|
|
func (s *PlayScene) Draw(d *Doodle) error {
|
2021-07-19 03:04:24 +00:00
|
|
|
// Skip if still loading.
|
|
|
|
if loadscreen.IsActive() {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-06-21 02:00:46 +00:00
|
|
|
// Clear the canvas and fill it with white.
|
2018-07-22 00:12:22 +00:00
|
|
|
d.Engine.Clear(render.White)
|
2018-06-21 02:00:46 +00:00
|
|
|
|
2018-09-25 16:40:34 +00:00
|
|
|
// Draw the level.
|
|
|
|
s.drawing.Present(d.Engine, s.drawing.Point())
|
2018-06-21 02:00:46 +00:00
|
|
|
|
2018-07-25 03:57:22 +00:00
|
|
|
// Draw out bounding boxes.
|
2021-06-03 03:41:53 +00:00
|
|
|
if DebugCollision {
|
|
|
|
for _, actor := range s.drawing.Actors() {
|
|
|
|
d.DrawCollisionBox(s.drawing, actor)
|
|
|
|
}
|
|
|
|
}
|
2018-07-25 03:57:22 +00:00
|
|
|
|
2020-04-03 06:09:46 +00:00
|
|
|
// Draw the UI screen and any widgets that attached to it.
|
|
|
|
s.screen.Compute(d.Engine)
|
|
|
|
s.screen.Present(d.Engine, render.Origin)
|
|
|
|
|
2019-06-26 01:36:53 +00:00
|
|
|
// Draw the Edit button.
|
|
|
|
var (
|
2019-12-28 03:16:34 +00:00
|
|
|
canSize = s.drawing.Size()
|
|
|
|
size = s.editButton.Size()
|
|
|
|
padding = 8
|
2019-06-26 01:36:53 +00:00
|
|
|
)
|
2019-12-29 08:01:47 +00:00
|
|
|
s.editButton.MoveTo(render.Point{
|
2019-06-26 01:36:53 +00:00
|
|
|
X: canSize.W - size.W - padding,
|
|
|
|
Y: canSize.H - size.H - padding,
|
|
|
|
})
|
2019-12-29 08:01:47 +00:00
|
|
|
s.editButton.Present(d.Engine, s.editButton.Point())
|
2019-06-26 01:36:53 +00:00
|
|
|
|
2019-07-02 22:24:46 +00:00
|
|
|
// Draw the alert box window.
|
|
|
|
if !s.alertBox.Hidden() {
|
|
|
|
s.alertBox.Compute(d.Engine)
|
|
|
|
s.alertBox.MoveTo(render.Point{
|
2019-12-28 03:16:34 +00:00
|
|
|
X: (d.width / 2) - (s.alertBox.Size().W / 2),
|
|
|
|
Y: (d.height / 2) - (s.alertBox.Size().H / 2),
|
2019-07-02 22:24:46 +00:00
|
|
|
})
|
|
|
|
s.alertBox.Present(d.Engine, s.alertBox.Point())
|
|
|
|
}
|
|
|
|
|
2018-06-21 02:00:46 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-07-22 00:12:22 +00:00
|
|
|
// movePlayer updates the player's X,Y coordinate based on key pressed.
|
2019-12-22 22:11:01 +00:00
|
|
|
func (s *PlayScene) movePlayer(ev *event.State) {
|
2020-04-05 04:00:32 +00:00
|
|
|
var (
|
|
|
|
playerSpeed = float64(balance.PlayerMaxVelocity)
|
|
|
|
velocity = s.Player.Velocity()
|
|
|
|
direction float64
|
|
|
|
jumping bool
|
|
|
|
)
|
2019-04-10 02:17:56 +00:00
|
|
|
|
2020-04-05 04:00:32 +00:00
|
|
|
// Antigravity: player can move anywhere with arrow keys.
|
|
|
|
if s.antigravity {
|
|
|
|
velocity.X = 0
|
|
|
|
velocity.Y = 0
|
2020-01-03 06:05:49 +00:00
|
|
|
|
2020-04-05 04:00:32 +00:00
|
|
|
// Shift to slow your roll to 1 pixel per tick.
|
2021-01-03 23:19:21 +00:00
|
|
|
if keybind.Shift(ev) {
|
2020-04-05 04:00:32 +00:00
|
|
|
playerSpeed = 1
|
|
|
|
}
|
2018-07-24 03:10:53 +00:00
|
|
|
|
2021-01-03 23:19:21 +00:00
|
|
|
if keybind.Left(ev) {
|
2020-04-05 04:00:32 +00:00
|
|
|
velocity.X = -playerSpeed
|
2021-01-03 23:19:21 +00:00
|
|
|
} else if keybind.Right(ev) {
|
2020-04-05 04:00:32 +00:00
|
|
|
velocity.X = playerSpeed
|
|
|
|
}
|
2021-01-03 23:19:21 +00:00
|
|
|
if keybind.Up(ev) {
|
2020-04-05 04:00:32 +00:00
|
|
|
velocity.Y = -playerSpeed
|
2021-01-03 23:19:21 +00:00
|
|
|
} else if keybind.Down(ev) {
|
2020-04-05 04:00:32 +00:00
|
|
|
velocity.Y = playerSpeed
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Moving left or right.
|
2021-01-03 23:19:21 +00:00
|
|
|
if keybind.Left(ev) {
|
2020-04-05 04:00:32 +00:00
|
|
|
direction = -1
|
2021-01-03 23:19:21 +00:00
|
|
|
} else if keybind.Right(ev) {
|
2020-04-05 04:00:32 +00:00
|
|
|
direction = 1
|
|
|
|
}
|
2019-07-05 22:02:22 +00:00
|
|
|
|
2020-04-05 04:00:32 +00:00
|
|
|
// Up button to signal they want to jump.
|
2021-01-03 23:19:21 +00:00
|
|
|
if keybind.Up(ev) && (s.Player.Grounded() || s.playerJumpCounter >= 0) {
|
2020-04-05 04:00:32 +00:00
|
|
|
jumping = true
|
|
|
|
|
|
|
|
if s.Player.Grounded() {
|
|
|
|
// Allow them to sustain the jump this many ticks.
|
|
|
|
s.playerJumpCounter = 32
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Moving left or right? Interpolate their velocity by acceleration.
|
|
|
|
if direction != 0 {
|
|
|
|
// TODO: fast turn-around if they change directions so they don't
|
|
|
|
// slip and slide while their velocity updates.
|
|
|
|
velocity.X = physics.Lerp(
|
|
|
|
velocity.X,
|
|
|
|
direction*s.playerPhysics.MaxSpeed.X,
|
|
|
|
s.playerPhysics.Acceleration,
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
// Slow them back to zero using friction.
|
|
|
|
velocity.X = physics.Lerp(
|
|
|
|
velocity.X,
|
|
|
|
0,
|
|
|
|
s.playerPhysics.Friction,
|
|
|
|
)
|
2019-07-05 22:02:22 +00:00
|
|
|
}
|
|
|
|
|
2020-04-05 04:00:32 +00:00
|
|
|
// Moving upwards (jumping): give them full acceleration upwards.
|
|
|
|
if jumping {
|
|
|
|
velocity.Y = -playerSpeed
|
|
|
|
}
|
|
|
|
|
|
|
|
// While in the air, count down their jump counter; when zero they
|
|
|
|
// cannot jump again until they touch ground.
|
|
|
|
if !s.Player.Grounded() {
|
|
|
|
s.playerJumpCounter--
|
|
|
|
}
|
2018-07-24 03:10:53 +00:00
|
|
|
}
|
|
|
|
|
2021-01-03 23:19:21 +00:00
|
|
|
// Move the player unless frozen.
|
|
|
|
// TODO: if Y=0 then gravity fails, but not doing this allows the
|
|
|
|
// player to jump while frozen. Not a HUGE deal right now as only Warp Doors
|
|
|
|
// freeze the player currently but do address this later.
|
|
|
|
if s.Player.IsFrozen() {
|
|
|
|
velocity.X = 0
|
|
|
|
}
|
2019-04-14 22:25:03 +00:00
|
|
|
s.Player.SetVelocity(velocity)
|
2019-05-05 21:03:20 +00:00
|
|
|
|
2021-01-03 23:19:21 +00:00
|
|
|
// If the "Use" key is pressed, set an actor flag on the player.
|
|
|
|
s.Player.SetUsing(keybind.Use(ev))
|
|
|
|
|
2021-06-20 05:14:41 +00:00
|
|
|
s.scripting.To(s.Player.ID()).Events.RunKeypress(keybind.FromEvent(ev))
|
2019-04-14 22:25:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Drawing returns the private world drawing, for debugging with the console.
|
|
|
|
func (s *PlayScene) Drawing() *uix.Canvas {
|
|
|
|
return s.drawing
|
2018-06-21 02:00:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// LoadLevel loads a level from disk.
|
2018-07-21 22:11:00 +00:00
|
|
|
func (s *PlayScene) LoadLevel(filename string) error {
|
2018-09-25 16:40:34 +00:00
|
|
|
s.Filename = filename
|
2018-06-21 02:00:46 +00:00
|
|
|
|
2019-07-02 22:24:46 +00:00
|
|
|
level, err := level.LoadFile(filename)
|
2018-09-25 16:40:34 +00:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("PlayScene.LoadLevel(%s): %s", filename, err)
|
|
|
|
}
|
2018-06-21 02:00:46 +00:00
|
|
|
|
2018-09-25 16:40:34 +00:00
|
|
|
s.Level = level
|
2018-10-28 05:22:13 +00:00
|
|
|
s.drawing.LoadLevel(s.d.Engine, s.Level)
|
2019-07-02 22:24:46 +00:00
|
|
|
s.drawing.InstallActors(s.Level.Actors)
|
2018-06-21 02:00:46 +00:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2018-07-24 03:10:53 +00:00
|
|
|
|
|
|
|
// Destroy the scene.
|
|
|
|
func (s *PlayScene) Destroy() error {
|
|
|
|
return nil
|
|
|
|
}
|