doodle/pkg/editor_ui_doodad.go
Noah Petherbridge 8965a7d86a Doodads: Crumbly Floor, Start Flag & State Blocks
Add new doodads:

* Start Flag: place this in a level to set the spawn point of the player
  character. If no flag is found, the player spawns at 0,0 in the top
  corner of the map. Only use one Start Flag per level, otherwise the
  player will randomly spawn at one of them.
* Crumbly Floor: a solid floor that begins to shake and then fall apart
  after a moment when a mobile character steps on it. The floor respawns
  after 5 seconds.
* State Blocks: blue and orange blocks that toggle between solid and
  pass-thru whenever a State Button is activated.
* State Button: a solid "ON/OFF" block that toggles State Blocks back
  and forth when touched. Only activates if touched on the side or bottom;
  acts as a solid floor when walked on from the top.

New features for doodad scripts:

* Actor scripts: call SetMobile(true) to mark an actor as a mobile mob
  (i.e. player character or enemy). Other doodads can check if the actor
  colliding with them IsMobile so they don't activate if placed too close
  to other (non-mobile) doodads in a level. The Blue and Red Azulians
  are the only mobile characters so far.
* Message.Broadcast allows sending a pub/sub message out to ALL doodads
  in the level, instead of only to linked doodads as Message.Publish does.
  This is used for the State Blocks to globally communicate on/off status
  without needing to link them all together manually.
2019-12-30 18:13:28 -08:00

270 lines
7.2 KiB
Go

