diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ab9c2e..ca54fbd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -63,10 +63,13 @@ run, picking up from whichever pin or SYNC sky the player was just looking at, and `SYNC` -- the DEFAULT -- lays the machine's own clock onto the dial: local noon is the DAY pin, midnight is NIGHT, six and eighteen the - twilights, an hour of the real day is fifty seconds of dial. Everything is a pure function of - the clock, so the pinned DUSK is exactly the running cycle stopped at - sunset. Setting **VOXEL** to `FULL` switches DAYTIME to `CYCLE` along with - the rest of the preset. + twilights, an hour of the real day is fifty seconds of dial. Everything is + a pure function of the clock, so the pinned DUSK is exactly the running + cycle stopped at sunset. While **VOXEL** sits on `FULL` the DAYTIME row is + HELD at `SYNC` and taken off the menu with the other rows the preset owns + (DayNight.forceSync, enforced from the preset, the rows hook and the + manager's options_changed -- the same three places BATTLE LAYOUT's pin + lives): the full diorama runs on the real sky. **The sun and the moon are in the sky**, and their positions are honest: the disc is the light's own direction projected through the same matrix the @@ -198,6 +201,17 @@ Pokemon go solid white for those frames, silhouette and all, and nothing else in the frame moves. +- **A scripted battle cut straight in with no transition** (an ENGINE seam, + fixed in `src/script/Commands.lua` rather than in this mod): the rival in + Oak's lab, and every `start_battle` script, pushed the BattleState bare -- + no flash, no wipe, the theme starting late -- where the original wipes + into scripted fights like any other. `start_battle` now routes through the + overworld's own `pushBattle`, which is also the path this mod wraps, so a + scripted fight gets its arena staged and the cast culled BEFORE the wipe + instead of catching up behind it. A battle scripted with no overworld + under it still starts bare, and no music plays twice (BattleState's own + start is a same-song no-op). + - **A standing figure's shadow detached from its feet under a low sun.** The shadow compare forgives `slack` world pixels so lit ground does not acne against its own texels, and that same forgiveness lit the first `slack` of diff --git a/README.md b/README.md index d89bbd7..e87e059 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ menu. | `6`, or the **T-SHIFT** options row | OFF → 1 → 2 → 3 → OFF (miniature blur) | | `7`, or the **V-CURVE** options row | OFF → 1 → 2 → 3 — bend the world over the horizon | | `8`, or the **3D-BTL** options row | ON / OFF — fight on the map instead of on a white field | -| the **DAYTIME** options row | DAY / NIGHT / DUSK / DAWN / CYCLE — what time it is outdoors | +| the **DAYTIME** options row | SYNC / DAY / NIGHT / DUSK / DAWN / CYCLE — what time it is outdoors; held at SYNC (and off the menu) while VOXEL is FULL | **3D-BTL** is on by default and is independent of **VOXEL**: battles draw on the world whether or not the free-roam camera is pitched over. \ No newline at end of file diff --git a/lib/DayNight.lua b/lib/DayNight.lua index f561c57..3be50e5 100644 --- a/lib/DayNight.lua +++ b/lib/DayNight.lua @@ -64,14 +64,26 @@ DayNight.KEY = "daytime" DayNight.LABEL = "DAYTIME" -- "sync" first: an unset or unreadable value follows the machine's own --- clock, per the row's contract (ModSetting values[1] is the default). --- "cycle" stays LAST: the FULL preset reaches for it by position. +-- clock, per the row's contract (ModSetting values[1] is the default) -- +-- and forceSync below reaches for it by the same position. DayNight.setting = ModSetting.new(DayNight.KEY, DayNight.LABEL, { "sync", "day", "night", "dusk", "dawn", "cycle" }, { "SYNC", "DAY", "NIGHT", "DUSK", "DAWN", "CYCLE" }) +-- The one writer for the FULL pin. While VOXEL sits on FULL the DAYTIME +-- row is off the menu with the rest of the rows the preset owns, and the +-- value is held HERE at SYNC -- the diorama preset's sky follows the clock +-- on the wall, whatever was chosen before. Called from every path that can +-- arrive at or act under FULL (main.lua: the preset itself, the rows hook, +-- the manager's options_changed), mirroring OverworldBattle.forceOG. +function DayNight.forceSync(game) + if DayNight.setting:get() ~= "sync" then + DayNight.setting:setIndex(1, game) + end +end + DayNight.clock = DayNight.T.day -- the running cycle's own position -- ------- the two arcs diff --git a/main.lua b/main.lua index 301fc5a..24f860d 100644 --- a/main.lua +++ b/main.lua @@ -293,10 +293,11 @@ applyFull = function(level) -- is solved against (OverworldBattle.forceOG); FULL has just switched staged -- fights on, so the layout follows them. OverworldBattle.forceOG(Game) - -- FULL is the whole diorama, and a diorama with a running sky is more of - -- one: the clock is set going (CYCLE is the ladder's last rung). Set, not - -- held, like everything else here -- the player can pin it back afterwards. - DayNight.setting:setIndex(#DayNight.setting.values, Game) + -- and the sky on the clock on the wall: FULL pins DAYTIME to SYNC. Unlike + -- the rest of the preset this one IS held, not just set -- the row is off + -- the menu while FULL owns it (the rows hook below), so a value changed + -- under it could never be seen or changed back. + DayNight.forceSync(Game) if Game.writeOptions then pcall(Game.writeOptions, Game) end end @@ -492,6 +493,9 @@ mod.hooks:wrap("ui.options.rows", function(next, game, rows) dropRow(out, "battleLayout") end if Voxel.isFull(Pipelines.level("voxel")) then + -- FULL keeps every mod row off the menu (the early return skips the + -- insert below), and holds DAYTIME at SYNC while the row is unreachable + DayNight.forceSync(game) return dropRow(out, "pipeline:tiltshift") end local extra = {} @@ -510,6 +514,11 @@ mod.events:on("mod.options_changed", function(payload) -- the OPTIONS row does. The manager persists its own value; this is the one -- that has to follow it. if stagedBattles() then OverworldBattle.forceOG() end + -- and DAYTIME changed from the manager's page while FULL owns it snaps + -- straight back to SYNC -- the OPTIONS row is hidden, but the manager's is + -- not, and FULL's pin must hold against both + local Pipelines = require("src.render.Pipelines") + if Voxel.isFull(Pipelines.level("voxel")) then DayNight.forceSync() end end) -- ------- keeping the geometry in step with the world diff --git a/tests/dramatic_shape_test.lua b/tests/dramatic_shape_test.lua index f3caeb0..89ff230 100644 --- a/tests/dramatic_shape_test.lua +++ b/tests/dramatic_shape_test.lua @@ -134,6 +134,19 @@ T.check(not fullIds["pipeline:tiltshift"], T.check(not fullIds["DRAMATIC_SHAPE:grid"], "and V-GRID") T.check(not fullIds["DRAMATIC_SHAPE:curve"], "and V-CURVE") T.check(not fullIds["DRAMATIC_SHAPE:battles"], "and 3D-BTL") +T.check(not fullIds["DRAMATIC_SHAPE:daytime"], "and DAYTIME") + +-- DAYTIME is not only hidden under FULL, it is HELD at SYNC: the row cannot +-- be reached while FULL owns it, so a value changed underneath (the mod +-- manager's page, an edited options file) snaps back when the menu asks +do + local DayNight = run.loader.exports.DRAMATIC_SHAPE.lib.require("DayNight") + DayNight.setting:sync("night") + Runtime.call("ui.options.rows", function(_, r) return r end, + { data = Data }, { { id = "tilt" } }) + T.eq(DayNight.setting:get(), "sync", + "under FULL the rows hook pins DAYTIME back to SYNC, whatever was chosen") +end -- ------- BATTLE LAYOUT is pinned to OG while a fight can be staged on the map -- @@ -2115,19 +2128,16 @@ DayNight.update(0) T.check(math.abs(DayNight.clock - 25) < 1e-9, "CYCLE picks up from wherever SYNC's sky already was") DayNight.hours = hoursWas -T.eq(DayNight.setting.values[#DayNight.setting.values], "cycle", - "cycle stays the ladder's last rung -- the FULL preset reaches for it " - .. "by position") --- arriving at FULL sets the clock going +-- arriving at FULL pins the sky to the clock on the wall local Game = require("src.core.Game") local hadSave = Game.save Game.save = { options = {} } DayNight.setting:sync("day") defs.voxel.update(0, 2) -- any rung that is not FULL defs.voxel.update(0, 1) -- and the arrival -T.eq(DayNight.setting:get(), "cycle", - "FULL switches DAYTIME to CYCLE -- the diorama gets its running sky") +T.eq(DayNight.setting:get(), "sync", + "FULL pins DAYTIME to SYNC, whatever was chosen before") Game.save = hadSave -- put the room back the way it was found (SYNC is the shipped default) @@ -2138,6 +2148,42 @@ Voxel3D.tint = { 1, 1, 1 } Voxel3D.vp = nil end +-- ------- a scripted fight wipes in like a walked-into one +-- +-- An engine seam this mod leans on: Commands.start_battle used to push the +-- BattleState bare, so the rival in Oak's lab CUT to battle with no +-- transition -- no flash, no wipe, the theme starting late. It now routes +-- through the overworld's own pushBattle, the same path a grass encounter +-- takes (and the path this mod wraps to stage the arena before the wipe). +do +local Commands = require("src.script.Commands") +local realBS = package.loaded["src.battle.BattleState"] +package.loaded["src.battle.BattleState"] = { + newWild = function() return { kind = "wild" } end, + newTrainer = function() return { kind = "trainer" } end, +} +local pushed, viaOverworld = nil, nil +local runner = { yield = function() end, resume = function() end } +local ctx = { + runner = runner, + game = { stack = { push = function(_, s) pushed = s end } }, + overworld = { + pushBattle = function(_, b) viaOverworld = b end, + afterBattle = function() end, + }, +} +Commands.start_battle(ctx, "trainer", "RIVAL1", 1) +T.check(viaOverworld ~= nil and pushed == nil, + "a scripted trainer goes through pushBattle: the flash, the wipe and the " + .. "theme, like any fight walked into") +T.eq(viaOverworld.kind, "trainer", "with the battle it was asked to start") +ctx.overworld = nil +Commands.start_battle(ctx, "wild", "PIDGEY", 5) +T.check(pushed ~= nil, + "and a battle scripted with no overworld under it still starts bare") +package.loaded["src.battle.BattleState"] = realBS +end + -- ------- night falls in the forest -- -- Viridian Forest is not outdoor (no sky, and the light through the leaves