diff --git a/dev-assets/doodads/bird/Makefile b/dev-assets/doodads/bird/Makefile index e70fedc..d0ff88d 100644 --- a/dev-assets/doodads/bird/Makefile +++ b/dev-assets/doodads/bird/Makefile @@ -2,9 +2,17 @@ ALL: build .PHONY: build build: - doodad convert -t "Bird (red)" left-1.png left-2.png right-1.png right-2.png \ - dive-left.png dive-right.png bird-red.doodad + doodad convert -t "Bird (red)" red/left-1.png red/left-2.png red/right-1.png \ + red/right-2.png red/dive-left.png red/dive-right.png \ + bird-red.doodad doodad install-script bird.js bird-red.doodad + doodad edit-doodad --tag "color=red" bird-red.doodad + + doodad convert -t "Bird (blue)" blue/left-1.png blue/left-2.png blue/right-1.png \ + blue/right-2.png blue/dive-left.png blue/dive-right.png \ + bird-blue.doodad + doodad install-script bird.js bird-blue.doodad + doodad edit-doodad --tag "color=blue" bird-blue.doodad # Tag the category for these doodads for i in *.doodad; do\ diff --git a/dev-assets/doodads/bird/bird.js b/dev-assets/doodads/bird/bird.js index 67fa32d..9a236d8 100644 --- a/dev-assets/doodads/bird/bird.js +++ b/dev-assets/doodads/bird/bird.js @@ -1,8 +1,26 @@ -// Bird +// Bird (red and blue) + +/* +Base A.I. behaviors (red bird) are: + +- Tries to maintain original altitude in level and flies left/right. +- Divebombs the player to attack, then climbs back to its original + altitude when it hits a floor/wall. + +Blue bird: + +- Flies in a sine wave pattern (its target altitude fluctuates + around the bird's original placement on the level). +- Longer aggro radius to dive. +*/ let speed = 4, Vx = Vy = 0, - altitude = Self.Position().Y; // original height in the level + color = Self.GetTag("color"), // informs our A.I. behaviors + searchStep = 12 // pixels to step while searching for a player + searchLimit = color === "blue" ? 24 : 12, // multiples of searchStep for aggro radius + altitude = Self.Position().Y, // original height in level + targetAltitude = altitude; // bird's target height to maintain let direction = "left", lastDirection = "left"; @@ -42,6 +60,11 @@ function main() { lastSampled = Point(0, 0); setInterval(() => { + // Run blue bird A.I. if we are blue: moves our target altitude along a sine wave. + if (color === "blue") { + AI_BlueBird(); + } + // Sample how far we've moved to detect hitting a wall. if (sampleTick % sampleRate === 0) { let curP = Self.Position() @@ -65,8 +88,8 @@ function main() { // If we are not flying at our original altitude, correct for that. let curV = Self.Position(); Vy = 0.0; - if (curV.Y != altitude) { - Vy = curV.Y < altitude ? 1 : -1; + if (curV.Y != targetAltitude) { + Vy = curV.Y < targetAltitude ? 1 : -1; } // Scan for the player character and dive. @@ -114,9 +137,9 @@ function AI_ScanForPlayer() { return } - let stepY = 12, // number of pixels to skip + let stepY = searchStep, // number of pixels to skip stepX = stepY, - limit = stepX * 20, // furthest we'll scan + limit = stepX * searchLimit, // furthest we'll scan scanX = scanY = 0, size = Self.Size(), fromPoint = Self.Position(); @@ -147,6 +170,35 @@ function AI_ScanForPlayer() { return; } +// Sine wave controls for the Blue bird. +var sineLimit = 32, + sineCounter = 0, + sineDirection = 1, + sineFrequency = 5, // every 500ms + sineStep = 16; + +// A.I. Subroutine: sine wave pattern (Blue bird). +function AI_BlueBird() { + // The main loop runs on a 100ms interval, control how frequently + // to adjust the bird's target velocity. + if (sineCounter > 0 && (sineCounter % sineFrequency) > 1) { + sineCounter++; + return; + } + sineCounter++; + + targetAltitude += sineStep*sineDirection; + + // Cap the distance between our starting altitude and sine-wave target. + if (targetAltitude < altitude && altitude - targetAltitude >= sineLimit) { + targetAltitude = altitude - sineLimit; + sineDirection = 1 + } else if (targetAltitude > altitude && targetAltitude - altitude >= sineLimit) { + targetAltitude = altitude + sineLimit; + sineDirection = -1 + } +} + // If under control of the player character. function player() { let playerSpeed = 12, diff --git a/dev-assets/doodads/bird/blue/dive-left.png b/dev-assets/doodads/bird/blue/dive-left.png new file mode 100644 index 0000000..1c2f7b7 Binary files /dev/null and b/dev-assets/doodads/bird/blue/dive-left.png differ diff --git a/dev-assets/doodads/bird/blue/dive-right.png b/dev-assets/doodads/bird/blue/dive-right.png new file mode 100644 index 0000000..04f0992 Binary files /dev/null and b/dev-assets/doodads/bird/blue/dive-right.png differ diff --git a/dev-assets/doodads/bird/blue/left-1.png b/dev-assets/doodads/bird/blue/left-1.png new file mode 100644 index 0000000..e988064 Binary files /dev/null and b/dev-assets/doodads/bird/blue/left-1.png differ diff --git a/dev-assets/doodads/bird/blue/left-2.png b/dev-assets/doodads/bird/blue/left-2.png new file mode 100644 index 0000000..f4e6fb5 Binary files /dev/null and b/dev-assets/doodads/bird/blue/left-2.png differ diff --git a/dev-assets/doodads/bird/blue/right-1.png b/dev-assets/doodads/bird/blue/right-1.png new file mode 100644 index 0000000..7c63982 Binary files /dev/null and b/dev-assets/doodads/bird/blue/right-1.png differ diff --git a/dev-assets/doodads/bird/blue/right-2.png b/dev-assets/doodads/bird/blue/right-2.png new file mode 100644 index 0000000..d812571 Binary files /dev/null and b/dev-assets/doodads/bird/blue/right-2.png differ diff --git a/dev-assets/doodads/bird/dive-left.png b/dev-assets/doodads/bird/red/dive-left.png similarity index 100% rename from dev-assets/doodads/bird/dive-left.png rename to dev-assets/doodads/bird/red/dive-left.png diff --git a/dev-assets/doodads/bird/dive-right.png b/dev-assets/doodads/bird/red/dive-right.png similarity index 100% rename from dev-assets/doodads/bird/dive-right.png rename to dev-assets/doodads/bird/red/dive-right.png diff --git a/dev-assets/doodads/bird/left-1.png b/dev-assets/doodads/bird/red/left-1.png similarity index 100% rename from dev-assets/doodads/bird/left-1.png rename to dev-assets/doodads/bird/red/left-1.png diff --git a/dev-assets/doodads/bird/left-2.png b/dev-assets/doodads/bird/red/left-2.png similarity index 100% rename from dev-assets/doodads/bird/left-2.png rename to dev-assets/doodads/bird/red/left-2.png diff --git a/dev-assets/doodads/bird/right-1.png b/dev-assets/doodads/bird/red/right-1.png similarity index 100% rename from dev-assets/doodads/bird/right-1.png rename to dev-assets/doodads/bird/red/right-1.png diff --git a/dev-assets/doodads/bird/right-2.png b/dev-assets/doodads/bird/red/right-2.png similarity index 100% rename from dev-assets/doodads/bird/right-2.png rename to dev-assets/doodads/bird/red/right-2.png diff --git a/pkg/balance/cheats.go b/pkg/balance/cheats.go index eadeef2..bdfa660 100644 --- a/pkg/balance/cheats.go +++ b/pkg/balance/cheats.go @@ -26,10 +26,6 @@ var ( CheatGiveKeys = "give all keys" CheatDropItems = "drop all items" CheatPlayAsBird = "fly like a bird" - CheatPlayAsBoy = "pinocchio" - CheatPlayAsAzuBlue = "the cell" - CheatPlayAsThief = "play as thief" - CheatPlayAsAnvil = "megaton weight" CheatGodMode = "god mode" CheatDebugLoadScreen = "test load screen" CheatUnlockLevels = "master key" @@ -39,3 +35,15 @@ var ( var ( CheatEnabledUnlockLevels bool ) + +// Actor replacement cheats +var CheatActors = map[string]string{ + "pinocchio": "boy", + "the cell": "azu-blue", + "super azulian": "azu-red", + "hyper azulian": "azu-white", + "fly like a bird": "bird-red", + "bluebird": "bird-blue", + "megaton weight": "anvil", + "play as thief": "thief", +} diff --git a/pkg/balance/numbers.go b/pkg/balance/numbers.go index 0b6eac4..15bb536 100644 --- a/pkg/balance/numbers.go +++ b/pkg/balance/numbers.go @@ -131,8 +131,8 @@ var ( EagerRenderLevelChunks = true // Number of chunks margin outside the Canvas Viewport for the LoadingViewport. - LoadingViewportMarginChunks = 2 - CanvasLoadUnloadModuloTicks uint64 = 4 + LoadingViewportMarginChunks = render.NewPoint(8, 4) // hoz, vert + CanvasLoadUnloadModuloTicks uint64 = 2 ) // Edit Mode Values diff --git a/pkg/cheats.go b/pkg/cheats.go index 937d592..6578ee8 100644 --- a/pkg/cheats.go +++ b/pkg/cheats.go @@ -1,6 +1,7 @@ package doodle import ( + "strings" "time" "git.kirsle.net/apps/doodle/pkg/balance" @@ -134,26 +135,6 @@ func (c Command) cheatCommand(d *Doodle) bool { setPlayerCharacter = true d.Flash("Set default player character to Bird (red)") - case balance.CheatPlayAsBoy: - balance.PlayerCharacterDoodad = "boy.doodad" - setPlayerCharacter = true - d.Flash("Set default player character to Boy") - - case balance.CheatPlayAsAzuBlue: - balance.PlayerCharacterDoodad = "azu-blu.doodad" - setPlayerCharacter = true - d.Flash("Set default player character to Blue Azulian") - - case balance.CheatPlayAsThief: - balance.PlayerCharacterDoodad = "thief.doodad" - setPlayerCharacter = true - d.Flash("Set default player character to Thief") - - case balance.CheatPlayAsAnvil: - balance.PlayerCharacterDoodad = "anvil.doodad" - setPlayerCharacter = true - d.Flash("Set default player character to the Anvil") - case balance.CheatGodMode: if isPlay { d.Flash("God mode toggled") @@ -189,7 +170,15 @@ func (c Command) cheatCommand(d *Doodle) bool { } default: - return false + // See if it was an endorsed actor cheat. + if filename, ok := balance.CheatActors[strings.ToLower(c.Raw)]; ok { + d.Flash("Set default player character to %s.", filename) + balance.PlayerCharacterDoodad = filename + setPlayerCharacter = true + } else { + // Not a cheat code. + return false + } } // If we're setting the player character and in Play Mode, do it. diff --git a/pkg/level/chunker.go b/pkg/level/chunker.go index 24b9f37..58877c7 100644 --- a/pkg/level/chunker.go +++ b/pkg/level/chunker.go @@ -20,7 +20,7 @@ type Chunker struct { // Layer is optional for the caller, levels use only 0 and // doodads use them for frames. When chunks are exported to // zipfile the Layer keeps them from overlapping. - Layer int + Layer int `json:"-"` // internal use only Size int `json:"size"` // A Zipfile reference for new-style levels and doodads which @@ -311,7 +311,7 @@ func (c *Chunker) GetChunk(p render.Point) (*Chunk, bool) { // Hit the zipfile for it. if c.Zipfile != nil { if chunk, err := ChunkFromZipfile(c.Zipfile, c.Layer, p); err == nil { - log.Debug("GetChunk(%s) cache miss, read from zip", p) + // log.Debug("GetChunk(%s) cache miss, read from zip", p) c.SetChunk(p, chunk) // cache it c.logChunkAccess(p, chunk) // for the LRU cache if c.pal != nil { diff --git a/pkg/scripting/pubsub.go b/pkg/scripting/pubsub.go index 6c80dd8..e0fa76f 100644 --- a/pkg/scripting/pubsub.go +++ b/pkg/scripting/pubsub.go @@ -35,7 +35,7 @@ func RegisterPublishHooks(s *Supervisor, vm *VM) { for { select { case <-vm.stop: - log.Info("JavaScript VM %s stopping PubSub goroutine", vm.Name) + log.Debug("JavaScript VM %s stopping PubSub goroutine", vm.Name) return case msg := <-vm.Inbound: vm.muSubscribe.Lock() diff --git a/pkg/uix/canvas.go b/pkg/uix/canvas.go index 122d679..ab3b4b4 100644 --- a/pkg/uix/canvas.go +++ b/pkg/uix/canvas.go @@ -377,10 +377,10 @@ func (w *Canvas) LoadingViewport() render.Rect { } return render.Rect{ - X: vp.X - chunkSize*margin, - Y: vp.Y - chunkSize*margin, - W: vp.W + chunkSize*margin, - H: vp.H + chunkSize*margin, + X: vp.X - chunkSize*margin.X, + Y: vp.Y - chunkSize*margin.Y, + W: vp.W + chunkSize*margin.X, + H: vp.H + chunkSize*margin.Y, } } diff --git a/pkg/windows/settings.go b/pkg/windows/settings.go index 4b3df25..83d79ef 100644 --- a/pkg/windows/settings.go +++ b/pkg/windows/settings.go @@ -132,6 +132,7 @@ func (c Settings) makeOptionsTab(tabFrame *ui.TabFrame, Width, Height int) *ui.F Label: "Hide touchscreen control hints during Play Mode", Font: balance.UIFont, BoolVariable: c.HideTouchHints, + OnClick: onClick, }, { Label: "Level & Doodad Editor", @@ -141,6 +142,7 @@ func (c Settings) makeOptionsTab(tabFrame *ui.TabFrame, Width, Height int) *ui.F Label: "Horizontal instead of vertical toolbars", Font: balance.UIFont, BoolVariable: c.HorizontalToolbars, + OnClick: onClick, Tooltip: ui.Tooltip{ Text: "Note: reload your level after changing this option.\n" + "Playtesting and returning will do.", @@ -151,6 +153,7 @@ func (c Settings) makeOptionsTab(tabFrame *ui.TabFrame, Width, Height int) *ui.F Label: "Disable auto-save in the Editor", Font: balance.UIFont, BoolVariable: c.DisableAutosave, + OnClick: onClick, }, { Label: "Draw a crosshair at the mouse cursor.", @@ -409,109 +412,47 @@ func (c Settings) makeExperimentalTab(tabFrame *ui.TabFrame, Width, Height int) // Common click handler for all settings, // so we can write the updated info to disk. - onClick := func(ed ui.EventData) error { + onClick := func() { saveGameSettings() - return nil } - rows := []struct { - Header string - Text string - Boolean *bool - TextVariable *string - PadY int - PadX int - name string // for special cases - }{ + form := magicform.Form{ + Supervisor: c.Supervisor, + Engine: c.Engine, + Vertical: true, + LabelWidth: 150, + } + form.Create(tab, []magicform.Field{ { - Header: "Enable Experimental Features", + Label: "Enable Experimental Features", + Font: balance.LabelFont, }, { - Text: "The setting below can enable experimental features in this\n" + + Label: "The setting below can enable experimental features in this\n" + "game. These are features which are still in development and\n" + "may have unstable or buggy behavior.", - PadY: 2, + Font: balance.UIFont, }, { - Header: "Viewport window", + Label: "Viewport window", + Font: balance.LabelFont, }, { - Text: "This option in the Level menu opens another view into\n" + + Label: "This option in the Level menu opens another view into\n" + "the level. Has glitchy wallpaper problems.", - PadY: 2, + Font: balance.UIFont, }, { - Boolean: c.EnableFeatures, - Text: "Enable experimental features", - PadX: 4, + BoolVariable: c.EnableFeatures, + Label: "Enable experimental features", + Font: balance.UIFont, + OnClick: onClick, }, { - Text: "Restart the game for changes to take effect.", - PadY: 2, + Label: "Restart the game for changes to take effect.", + Font: balance.UIFont, }, - } - for _, row := range rows { - row := row - frame := ui.NewFrame("Frame") - tab.Pack(frame, ui.Pack{ - Side: ui.N, - FillX: true, - PadY: row.PadY, - }) - - // Headers get their own row to themselves. - if row.Header != "" { - label := ui.NewLabel(ui.Label{ - Text: row.Header, - Font: balance.LabelFont, - }) - frame.Pack(label, ui.Pack{ - Side: ui.W, - PadX: row.PadX, - }) - continue - } - - // Checkboxes get their own row. - if row.Boolean != nil { - cb := ui.NewCheckbox(row.Text, row.Boolean, ui.NewLabel(ui.Label{ - Text: row.Text, - Font: balance.UIFont, - })) - cb.Handle(ui.Click, onClick) - cb.Supervise(c.Supervisor) - - // Add warning to the toolbars option if the EditMode is currently active. - if row.name == "toolbars" && c.SceneName == "Edit" { - ui.NewTooltip(cb, ui.Tooltip{ - Text: "Note: reload your level after changing this option.\n" + - "Playtesting and returning will do.", - Edge: ui.Top, - }) - } - - frame.Pack(cb, ui.Pack{ - Side: ui.W, - PadX: row.PadX, - }) - continue - } - - // Any leftover Text gets packed to the left. - if row.Text != "" { - tf := ui.NewFrame("TextFrame") - label := ui.NewLabel(ui.Label{ - Text: row.Text, - Font: balance.UIFont, - }) - tf.Pack(label, ui.Pack{ - Side: ui.W, - }) - frame.Pack(tf, ui.Pack{ - Side: ui.W, - }) - } - } + }) return tab }