From 7ea86b4ffcfc077865b44c2bb8a94596db850c3c Mon Sep 17 00:00:00 2001 From: Noah Petherbridge Date: Thu, 2 Sep 2021 22:33:28 -0700 Subject: [PATCH] Generic Doodad Script Selection In the Doodad Properties window, instead of browsing to select a .js file to install your script, a SelectBox of built-in generic scripts are available. These scripts implement simple behaviors and adapt to the full canvas size of the doodad. Built-in scripts so far include: * generic-anvil.js: behaves just like the Anvil. * generic-fire.js: the entire canvas hitbox acts like fire pixels, "burning" mobile doodads and failing the level for the player. * generic-solid.js: the entire canvas hitbox acts solid --- assets/scripts/generic-anvil.js | 61 ++++++++++ assets/scripts/generic-fire.js | 36 ++++++ assets/scripts/generic-solid.js | 19 +++ pkg/uix/scripting.go | 6 +- pkg/windows/doodad_properties.go | 199 ++++++++++++++++++++++++++----- 5 files changed, 291 insertions(+), 30 deletions(-) create mode 100644 assets/scripts/generic-anvil.js create mode 100644 assets/scripts/generic-fire.js create mode 100644 assets/scripts/generic-solid.js diff --git a/assets/scripts/generic-anvil.js b/assets/scripts/generic-anvil.js new file mode 100644 index 0000000..5caa7dc --- /dev/null +++ b/assets/scripts/generic-anvil.js @@ -0,0 +1,61 @@ +// Generic "Anvil" Doodad Script +/* +A doodad that falls and is dangerous while it falls. + +Can be attached to any doodad. +*/ + +var falling = false; + +function main() { + // Make the hitbox be the full canvas size of this doodad. + // Adjust if you want a narrower hitbox. + var size = Self.Size() + Self.SetHitbox(0, 0, size.W, size.H) + + // Note: doodad is not "solid" but hurts if it falls on you. + Self.SetMobile(true); + Self.SetGravity(true); + + // Monitor our Y position to tell if we've been falling. + var lastPoint = Self.Position(); + setInterval(function () { + var nowAt = Self.Position(); + if (nowAt.Y > lastPoint.Y) { + falling = true; + } else { + falling = false; + } + lastPoint = nowAt; + }, 100); + + Events.OnCollide(function (e) { + if (!e.Settled) { + return; + } + + // Were we falling? + if (falling) { + if (e.InHitbox) { + if (e.Actor.IsPlayer()) { + // Fatal to the player. + Sound.Play("crumbly-break.wav"); + FailLevel("Watch out for " + Self.Title + "!"); + return; + } + else if (e.Actor.IsMobile()) { + // Destroy mobile doodads. + Sound.Play("crumbly-break.wav"); + e.Actor.Destroy(); + } + } + } + }); + + // When we receive power, we reset to our original position. + var origPoint = Self.Position(); + Message.Subscribe("power", function (powered) { + Self.MoveTo(origPoint); + Self.SetVelocity(Vector(0, 0)); + }); +} diff --git a/assets/scripts/generic-fire.js b/assets/scripts/generic-fire.js new file mode 100644 index 0000000..aad0fa7 --- /dev/null +++ b/assets/scripts/generic-fire.js @@ -0,0 +1,36 @@ +// Generic "Fire" Doodad Script +/* +The entire square shape of your doodad acts similar to "Fire" +pixels - killing the player character upon contact. + +Can be attached to any doodad. +*/ + +function main() { + // Make the hitbox be the full canvas size of this doodad. + // Adjust if you want a narrower hitbox. + var size = Self.Size() + Self.SetHitbox(0, 0, size.W, size.H) + + Events.OnCollide(function (e) { + if (!e.Settled || !e.InHitbox) { + return; + } + + // Turn mobile actors black, like real fire does. + if (e.Actor.IsMobile()) { + e.Actor.Canvas.MaskColor = RGBA(1, 1, 1, 255) + } + + // End the level if it's the player. + if (e.Actor.IsPlayer()) { + FailLevel("Watch out for " + Self.Title + "!"); + } + }) + + Events.OnLeave(function (e) { + if (e.Actor.IsMobile()) { + e.Actor.MaskColor = RGBA(0, 0, 0, 0) + } + }) +} \ No newline at end of file diff --git a/assets/scripts/generic-solid.js b/assets/scripts/generic-solid.js new file mode 100644 index 0000000..49cd62d --- /dev/null +++ b/assets/scripts/generic-solid.js @@ -0,0 +1,19 @@ +// Generic "Solid" Doodad Script +/* +The entire square shape of your doodad acts similar to "solid" +pixels - blocking collision from all sides. + +Can be attached to any doodad. +*/ + +function main() { + // Make the hitbox be the full canvas size of this doodad. + // Adjust if you want a narrower hitbox. + var size = Self.Size() + Self.SetHitbox(0, 0, size.W, size.H) + + // Solid to all collisions. + Events.OnCollide(function (e) { + return false; + }) +} \ No newline at end of file diff --git a/pkg/uix/scripting.go b/pkg/uix/scripting.go index 2aa16ac..f4f3cf5 100644 --- a/pkg/uix/scripting.go +++ b/pkg/uix/scripting.go @@ -12,7 +12,11 @@ func (w *Canvas) MakeSelfAPI(actor *Actor) map[string]interface{} { "Title": actor.Doodad().Title, // functions - "ID": actor.ID, + "ID": actor.ID, + "Size": func() render.Rect { + var size = actor.Doodad().ChunkSize() + return render.NewRect(size, size) + }, "GetTag": actor.Doodad().Tag, "Position": actor.Position, "MoveTo": func(p render.Point) { diff --git a/pkg/windows/doodad_properties.go b/pkg/windows/doodad_properties.go index 5189517..2837c96 100644 --- a/pkg/windows/doodad_properties.go +++ b/pkg/windows/doodad_properties.go @@ -1,11 +1,13 @@ package windows import ( + "fmt" "io/ioutil" "os" "path/filepath" "sort" + "git.kirsle.net/apps/doodle/assets" "git.kirsle.net/apps/doodle/pkg/balance" "git.kirsle.net/apps/doodle/pkg/doodads" "git.kirsle.net/apps/doodle/pkg/log" @@ -16,6 +18,37 @@ import ( "git.kirsle.net/go/ui" ) +// Some generic built-in doodad scripts users can attach. +var GenericScripts = []struct { + Label string + Help string + Filename string +}{ + { + Label: "Generic Solid", + Help: "The whole canvas of your doodad acts solid.\n" + + "The player and other mobile doodads can walk on\n" + + "top of it, and it blocks movement from the sides.", + Filename: "assets/scripts/generic-solid.js", + }, + { + Label: "Generic Fire", + Help: "The whole canvas of your doodad acts like fire.\n" + + "Mobile doodads who touch it turn dark, and if\n" + + "the player touches it - game over! The failure\n" + + "message says: 'Watch out for (title)!'", + Filename: "assets/scripts/generic-fire.js", + }, + { + Label: "Generic Anvil", + Help: "This doodad will behave like the Anvil: fall with\n" + + "gravity and be deadly to any mobile doodad that it\n" + + "lands on! The failure message says:\n" + + "'Watch out for (title)!'", + Filename: "assets/scripts/generic-anvil.js", + }, +} + // DoodadProperties window. type DoodadProperties struct { // Settings passed in by doodle @@ -247,39 +280,147 @@ func (c DoodadProperties) makeMetaTab(tabFrame *ui.TabFrame, Width, Height int) }) } - // Browse Script button. - btnBrowse := ui.NewButton("Browse Script", ui.NewLabel(ui.Label{ - Text: "Attach a script...", - Font: balance.MenuFont, - })) - btnBrowse.SetStyle(&balance.ButtonPrimary) - btnBrowse.Handle(ui.Click, func(ed ui.EventData) error { - filename, err := native.OpenFile("Choose a .js file", "*.js") - if err != nil { - shmem.Flash("Couldn't show file dialog: %s", err) + // Attaching a Script Frame + { + label := ui.NewLabel(ui.Label{ + Text: "Attach a Script", + Font: balance.LabelFont, + }) + tab.Pack(label, ui.Pack{ + Side: ui.N, + FillX: true, + }) + + frame := ui.NewFrame("Attach Script Frame") + tab.Pack(frame, ui.Pack{ + Side: ui.N, + FillX: true, + }) + + // Browse Script label. + lblBrowse := ui.NewLabel(ui.Label{ + Text: "Browse and attach a .js file:", + Font: balance.MenuFont, + }) + frame.Pack(lblBrowse, ui.Pack{ + Side: ui.W, + }) + + // Browse Script button. + btnBrowse := ui.NewButton("Browse Script", ui.NewLabel(ui.Label{ + Text: "Attach a script...", + Font: balance.MenuFont, + })) + btnBrowse.SetStyle(&balance.ButtonPrimary) + btnBrowse.Handle(ui.Click, func(ed ui.EventData) error { + filename, err := native.OpenFile("Choose a .js file", "*.js") + if err != nil { + shmem.Flash("Couldn't show file dialog: %s", err) + return nil + } + + data, err := ioutil.ReadFile(filename) + if err != nil { + shmem.Flash("Couldn't read file: %s", err) + return nil + } + + c.EditDoodad.Script = string(data) + shmem.Flash("Attached %d-byte script to this doodad.", len(c.EditDoodad.Script)) + + // Toggle the if/else frames. + ifScript.Show() + elseScript.Hide() + return nil - } + }) + c.Supervisor.Add(btnBrowse) + frame.Pack(btnBrowse, ui.Pack{ + Side: ui.E, + }) + } + + // Built-in Generic Scripts Frame + { + frame := ui.NewFrame("Generic Scripts Frame") + tab.Pack(frame, ui.Pack{ + Side: ui.N, + FillX: true, + PadY: 4, + }) + + label := ui.NewLabel(ui.Label{ + Text: "Or select from a generic script:", + Font: ui.MenuFont, + }) + frame.Pack(label, ui.Pack{ + Side: ui.W, + }) + + // SelectBox for the built-ins. + sb := ui.NewSelectBox("Select", ui.Label{ + Font: ui.MenuFont, + }) + tab.Pack(sb, ui.Pack{ + Side: ui.N, + FillX: true, + }) + + for _, script := range GenericScripts { + sb.AddItem(script.Label, script.Filename, func() {}) + } + sb.SetValue(GenericScripts[0].Filename) + sb.AlwaysChange = true + sb.Handle(ui.Change, func(ed ui.EventData) error { + if selection, ok := sb.GetValue(); ok { + if filename, ok := selection.Value.(string); ok { + // Get this script from the built-in assets. + data, err := assets.Asset(filename) + if err != nil { + shmem.Flash("Couldn't get script: %s", err) + return nil + } + + // Find the data from the builtins. + var label, help string + for _, script := range GenericScripts { + if script.Filename == filename { + label = script.Label + help = script.Help + break + } + } + + // Prompt the user + a description of this option. + var ( + basename = filepath.Base(filename) + description = fmt.Sprintf( + "Do you want to install %s to your doodad?\n\n"+ + "%s\n\n%s", + basename, + label, + help, + ) + ) + + modal.Confirm(description).Then(func() { + c.EditDoodad.Script = string(data) + + shmem.Flash("Attached %s to your doodad", filepath.Base(filename)) + + // Toggle the if/else frames. + ifScript.Show() + elseScript.Hide() + }) + } + } - data, err := ioutil.ReadFile(filename) - if err != nil { - shmem.Flash("Couldn't read file: %s", err) return nil - } + }) - c.EditDoodad.Script = string(data) - shmem.Flash("Attached %d-byte script to this doodad.", len(c.EditDoodad.Script)) - - // Toggle the if/else frames. - ifScript.Show() - elseScript.Hide() - - return nil - }) - c.Supervisor.Add(btnBrowse) - tab.Pack(btnBrowse, ui.Pack{ - Side: ui.N, - Padding: 4, - }) + sb.Supervise(c.Supervisor) + c.Supervisor.Add(sb) + } // Show/hide appropriate frames. if c.EditDoodad.Script == "" {