From 220a87d9d12f0705dd36a9d5300046cf0273db86 Mon Sep 17 00:00:00 2001 From: Noah Petherbridge Date: Sun, 9 Oct 2022 17:44:27 -0700 Subject: [PATCH] Doodad/Actor Runtime Options * Add "Options" support for Doodads: these allow for individual Actor instances on your level to customize properties about the doodad. They're like "Tags" except the player can customize them on a per-actor basis. * Doodad Editor: you can specify the Options in the Doodad Properties window. * Level Editor: when the Actor Tool is selected, on mouse-over of an actor, clicking on the gear icon will open a new "Actor Properties" window which shows metadata (title, author, ID, position) and an Options tab to configure the actor's options. Updates to the scripting API: * Self.Options() returns a list of option names defined on the Doodad. * Self.GetOption(name) returns the value for the named option, or nil if neither the actor nor its doodad have the option defined. The return type will be correctly a string, boolean or integer type. Updates to the doodad command-line tool: * `doodad show` will print the Options on a .doodad file and, when showing a .level file with --actors, prints any customized Options with the actors. * `doodad edit-doodad` adds a --option parameter to define options. Options added to the game's built-in doodads: * Warp Doors: "locked (exit only)" will make it so the door can not be opened by the player, giving the "locked" message (as if it had no linked door), but the player may still exit from the door if sent by another warp door. * Electric Door & Electric Trapdoor: "opened" can make the door be opened by default when the level begins instead of closed. A switch or a button that removes power will close the door as normal. * Colored Doors & Small Key Door: "unlocked" will make the door unlocked at level start, not requiring a key to open it. * Colored Keys & Small Key: "has gravity" will make the key subject to gravity and set its Mobile flag so that if it falls onto a button, it will activate. * Gemstones: they had gravity by default; you can now uncheck "has gravity" to remove their Gravity and IsMobile status. * Gemstone Totems: "has gemstone" will set the totem to its unlocked status by default with the gemstone inserted. No power signal will be emitted; it is cosmetic only. * Fire Region: "name" can let you set a name for the fire region similarly to names for fire pixels: "Watch out for ${name}!" * Invisible Warp Door: "locked (exit only)" added as well. --- doors/build.sh | 15 +++++++++++++++ doors/colored-door.js | 20 ++++++++++++++++++-- doors/electric-door.js | 22 ++++++++++++++++++---- doors/keys.js | 14 ++++++++++++-- gems/Makefile | 11 +++++++++++ gems/gemstone.js | 9 ++++++--- gems/totem.js | 9 ++++++++- options_test.js | 15 +++++++++++++++ regions/Makefile | 3 ++- regions/fire.js | 6 ++++-- trapdoors/Makefile | 2 +- trapdoors/electric-trapdoor.js | 8 +++++++- warp-door/Makefile | 2 +- warp-door/warp-door.js | 17 ++++++++++++++--- 14 files changed, 132 insertions(+), 21 deletions(-) create mode 100644 options_test.js diff --git a/doors/build.sh b/doors/build.sh index 7615a8d..d38ef5b 100755 --- a/doors/build.sh +++ b/doors/build.sh @@ -43,8 +43,23 @@ doodad edit-doodad -q --tag color=small small-key.doodad doodad install-script keys.js small-key.doodad doodad convert -t "Electric Door" electric{1,2,3,4}.png door-electric.doodad +doodad edit-doodad -q --option "opened=bool" door-electric.doodad doodad install-script electric-door.js door-electric.doodad +# All locked doors have an option to unlock. +doodad edit-doodad -q --option "unlocked=bool" door-green.doodad +doodad edit-doodad -q --option "unlocked=bool" door-blue.doodad +doodad edit-doodad -q --option "unlocked=bool" door-yellow.doodad +doodad edit-doodad -q --option "unlocked=bool" door-red.doodad +doodad edit-doodad -q --option "unlocked=bool" small-key-door.doodad + +# All keys may be subject to gravity. +doodad edit-doodad -q --option "has gravity=bool" key-green.doodad +doodad edit-doodad -q --option "has gravity=bool" key-blue.doodad +doodad edit-doodad -q --option "has gravity=bool" key-yellow.doodad +doodad edit-doodad -q --option "has gravity=bool" key-red.doodad +doodad edit-doodad -q --option "has gravity=bool" small-key.doodad + # Tag the category for these doodads for i in *.doodad; do doodad edit-doodad --tag "category=doors" $i; done doodad edit-doodad --tag "category=doors,gizmos" door-electric.doodad diff --git a/doors/colored-door.js b/doors/colored-door.js index 1c47ab6..b529d57 100644 --- a/doors/colored-door.js +++ b/doors/colored-door.js @@ -1,7 +1,17 @@ -// Colored Locked Doors. +/* +Colored Locked Doors + +This script handles the blue, green, red, yellow and small-key doors. + +Each door has a corresponding key that will unlock it. Small Key doors consume a +small key when unlocked for the first time. + +Options: "unlocked" can make the door unlocked by default when the level begins. +*/ const color = Self.GetTag("color"), - keyname = color === "small" ? "small-key.doodad" : "key-" + color + ".doodad"; + keyname = color === "small" ? "small-key.doodad" : "key-" + color + ".doodad", + isUnlocked = Self.GetOption("unlocked"); function main() { // Layers in the doodad image. @@ -19,6 +29,12 @@ function main() { Self.SetHitbox(34, 0, 13, 76); + // Options: door is unlocked at level start? + if (isUnlocked) { + unlocked = true; + Self.ShowLayer(layer.unlocked); + } + Events.OnCollide((e) => { // Record the side that this actor has touched us, in case the door // needs to open. diff --git a/doors/electric-door.js b/doors/electric-door.js index 5c35201..07e274b 100644 --- a/doors/electric-door.js +++ b/doors/electric-door.js @@ -1,8 +1,16 @@ -// Electric Door +/* +Electric Door -let animating = false; -let opened = false; -let powerState = false; +Opens when it receives power. Closes when power is removed. + +Always toggles when activated by a switch. + +Options: opened (bool) to make it opened by default on level start. +*/ + +let animating = false, + opened = Self.GetOption("opened") === true, + powerState = false; // Function to handle the door opening or closing. function setPoweredState(powered) { @@ -35,6 +43,12 @@ function main() { Self.SetHitbox(0, 0, 34, 76); + // If the player has configured the door to be opened by default, make it so. + if (opened) { + powerState = true; + Self.ShowLayer(3); + } + // A linked Switch that activates the door will send the Toggle signal // immediately before the Power signal. The door can just invert its // state on this signal, and ignore the very next Power signal. Ordinary diff --git a/doors/keys.js b/doors/keys.js index c51154d..23be9fe 100644 --- a/doors/keys.js +++ b/doors/keys.js @@ -1,9 +1,19 @@ -// Colored Keys and Small Key +/* +Colored Keys and Small Key + +Options: "has gravity" will make the key subject to gravity. +*/ const color = Self.GetTag("color"), - quantity = color === "small" ? 1 : 0; + quantity = color === "small" ? 1 : 0, + hasGravity = Self.GetOption("has gravity") === true; function main() { + if (hasGravity) { + Self.SetGravity(hasGravity); + Self.SetMobile(true); + } + Events.OnCollide((e) => { if (e.Settled) { if (e.Actor.HasInventory()) { diff --git a/gems/Makefile b/gems/Makefile index f9e5d87..1765147 100644 --- a/gems/Makefile +++ b/gems/Makefile @@ -55,4 +55,15 @@ build: doodad edit-doodad --tag "category=doors" $${i};\ done + # Gemstones have gravity by default, but make it configurable. + doodad edit-doodad -q --option "has gravity=bool=true" gem-green.doodad + doodad edit-doodad -q --option "has gravity=bool=true" gem-yellow.doodad + doodad edit-doodad -q --option "has gravity=bool=true" gem-red.doodad + doodad edit-doodad -q --option "has gravity=bool=true" gem-blue.doodad + + # Totems have an option to mark them unlocked by default. + for i in gem-totem-*.doodad; do\ + doodad edit-doodad -q --option "has gemstone=bool" $${i};\ + done + cp *.doodad ../../../assets/doodads/ \ No newline at end of file diff --git a/gems/gemstone.js b/gems/gemstone.js index efb9361..73ec353 100644 --- a/gems/gemstone.js +++ b/gems/gemstone.js @@ -1,11 +1,14 @@ // Gem stone collectibles/keys. const color = Self.GetTag("color"), - shimmerFreq = 1000; + shimmerFreq = 1000, + hasGravity = Self.GetOption("has gravity") === true; function main() { - Self.SetMobile(true); - Self.SetGravity(true); + if (hasGravity) { + Self.SetMobile(true); + Self.SetGravity(true); + } Self.AddAnimation("shimmer", 100, [0, 1, 2, 3, 0]); Events.OnCollide((e) => { diff --git a/gems/totem.js b/gems/totem.js index 3598f50..be526e8 100644 --- a/gems/totem.js +++ b/gems/totem.js @@ -20,7 +20,8 @@ let color = Self.GetTag("color"), activated = false, linkedReceiver = false, // is linked to a non-totem which might want power totems = {}, // linked totems - shimmerFreq = 1000; + shimmerFreq = 1000, + isUnlocked = Self.GetOption("has gemstone") === true; function main() { // Show the hollow socket on level load (last layer) @@ -39,6 +40,12 @@ function main() { // are the filled socket sprites. Self.AddAnimation("shimmer", 100, [0, 1, 2, 3, 0]); + // Options: if it already has its gemstone, set its state accordingly. + if (isUnlocked) { + activated = true; + Self.ShowLayer(0); + } + Events.OnCollide((e) => { if (activated) return; diff --git a/options_test.js b/options_test.js new file mode 100644 index 0000000..3a84128 --- /dev/null +++ b/options_test.js @@ -0,0 +1,15 @@ +// Test doodad script for Options. +function main() { + console.error("OptionsTest Doodad: %s (%s)", Self.Title, Self.ID()); + const options = Self.Options(); + for (let item of options) { + let value = Self.GetOption(item); + console.log("Option %s = %+v", item, value); + + if (value === true) { + console.log("It is a true boolean!"); + } else if (value === false) { + console.log("It is a false boolean!"); + } + } +} \ No newline at end of file diff --git a/regions/Makefile b/regions/Makefile index 890ecf4..afdd10c 100644 --- a/regions/Makefile +++ b/regions/Makefile @@ -13,6 +13,7 @@ build: # Fire Region doodad convert -t "Fire Region" fire-128.png reg-fire.doodad doodad install-script fire.js reg-fire.doodad + doodad edit-doodad -q --option "name=str=fire" reg-fire.doodad # Stall Region doodad convert -t "Stall Player (250ms)" stall-128.png reg-stall-250.doodad @@ -29,7 +30,7 @@ build: # Warp Door doodad convert -t "Invisible Warp Door" warp-door-64.png reg-warp-door.doodad - doodad edit-doodad --tag "color=invisible" reg-warp-door.doodad + doodad edit-doodad --tag "color=invisible" --option "locked (exit only)=bool" reg-warp-door.doodad doodad install-script ../warp-door/warp-door.js reg-warp-door.doodad # Reset Level Timer diff --git a/regions/fire.js b/regions/fire.js index 977537a..452e53d 100644 --- a/regions/fire.js +++ b/regions/fire.js @@ -1,4 +1,6 @@ -// Goal Region. +// Fire Region +const name = Self.GetOption("name"); + function main() { Self.Hide(); @@ -13,7 +15,7 @@ function main() { } if (e.InHitbox) { - FailLevel("You have died!"); + FailLevel(`Watch out for ${name}!`); } }); } diff --git a/trapdoors/Makefile b/trapdoors/Makefile index 596c1b2..718118c 100644 --- a/trapdoors/Makefile +++ b/trapdoors/Makefile @@ -29,6 +29,6 @@ build: # Build the Electric Trapdoor. doodad convert -t "Electric Trapdoor" electric{1,2,3,4}.png electric-trapdoor.doodad doodad install-script electric-trapdoor.js electric-trapdoor.doodad - doodad edit-doodad -q --tag "category=doors,gizmos" electric-trapdoor.doodad + doodad edit-doodad -q --tag "category=doors,gizmos" --option "opened=bool" electric-trapdoor.doodad cp *.doodad ../../../assets/doodads/ diff --git a/trapdoors/electric-trapdoor.js b/trapdoors/electric-trapdoor.js index 836f662..8f69dc9 100644 --- a/trapdoors/electric-trapdoor.js +++ b/trapdoors/electric-trapdoor.js @@ -3,7 +3,7 @@ var animationSpeed = 100, spriteWidth = 114, thickness = 7, - isOpen = false, + isOpen = Self.GetOption("opened") === true, animating = false, powerState = false; @@ -13,6 +13,12 @@ function main() { Self.AddAnimation("open", animationSpeed, [0, 1, 2, 3]); Self.AddAnimation("close", animationSpeed, [3, 2, 1, 0]); + // Options: if the player marked it "opened" make it open by default. + if (isOpen) { + Self.ShowLayer(3); + powerState = true; + } + // Subscribe to Switches and other power sources. Note: if a // switch toggles us, we ignore the immediately following // power signal which will be coming from the same switch. diff --git a/warp-door/Makefile b/warp-door/Makefile index 90d80b2..f541d65 100644 --- a/warp-door/Makefile +++ b/warp-door/Makefile @@ -17,7 +17,7 @@ build: doodad install-script warp-door.js warp-door-orange.doodad for i in *.doodad; do\ - doodad edit-doodad --tag "category=doors" --hitbox=34,76 $${i};\ + doodad edit-doodad --tag "category=doors" --option "locked (exit only)=bool" --hitbox=34,76 $${i};\ done for i in warp-door-*.doodad; do\ doodad edit-doodad --tag "category=doors,gizmos" $${i};\ diff --git a/warp-door/warp-door.js b/warp-door/warp-door.js index 2bde9f3..0e1965c 100644 --- a/warp-door/warp-door.js +++ b/warp-door/warp-door.js @@ -1,7 +1,18 @@ -// Warp Doors +/* +Warp Doors + +Link it to another Warp Door and the player can travel between them. + +Doors without links will behave as 'locked' doors and cannot be opened. + +Options: "locked (exit only)" bool will make the door behave as locked +even if linked - it can be an exit-only door that the player can come +out of but can not open. +*/ const color = Self.GetTag("color"), - isStateDoor = color === 'blue' || color === 'orange'; + isStateDoor = color === 'blue' || color === 'orange', + isLocked = Self.GetOption("locked (exit only)") === true; // State in case we're a blue warp door. let state = color === 'blue', @@ -63,7 +74,7 @@ function main() { } // Doors without linked exits are not usable. - if (linkedDoor === null) { + if (linkedDoor === null || isLocked) { if (!flashedCooldown) { Flash("This door is locked."); flashedCooldown = true;