diff --git a/assets/scripts/generic-anvil.js b/assets/scripts/generic-anvil.js index 5caa7dc..369e5b6 100644 --- a/assets/scripts/generic-anvil.js +++ b/assets/scripts/generic-anvil.js @@ -10,8 +10,10 @@ 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) + if (Self.Hitbox().IsZero()) { + 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); diff --git a/assets/scripts/generic-fire.js b/assets/scripts/generic-fire.js index aad0fa7..5b4f36d 100644 --- a/assets/scripts/generic-fire.js +++ b/assets/scripts/generic-fire.js @@ -9,8 +9,10 @@ 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) + if (Self.Hitbox().IsZero()) { + var size = Self.Size() + Self.SetHitbox(0, 0, size.W, size.H) + } Events.OnCollide(function (e) { if (!e.Settled || !e.InHitbox) { diff --git a/assets/scripts/generic-item.js b/assets/scripts/generic-item.js new file mode 100644 index 0000000..aaf3922 --- /dev/null +++ b/assets/scripts/generic-item.js @@ -0,0 +1,35 @@ +// Generic Item Script +/* +A script that makes your item pocket-able, like the Keys. + +Your doodad sprite will appear in the Inventory menu if the +player picks it up. + +Configure it with tags: +- quantity: integer quantity value, default is 1, + set to 0 to make it a 'key item' + +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. + if (Self.Hitbox().IsZero()) { + var size = Self.Size() + Self.SetHitbox(0, 0, size.W, size.H) + } + + var qtySetting = Self.GetTag("quantity") + var quantity = qtySetting === "" ? 1 : parseInt(qtySetting); + + Events.OnCollide(function (e) { + if (e.Settled) { + if (e.Actor.HasInventory()) { + Sound.Play("item-get.wav") + e.Actor.AddItem(Self.Filename, quantity); + Self.Destroy(); + } + } + }) +} diff --git a/assets/scripts/generic-solid.js b/assets/scripts/generic-solid.js index 49cd62d..154bd2b 100644 --- a/assets/scripts/generic-solid.js +++ b/assets/scripts/generic-solid.js @@ -9,8 +9,10 @@ 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) + if (Self.Hitbox().IsZero()) { + var size = Self.Size() + Self.SetHitbox(0, 0, size.W, size.H) + } // Solid to all collisions. Events.OnCollide(function (e) { diff --git a/pkg/doodads/doodad.go b/pkg/doodads/doodad.go index b95f511..10d07ad 100644 --- a/pkg/doodads/doodad.go +++ b/pkg/doodads/doodad.go @@ -14,6 +14,7 @@ type Doodad struct { Hidden bool `json:"hidden,omitempty"` Palette *level.Palette `json:"palette"` Script string `json:"script"` + Hitbox render.Rect `json:"hitbox"` Layers []Layer `json:"layers"` Tags map[string]string `json:"data"` // arbitrary key/value data storage } @@ -35,6 +36,7 @@ func New(size int) *Doodad { Version: 1, }, Palette: level.DefaultPalette(), + Hitbox: render.NewRect(size, size), Layers: []Layer{ { Name: "main", diff --git a/pkg/uix/actor.go b/pkg/uix/actor.go index 2b0643a..9852b90 100644 --- a/pkg/uix/actor.go +++ b/pkg/uix/actor.go @@ -326,8 +326,12 @@ func (a *Actor) SetHitbox(x, y, w, h int) { } } -// Hitbox returns the actor's elected hitbox. +// Hitbox returns the actor's elected hitbox. If the JavaScript did not set +// a hitbox, it defers to the Doodad's metadata hitbox. func (a *Actor) Hitbox() render.Rect { + if a.hitbox.IsZero() && !a.Drawing.Doodad.Hitbox.IsZero() { + return a.Drawing.Doodad.Hitbox + } return a.hitbox } diff --git a/pkg/uix/scripting.go b/pkg/uix/scripting.go index f4f3cf5..cbb5ab2 100644 --- a/pkg/uix/scripting.go +++ b/pkg/uix/scripting.go @@ -24,6 +24,7 @@ func (w *Canvas) MakeSelfAPI(actor *Actor) map[string]interface{} { actor.SetGrounded(false) }, "SetHitbox": actor.SetHitbox, + "Hitbox": actor.Hitbox, "SetVelocity": actor.SetVelocity, "SetMobile": actor.SetMobile, "SetInventory": actor.SetInventory, diff --git a/pkg/windows/doodad_properties.go b/pkg/windows/doodad_properties.go index 2837c96..d7eac6e 100644 --- a/pkg/windows/doodad_properties.go +++ b/pkg/windows/doodad_properties.go @@ -6,6 +6,8 @@ import ( "os" "path/filepath" "sort" + "strconv" + "strings" "git.kirsle.net/apps/doodle/assets" "git.kirsle.net/apps/doodle/pkg/balance" @@ -14,6 +16,7 @@ import ( "git.kirsle.net/apps/doodle/pkg/modal" "git.kirsle.net/apps/doodle/pkg/native" "git.kirsle.net/apps/doodle/pkg/shmem" + "git.kirsle.net/apps/doodle/pkg/userdir" "git.kirsle.net/go/render" "git.kirsle.net/go/ui" ) @@ -23,6 +26,7 @@ var GenericScripts = []struct { Label string Help string Filename string + SetTags map[string]string }{ { Label: "Generic Solid", @@ -47,6 +51,16 @@ var GenericScripts = []struct { "'Watch out for (title)!'", Filename: "assets/scripts/generic-anvil.js", }, + { + Label: "Generic Collectible Item", + Help: "This doodad will behave like a pocketable item, like\n" + + "the Keys. Tip: set a Doodad tag like quantity=0 to set\n" + + "the item quantity when picked up (default is 1).", + Filename: "assets/scripts/generic-item.js", + SetTags: map[string]string{ + "quantity": "1", + }, + }, } // DoodadProperties window. @@ -120,8 +134,10 @@ func (c DoodadProperties) makeMetaTab(tabFrame *ui.TabFrame, Width, Height int) ////////////// // Draw the editable metadata form. + var hitboxString = c.EditDoodad.Hitbox.String() for _, data := range []struct { Label string + Prompt string // optional Variable *string Update func(string) }{ @@ -139,6 +155,40 @@ func (c DoodadProperties) makeMetaTab(tabFrame *ui.TabFrame, Width, Height int) c.EditDoodad.Author = v }, }, + { + Label: "Hitbox:", + Prompt: "Enter hitbox in X,Y,W,H or just W,H format: ", + Variable: &hitboxString, + Update: func(v string) { + // Parse it. + parts := strings.Split(v, ",") + var ints []int + for _, part := range parts { + a, err := strconv.Atoi(strings.TrimSpace(part)) + if err != nil { + shmem.Flash("Invalid format for hitbox, using the default") + return + } + ints = append(ints, a) + } + + if len(ints) == 2 { + c.EditDoodad.Hitbox = render.NewRect(ints[0], ints[1]) + } else if len(ints) == 4 { + c.EditDoodad.Hitbox = render.Rect{ + X: ints[0], + Y: ints[1], + W: ints[2], + H: ints[3], + } + } else { + shmem.Flash("Hitbox should be in X,Y,W,H or just W,H format, 2 or 4 numbers.") + return + } + + hitboxString = c.EditDoodad.Hitbox.String() + }, + }, } { data := data frame := ui.NewFrame("Metadata " + data.Label + " Frame") @@ -166,7 +216,12 @@ func (c DoodadProperties) makeMetaTab(tabFrame *ui.TabFrame, Width, Height int) Font: balance.MenuFont, })) btn.Handle(ui.Click, func(ed ui.EventData) error { - shmem.Prompt("Enter a new "+data.Label+" ", func(answer string) { + var prompt = data.Prompt + if prompt == "" { + prompt = "Enter a new " + data.Label + ": " + } + + shmem.Prompt(prompt, func(answer string) { if answer != "" { data.Update(answer) } @@ -236,13 +291,23 @@ func (c DoodadProperties) makeMetaTab(tabFrame *ui.TabFrame, Width, Height int) PadX: 2, }) - // Save Button - saveBtn := ui.NewButton("Save", ui.NewLabel(ui.Label{ - Text: "Save", + // Open Button + saveBtn := ui.NewButton("Open", ui.NewLabel(ui.Label{ + Text: "View", Font: balance.MenuFont, })) saveBtn.SetStyle(&balance.ButtonPrimary) saveBtn.Handle(ui.Click, func(ed ui.EventData) error { + // Write the js file to cache and try and open it in the user's + // native text editor program. + outname := filepath.Join(userdir.CacheDirectory, c.EditDoodad.Filename+".js") + err := ioutil.WriteFile(outname, []byte(c.EditDoodad.Script), 0644) + if err == nil { + native.OpenLocalURL(outname) + return nil + } + + // Otherwise, prompt the user for their filepath. shmem.Prompt("Save script as (*.js): ", func(answer string) { if answer != "" { cwd, _ := os.Getwd() @@ -251,6 +316,7 @@ func (c DoodadProperties) makeMetaTab(tabFrame *ui.TabFrame, Width, Height int) shmem.Flash(err.Error()) } else { shmem.Flash("Written to: %s (%d bytes)", filepath.Join(cwd, answer), len(c.EditDoodad.Script)) + native.OpenLocalURL(filepath.Join(cwd, answer)) } } }) @@ -383,10 +449,12 @@ func (c DoodadProperties) makeMetaTab(tabFrame *ui.TabFrame, Width, Height int) // Find the data from the builtins. var label, help string + var setTags map[string]string for _, script := range GenericScripts { if script.Filename == filename { label = script.Label help = script.Help + setTags = script.SetTags break } } @@ -408,6 +476,14 @@ func (c DoodadProperties) makeMetaTab(tabFrame *ui.TabFrame, Width, Height int) shmem.Flash("Attached %s to your doodad", filepath.Base(filename)) + // Set any tags that come with this script. + if setTags != nil && len(setTags) > 0 { + for k, v := range setTags { + log.Info("Set doodad tag %s=%s", k, v) + c.EditDoodad.Tags[k] = v + } + } + // Toggle the if/else frames. ifScript.Show() elseScript.Hide()