diff --git a/dev-assets/doodads/build.sh b/dev-assets/doodads/build.sh index d115872..6bc41eb 100755 --- a/dev-assets/doodads/build.sh +++ b/dev-assets/doodads/build.sh @@ -66,6 +66,10 @@ objects() { cd crumbly-floor/ make cd .. + + cd regions/ + make + cd .. } onoff() { diff --git a/dev-assets/doodads/regions/Makefile b/dev-assets/doodads/regions/Makefile new file mode 100644 index 0000000..63b40ad --- /dev/null +++ b/dev-assets/doodads/regions/Makefile @@ -0,0 +1,25 @@ +ALL: build + +.PHONY: build +build: + # Goal Region + doodad convert -t "Goal Region" goal-128.png reg-goal.doodad + doodad install-script goal.js reg-goal.doodad + + # Fire Region + doodad convert -t "Fire Region" fire-128.png reg-fire.doodad + doodad install-script fire.js reg-fire.doodad + + # Stall Region + doodad convert -t "Stall Player (250ms)" stall-128.png reg-stall-250.doodad + doodad edit-doodad --tag "ms=250" reg-stall-250.doodad + doodad install-script stall.js reg-stall-250.doodad + + # Power Source + doodad convert -t "Power Source" power-64.png power-source.doodad + doodad install-script power.js power-source.doodad + + for i in *.doodad; do\ + doodad edit-doodad --tag "category=technical" $${i};\ + done + cp *.doodad ../../../assets/doodads/ \ No newline at end of file diff --git a/dev-assets/doodads/regions/fire-128.png b/dev-assets/doodads/regions/fire-128.png new file mode 100644 index 0000000..117a231 Binary files /dev/null and b/dev-assets/doodads/regions/fire-128.png differ diff --git a/dev-assets/doodads/regions/fire.js b/dev-assets/doodads/regions/fire.js new file mode 100644 index 0000000..aae40c8 --- /dev/null +++ b/dev-assets/doodads/regions/fire.js @@ -0,0 +1,19 @@ +// Goal Region. +function main() { + Self.Hide(); + + Events.OnCollide(function (e) { + if (!e.Settled) { + return; + } + + // Only care if it's the player. + if (!e.Actor.IsPlayer()) { + return; + } + + if (e.InHitbox) { + FailLevel("You have died!"); + } + }); +} diff --git a/dev-assets/doodads/regions/goal-128.png b/dev-assets/doodads/regions/goal-128.png new file mode 100644 index 0000000..b769917 Binary files /dev/null and b/dev-assets/doodads/regions/goal-128.png differ diff --git a/dev-assets/doodads/regions/goal.js b/dev-assets/doodads/regions/goal.js new file mode 100644 index 0000000..1d06f8f --- /dev/null +++ b/dev-assets/doodads/regions/goal.js @@ -0,0 +1,19 @@ +// Goal Region. +function main() { + Self.Hide(); + + Events.OnCollide(function (e) { + if (!e.Settled) { + return; + } + + // Only care if it's the player. + if (!e.Actor.IsPlayer()) { + return; + } + + if (e.InHitbox) { + EndLevel(); + } + }); +} diff --git a/dev-assets/doodads/regions/power-128.png b/dev-assets/doodads/regions/power-128.png new file mode 100644 index 0000000..fcdab41 Binary files /dev/null and b/dev-assets/doodads/regions/power-128.png differ diff --git a/dev-assets/doodads/regions/power-64.png b/dev-assets/doodads/regions/power-64.png new file mode 100644 index 0000000..cfe47f5 Binary files /dev/null and b/dev-assets/doodads/regions/power-64.png differ diff --git a/dev-assets/doodads/regions/power.js b/dev-assets/doodads/regions/power.js new file mode 100644 index 0000000..072a526 --- /dev/null +++ b/dev-assets/doodads/regions/power.js @@ -0,0 +1,22 @@ +// Power source. +// Emits a power(true) signal once on level start. +// If it receives a power signal, it will repeat it after 5 seconds. +// Link two of these bad boys together and you got yourself a clock. +function main() { + Self.Hide(); + + // See if we are not linked to anything. + var links = Self.GetLinks(); + if (links.length === 0) { + console.error( + "%s at %s is not linked to anything! This doodad emits a power(true) on level start to all linked doodads.", + Self.Title, + Self.Position() + ); + } + + Message.Subscribe("broadcast:ready", function () { + Message.Publish("switch:toggle", true); + Message.Publish("power", true); + }); +} diff --git a/dev-assets/doodads/regions/stall-128.png b/dev-assets/doodads/regions/stall-128.png new file mode 100644 index 0000000..ea0bd5e Binary files /dev/null and b/dev-assets/doodads/regions/stall-128.png differ diff --git a/dev-assets/doodads/regions/stall.js b/dev-assets/doodads/regions/stall.js new file mode 100644 index 0000000..c174594 --- /dev/null +++ b/dev-assets/doodads/regions/stall.js @@ -0,0 +1,40 @@ +// Stall Player. +// Tags: ms (int) +// Grabs the player one time. Resets if it receives power. +function main() { + Self.Hide(); + + var active = true, + timeout = 250, + ms = Self.GetTag("ms"); + + if (ms.length > 0) { + timeout = parseInt(ms); + } + + Events.OnCollide(function (e) { + if (!active || !e.Settled) { + return; + } + + // Only care if it's the player. + if (!e.Actor.IsPlayer()) { + return; + } + + if (e.InHitbox) { + // Grab hold of the player. + e.Actor.Freeze(); + setTimeout(function () { + e.Actor.Unfreeze(); + }, timeout); + + active = false; + } + }); + + // Reset the trap if powered by a button. + Message.Subscribe("power", function (powered) { + active = true; + }); +} diff --git a/pkg/uix/canvas_actors.go b/pkg/uix/canvas_actors.go index 32ad18a..ffdb9c8 100644 --- a/pkg/uix/canvas_actors.go +++ b/pkg/uix/canvas_actors.go @@ -83,6 +83,15 @@ func (w *Canvas) InstallScripts() error { } } + // Broadcast the "ready" signal to any actors that want to publish + // messages ASAP on level start. + for _, actor := range w.actors { + w.scripting.To(actor.ID()).Inbound <- scripting.Message{ + Name: "broadcast:ready", + Args: nil, + } + } + return nil } diff --git a/pkg/uix/scripting.go b/pkg/uix/scripting.go index cbb5ab2..3ca4daf 100644 --- a/pkg/uix/scripting.go +++ b/pkg/uix/scripting.go @@ -43,6 +43,8 @@ func (w *Canvas) MakeSelfAPI(actor *Actor) map[string]interface{} { "HasItem": actor.HasItem, "ClearInventory": actor.ClearInventory, "Destroy": actor.Destroy, + "Hide": actor.Hide, + "Show": actor.Show, "GetLinks": func() []map[string]interface{} { var result = []map[string]interface{}{} for _, linked := range w.GetLinkedActors(actor) { diff --git a/pkg/windows/doodad_dropper.go b/pkg/windows/doodad_dropper.go index f6d7e47..530db4a 100644 --- a/pkg/windows/doodad_dropper.go +++ b/pkg/windows/doodad_dropper.go @@ -91,6 +91,7 @@ func NewDoodadDropper(config DoodadDropper) *ui.Window { {"doors", "Doors"}, {"gizmos", "Gizmos"}, {"creatures", "Creatures"}, + {"technical", "Technical"}, {"", "All"}, } for _, category := range categories { @@ -114,6 +115,10 @@ func makeDoodadTab(config DoodadDropper, frame *ui.Frame, size render.Rect, cate columns = balance.DoodadDropperCols rows = balance.DoodadDropperRows + // Count how many doodad buttons we need vs. how many can fit. + iconsDrawn int + iconsPossible = columns * rows + // pagination values page = 1 pages int @@ -144,6 +149,50 @@ func makeDoodadTab(config DoodadDropper, frame *ui.Frame, size render.Rect, cate ), ) + // First, draw the empty grid of inset frames to serve as the 'background' + // of the drawer. This both serves an aesthetic purpose and reserves space + // in the widget for short page views. + { + var ( + decorFrame = ui.NewFrame("Background Slots") + row *ui.Frame + ) + + for i := 0; i < iconsPossible; i++ { + if row == nil || i%columns == 0 { + row = ui.NewFrame("BG Row") + decorFrame.Pack(row, ui.Pack{ + Side: ui.N, + }) + } + + spacer := ui.NewFrame("Spacer") + spacer.Configure(ui.Config{ + BorderSize: 2, + BorderStyle: ui.BorderSunken, + Background: render.Grey.Darken(20), + }) + spacer.Resize(render.NewRect( + buttonSize-2, // TODO: without the -2 the button border + buttonSize-2, // rests on top of the window border + )) + spacer.Compute(config.Engine) + row.Pack(spacer, ui.Pack{ + Side: ui.W, + }) + } + + decorFrame.Compute(config.Engine) + + // frame.Pack(decorFrame, ui.Pack{ + // Side: ui.NW, + // }) + frame.Place(decorFrame, ui.Place{ + Top: 0, + Left: 0, + }) + } + // Draw the doodad buttons in rows. var btnRows = []*ui.Frame{} { @@ -151,7 +200,8 @@ func makeDoodadTab(config DoodadDropper, frame *ui.Frame, size render.Rect, cate row *ui.Frame rowCount int // for labeling the ui.Frame for each row - // TODO: pre-size btnRows by calculating how many needed + // the state we end up at when we exhaust all doodads + lastColumn int // last position in current row ) for i, doodad := range items { @@ -162,17 +212,21 @@ func makeDoodadTab(config DoodadDropper, frame *ui.Frame, size render.Rect, cate rowCount++ row = ui.NewFrame(fmt.Sprintf("Doodad Row %d", rowCount)) - row.SetBackground(balance.DoodadButtonBackground) + + row.Resize(render.NewRect(size.W, buttonSize)) + row.Compute(config.Engine) btnRows = append(btnRows, row) frame.Pack(row, ui.Pack{ Side: ui.N, - // Fill: true, }) - // Hide overflowing rows until we scroll to them. + // Hide overflowing rows until we page to them. if hidden { row.Hide() } + + // New row, new columns. + lastColumn = 0 } can := uix.NewCanvas(int(buttonSize), true) @@ -213,6 +267,62 @@ func makeDoodadTab(config DoodadDropper, frame *ui.Frame, size render.Rect, cate )) btn.Compute(config.Engine) + + iconsDrawn++ + lastColumn++ + } + + // If we have fewer doodad icons than this page can hold, + // fill out dummy placeholder cells to maintain the UI shape. + // TODO: this is very redundant compared to the ATTEMPT above + // to only do this once. It seems our background widget doesn't + // size up the full tab height properly, so doodad tabs that + // have fewer than one page worth (short first page) the sizing + // was wrong. The below hack pads out the screen for short first + // pages only. There is still a bug with short LAST pages where + // it doesn't hold height and the pager buttons come up. + if iconsDrawn < iconsPossible { + for i := lastColumn; i < iconsPossible; i++ { + if row == nil || i%columns == 0 { + var hidden = rowCount >= rows + rowCount++ + + row = ui.NewFrame(fmt.Sprintf("Doodad Row %d", rowCount)) + row.SetBackground(balance.DoodadButtonBackground) + btnRows = append(btnRows, row) + frame.Pack(row, ui.Pack{ + Side: ui.N, + }) + + // Hide overflowing rows until we page to them. + if hidden { + row.Hide() + } + } + + spacer := ui.NewFrame("Spacer") + spacer.Configure(ui.Config{ + BorderSize: 2, + BorderStyle: ui.BorderSunken, + Background: render.Grey, + }) + spacer.Resize(render.NewRect( + buttonSize-2, // TODO: without the -2 the button border + buttonSize-2, // rests on top of the window border + )) + spacer.Compute(config.Engine) + row.Pack(spacer, ui.Pack{ + Side: ui.W, + }) + + // debug + // lbl := ui.NewLabel(ui.Label{ + // Text: fmt.Sprintf("i=%d\nrow=%d", i, rowCount), + // }) + // spacer.Pack(lbl, ui.Pack{ + // Side: ui.NW, + // }) + } } } diff --git a/scripts/fpm-bundle-32bit.sh b/scripts/fpm-bundle-32bit.sh new file mode 100755 index 0000000..5ed3de8 --- /dev/null +++ b/scripts/fpm-bundle-32bit.sh @@ -0,0 +1,88 @@ +#!/bin/bash + +# fpm-bundle: create bundles for the app. + +# Add the user-level "gem install fpm" to the $PATH. +# Might need fixing over time. +export PATH="$PATH:$HOME/.local/share/gem/ruby/3.0.0/bin" + +INSTALL_ROOT="/opt/sketchy-maze" +LAUNCHER_FILENAME="etc/linux/net.kirsle.ProjectDoodle.desktop" +LAUNCHER_ROOT="/usr/share/applications" # Where the .desktop file goes. +ICON_ROOT="/usr/share/icons/hicolor/" + +# Find out how many levels up we need to go, so this +# script can run from either of these locations: +# ./dist/sketchymaze-$version/ +# ./dist/stage/$version/linux/ +UPLEVELS="." +if [[ -f "../../${LAUNCHER_FILENAME}" ]]; then + # run from a ./dist/x folder. + UPLEVELS="../.." +elif [[ -f "../../../../${LAUNCHER_FILENAME}" ]]; then + # run from a release stage folder + UPLEVELS="../../../.." +else + echo Did not find ${LAUNCHER_FILENAME} relative to your working directory. + echo Good places to run this script include: + echo " * ./dist/sketchymaze-\$version/ (as in 'make dist')" + echo " * ./dist/stage/\$version/linux/ (as in 'make release')" + exit 1 +fi + +VERSION=`egrep -e 'Version\s+=' ${UPLEVELS}/pkg/branding/branding.go | head -n 1 | cut -d '"' -f 2` +LAUNCHER_FILE="${UPLEVELS}/${LAUNCHER_FILENAME}" + +if [[ ! -f "./sketchymaze" ]]; then + echo Run this script from the directory containing the Doodle binary. + echo This is usually at /dist/doodle-VERSION/ relative to the git root. + exit 1 +fi + +if [[ ! -f "$LAUNCHER_FILE" ]]; then + echo "Didn't find Linux desktop launcher relative to current folder." + echo "I looked at $LAUNCHER_FILE." + exit 1 +fi + +# Clean previous artifacts. +rm *.rpm *.deb + +# Create the root structure. +mkdir -p root +mkdir -p root$INSTALL_ROOT root$LAUNCHER_ROOT +cp * root$INSTALL_ROOT/ +cp $LAUNCHER_FILE root$LAUNCHER_ROOT/ + +# Copy icons in. +mkdir -p root$ICON_ROOT/{256x256,128x128,64x64,32x32,16x16}/apps +cp ${UPLEVELS}/etc/icons/256.png "root${ICON_ROOT}256x256/apps/project-doodle.png" +cp ${UPLEVELS}/etc/icons/128.png "root${ICON_ROOT}128x128/apps/project-doodle.png" +cp ${UPLEVELS}/etc/icons/64.png "root$ICON_ROOT/64x64/apps/project-doodle.png" +cp ${UPLEVELS}/etc/icons/32.png "root$ICON_ROOT/32x32/apps/project-doodle.png" +cp ${UPLEVELS}/etc/icons/16.png "root$ICON_ROOT/16x16/apps/project-doodle.png" + +# Copy runtime package and guidebook +cp -r guidebook rtp "root$INSTALL_ROOT/" + +echo ===================== +echo Starting fpm package build. +echo ===================== + +# RPM Package +fpm -C ./root -s dir -t rpm \ + -d SDL2 -d SDL2_ttf -a i386 \ + -n sketchy-maze -v ${VERSION} \ + --license="Copyright" \ + --maintainer=noah@kirsle.net \ + --description="Sketchy Maze - A drawing-based maze game." \ + --url="https://www.sketchymaze.com" + +# Debian Package +fpm -C ./root -s dir -t deb \ + -d libsdl2 -d libsdl2-ttf -a i386 \ + -n sketchy-maze -v ${VERSION} \ + --license="Copyright" \ + --maintainer=noah@kirsle.net \ + --description="Sketchy Maze - A drawing-based maze game." \ + --url="https://www.sketchymaze.com"