package doodle
// XXX REFACTOR XXX
// This function only uses EditorUI and not Doodle and is a candidate for
// refactor into a subpackage if EditorUI itself can ever be decoupled.
import (
"fmt"
"git.kirsle.net/apps/doodle/pkg/balance"
"git.kirsle.net/apps/doodle/pkg/doodads"
"git.kirsle.net/apps/doodle/pkg/level"
"git.kirsle.net/apps/doodle/pkg/log"
"git.kirsle.net/apps/doodle/pkg/uix"
"git.kirsle.net/go/render"
"git.kirsle.net/go/ui"
)
// DraggableActor is a Doodad being dragged from the Doodad palette.
type DraggableActor struct {
canvas *uix.Canvas
doodad *doodads.Doodad // if a new one from the palette
actor *level.Actor // if a level actor
}
// startDragActor begins the drag event for a Doodad onto a level.
// actor may be nil (if you drag a new doodad from the palette) or otherwise
// is an existing actor from the level.
func (u *EditorUI) startDragActor(doodad *doodads.Doodad, actor *level.Actor) {
u.Supervisor.DragStart()
if doodad == nil {
if actor != nil {
obj, err := doodads.LoadFile(actor.Filename)
if err != nil {
log.Error("startDragExistingActor: actor doodad name %s not found: %s", actor.Filename, err)
return
}
doodad = obj
} else {
panic("EditorUI.startDragActor: doodad AND/OR actor is required, but neither were given")
}
}
// Create the canvas to render on the mouse cursor.
drawing := uix.NewCanvas(doodad.Layers[0].Chunker.Size, false)
drawing.LoadDoodad(doodad)
drawing.Resize(doodad.Rect())
drawing.SetBackground(render.RGBA(0, 0, 1, 0)) // TODO: invisible becomes white
drawing.MaskColor = balance.DragColor // blueprint effect
u.DraggableActor = &DraggableActor{
canvas: drawing,
doodad: doodad,
actor: actor,
}
}
// setupDoodadFrame configures the Doodad Palette tab for Edit Mode.
// This is a subroutine of editor_ui.go#SetupPalette()
//
// Can return an error if userdir.ListDoodads() returns an error (like directory
// not found), but it will *ALWAYS* return a valid ui.Frame -- it will just be
// empty and uninitialized.
func (u *EditorUI) setupDoodadFrame(e render.Engine, window *ui.Window) (*ui.Frame, error) {
var (
frame = ui.NewFrame("Doodad Tab")
perRow = balance.UIDoodadsPerRow
)
// Pager buttons on top of the doodad list.
pager := ui.NewFrame("Doodad Pager")
pager.SetBackground(render.RGBA(255, 0, 0, 20)) // TODO: if I don't set a background color,
// this frame will light up the same color as the Link button on mouse
// over. somewhere some memory might be shared between the recent widgets
{
leftBtn := ui.NewButton("Prev Page", ui.NewLabel(ui.Label{
Text: "<",
Font: balance.MenuFont,
}))
leftBtn.Handle(ui.Click, func(p render.Point) {
u.scrollDoodadFrame(-1)
})
u.Supervisor.Add(leftBtn)
pager.Pack(leftBtn, ui.Pack{
Side: ui.W,
})
scroller := ui.NewFrame("Doodad Scroll Progressbar")
scroller.Configure(ui.Config{
Width: 20,
Height: 20,
Background: render.RGBA(128, 128, 128, 128),
})
pager.Pack(scroller, ui.Pack{
Side: ui.W,
})
u.doodadScroller = scroller
rightBtn := ui.NewButton("Next Page", ui.NewLabel(ui.Label{
Text: ">",
Font: balance.MenuFont,
}))
rightBtn.Handle(ui.Click, func(p render.Point) {
u.scrollDoodadFrame(1)
})
u.Supervisor.Add(rightBtn)
pager.Pack(rightBtn, ui.Pack{
Side: ui.E,
})
}
u.doodadPager = pager
frame.Pack(pager, ui.Pack{
Side: ui.N,
Fill: true,
PadY: 5,
})
doodadsAvailable, err := doodads.ListDoodads()
if err != nil {
return frame, fmt.Errorf(
"setupDoodadFrame: doodads.ListDoodads: %s",
err,
)
}
var buttonSize = (paletteWidth - window.BoxThickness(2)) / perRow
u.doodadButtonSize = buttonSize
// Load all the doodads, skip hidden ones.
var items []*doodads.Doodad
for _, filename := range doodadsAvailable {
doodad, err := doodads.LoadFile(filename)
if err != nil {
log.Error(err.Error())
doodad = doodads.New(balance.DoodadSize)
}
// Skip hidden doodads.
if doodad.Hidden && !balance.ShowHiddenDoodads {
log.Info("skip %s: hidden doodad", filename)
continue
}
doodad.Filename = filename
items = append(items, doodad)
}
// Draw the doodad buttons in a grid `perRow` buttons wide.
var (
row *ui.Frame
rowCount int // for labeling the ui.Frame for each row
btnRows = []*ui.Frame{} // Collect the row frames for the buttons.
)
for i, doodad := range items {
doodad := doodad
if row == nil || i%perRow == 0 {
rowCount++
row = ui.NewFrame(fmt.Sprintf("Doodad Row %d", rowCount))
row.SetBackground(balance.WindowBackground)
btnRows = append(btnRows, row)
frame.Pack(row, ui.Pack{
Side: ui.N,
Fill: true,
})
}
can := uix.NewCanvas(int(buttonSize), true)
can.Name = doodad.Title
can.SetBackground(render.White)
can.LoadDoodad(doodad)
btn := ui.NewButton(doodad.Title, can)
btn.Resize(render.NewRect(
buttonSize-2, // TODO: without the -2 the button border
buttonSize-2, // rests on top of the window border.
))
row.Pack(btn, ui.Pack{
Side: ui.W,
})
// Begin the drag event to grab this Doodad.
// NOTE: The drag target is the EditorUI.Canvas in
// editor_ui.go#SetupCanvas()
btn.Handle(ui.MouseDown, func(e render.Point) {
log.Warn("MouseDown on doodad %s (%s)", doodad.Filename, doodad.Title)
u.startDragActor(doodad, nil)
})
u.Supervisor.Add(btn)
// Resize the canvas to fill the button interior.
btnSize := btn.Size()
can.Resize(render.NewRect(
btnSize.W-btn.BoxThickness(2),
btnSize.H-btn.BoxThickness(2),
),
)
btn.Compute(e)
}
u.doodadRows = btnRows
u.scrollDoodadFrame(0)
return frame, nil
}
// scrollDoodadFrame handles the Page Up/Down buttons to adjust the number of
// Doodads visible on screen.
//
// rows is the number of rows to scroll. Positive values mean scroll *down*
// the list.
func (u *EditorUI) scrollDoodadFrame(rows int) {
u.doodadSkip += rows
if u.doodadSkip < 0 {
u.doodadSkip = 0
}
// Calculate about how many rows we can see given our current window size.
var (
maxVisibleHeight = u.d.height - 86
calculatedHeight int
rowsBefore int // count of rows hidden before
rowsVisible int
rowsAfter int // count of rows hidden after
rowsEstimated = maxVisibleHeight / u.doodadButtonSize // estimated number rows shown
maxSkip = ((len(u.doodadRows) * int(u.doodadButtonSize)) - int(u.doodadButtonSize*rowsEstimated)) / int(u.doodadButtonSize)
)
if maxSkip < 0 {
maxSkip = 0
}
if u.doodadSkip > maxSkip {
u.doodadSkip = maxSkip
}
// If the window is big enough to encompass all the doodads, don't show
// the pager toolbar, its just confusing.
if maxSkip == 0 {
u.doodadPager.Hide()
} else {
u.doodadPager.Show()
}
// Comb through the doodads and show/hide the relevant buttons.
for i, row := range u.doodadRows {
if i < u.doodadSkip {
row.Hide()
rowsBefore++
continue
}
calculatedHeight += u.doodadButtonSize
if calculatedHeight > maxVisibleHeight {
row.Hide()
rowsAfter++
} else {
row.Show()
rowsVisible++
}
}
var viewPercent = float64(rowsBefore+rowsVisible) / float64(len(u.doodadRows))
u.doodadScroller.Configure(ui.Config{
Width: int(float64(paletteWidth-50) * viewPercent), // TODO: hacky magic number
})
}