mirror of
https://github.com/DramaticShape/DramaticShapeVoxelMod.git
synced 2026-08-12 16:50:53 +02:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 917fbac38c | |||
| d8d1d7e336 | |||
| 4e6aa30c2c | |||
| 2e0ae37bcd | |||
| 9207712b19 | |||
| 2447046aee |
+24
-3
@@ -1,11 +1,23 @@
|
||||
# Changelog
|
||||
|
||||
## 1.1.1
|
||||
|
||||
### Fixed
|
||||
|
||||
- A move that shakes the screen no longer whites out the frame. The zone pass
|
||||
fills each zone with its blank colour before drawing the shifted copy -- the
|
||||
hardware showing empty BG in the strip the shake vacated -- and a shake
|
||||
program alternates offset and no-offset frames, so over the map that read as
|
||||
the whole battle screen, menu box included, flashing white a few times a
|
||||
second. The fill is dropped while a battle is staged on the map; the shake
|
||||
itself still moves the HUD.
|
||||
|
||||
## 1.2.0
|
||||
|
||||
### Added
|
||||
|
||||
- **A `FULL` rung on the VOXEL row**, directly after `OFF`. One choice that
|
||||
puts the whole mode in its intended state -- the 50-degree camera, the
|
||||
puts the whole mode in its intended state -- the 35-degree camera, the
|
||||
miniature blur at maximum, the horizon flat, the view fitted, and battles
|
||||
on the map -- rather than making a player assemble it from four rows.
|
||||
|
||||
@@ -20,6 +32,15 @@
|
||||
Leaving it deliberately undoes nothing -- reverting would discard whatever
|
||||
had been changed since.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **The hit flash whited out the whole screen.** The engine draws it as a
|
||||
full-screen white rectangle, which is a flash on a white battle field and
|
||||
a whiteout of the map, the HUD and the text box over a world. It is now
|
||||
dropped on the way past and put back where it was ever about: the two
|
||||
Pokemon go solid white for those frames, silhouette and all, and nothing
|
||||
else in the frame moves.
|
||||
|
||||
### Changed
|
||||
|
||||
- **Hotkey `3` walks the angle rungs only and steps over `FULL`.** The key is
|
||||
@@ -29,9 +50,9 @@
|
||||
with nothing on screen saying a keypress had done it. `FULL` stays on the
|
||||
OPTIONS row, where a preset that changes other rows belongs.
|
||||
|
||||
A press FROM `FULL` goes to `75`. `FULL` is already the 50-degree camera,
|
||||
A press FROM `FULL` goes to `50`. `FULL` is already the 35-degree camera,
|
||||
so stepping to the rung of that name would look like the key had done
|
||||
nothing.
|
||||
nothing. Matched by angle, so it follows `FULL` if that is ever retuned.
|
||||
|
||||
- **The mode's four options are one block in the menu.** The engine splices
|
||||
a pipeline row in beside TILT and lands a mod's own rows at the end of the
|
||||
|
||||
@@ -278,6 +278,17 @@ end
|
||||
-- yet (the terrain mesh is still building, the driver has no depth support).
|
||||
-- nil is not a failure: the caller simply leaves the battle screen as the
|
||||
-- engine drew it for that frame.
|
||||
-- White, for the hit flash, and how far toward it the card goes.
|
||||
--
|
||||
-- The shader replaces the card's colour rather than multiplying it, so at
|
||||
-- full strength this is the sprite turned into a solid white silhouette --
|
||||
-- which is what the effect is on a flat GB screen and far too much on a
|
||||
-- sprite standing in a lit world. Held well short of 1, the mon's own
|
||||
-- shading still reads through the flash: it looks struck rather than
|
||||
-- deleted.
|
||||
BattleScene.FLASH_COLOR = { 1, 1, 1 }
|
||||
BattleScene.FLASH_STRENGTH = 0.5
|
||||
|
||||
function BattleScene.render(state, arena, textures, token)
|
||||
if not (state and state.map and arena) then return nil end
|
||||
if not Voxel3D.available() then return nil end
|
||||
@@ -358,10 +369,20 @@ function BattleScene.render(state, arena, textures, token)
|
||||
-- front of it, and the alpha discard cuts the sprite's own outline out of
|
||||
-- the card. A small camera-ward pull keeps a card rooted to the ground
|
||||
-- plane from z-fighting the tile it is standing on.
|
||||
-- The engine's hit flash is a full-screen white rectangle, which on a
|
||||
-- white battle field is a flash and over a world is a whiteout of the
|
||||
-- map, the HUD and the text box alike. It is dropped on the way past
|
||||
-- (see OverworldBattle) and put back HERE, on the two things it was ever
|
||||
-- about: the mons themselves go solid white for those frames.
|
||||
local flashing = textures and textures.flash
|
||||
if flashing then
|
||||
Voxel3D.flatten(BattleScene.FLASH_COLOR, BattleScene.FLASH_STRENGTH)
|
||||
end
|
||||
for _, card in ipairs(monCards(arena, groundY, textures)) do
|
||||
Voxel3D.draw(BattleBillboard.mesh(), card.tex, card.model,
|
||||
BattleBillboard.PULL)
|
||||
end
|
||||
if flashing then Voxel3D.flatten(nil) end
|
||||
-- grass and flowers ride the same camera-ward pull the free-roam pass
|
||||
-- gives them, measured against THIS camera's pitch rather than the
|
||||
-- orbit's -- there is no character here for them to overdraw, but the
|
||||
|
||||
+69
-5
@@ -340,11 +340,21 @@ local function withoutBackgroundFill(battle, fn)
|
||||
if mode == "fill" and x == 0 and y == 0
|
||||
and w == BattleScene.GB_W and h == BattleScene.GB_H then
|
||||
local r, gr, b, a = g.getColor()
|
||||
if r > 0.99 and gr > 0.99 and b > 0.99 and a > 0.99 then
|
||||
local target = g.getCanvas()
|
||||
if target ~= nil
|
||||
and (target == battle.bgCanvas or target == battle.waveCanvas) then
|
||||
g.clear(0, 0, 0, 0)
|
||||
if r > 0.99 and gr > 0.99 and b > 0.99 then
|
||||
-- Two different full-frame whites, both replaced rather than drawn.
|
||||
--
|
||||
-- OPAQUE is the battle's background, and on the offscreen canvases it
|
||||
-- doubles as their clear, so there it becomes a transparent one.
|
||||
--
|
||||
-- TRANSLUCENT is the hit flash. Over a white field that reads as a
|
||||
-- flash; over a world it whites out the map, the HUD and the text box
|
||||
-- together. BattleScene puts it back on the mons alone.
|
||||
if a > 0.99 then
|
||||
local target = g.getCanvas()
|
||||
if target ~= nil
|
||||
and (target == battle.bgCanvas or target == battle.waveCanvas) then
|
||||
g.clear(0, 0, 0, 0)
|
||||
end
|
||||
end
|
||||
return
|
||||
end
|
||||
@@ -471,6 +481,18 @@ function OverworldBattle.sideTexture(battle, side)
|
||||
return { canvas = canvas, ax = ax, ay = ay, trainer = trainer }
|
||||
end
|
||||
|
||||
-- Whether the hit flash is showing this frame.
|
||||
--
|
||||
-- Mirrors BattleState:draw's own test, because the flash is a DRAW-time
|
||||
-- decision there (a counter plus the frame parity that makes it flicker) and
|
||||
-- there is no seam that reports it. Read-only, so the worst a future engine
|
||||
-- change can do is flash on a frame the engine would not have.
|
||||
function OverworldBattle.flashing(battle)
|
||||
local fx = battle and battle.fx
|
||||
if not (fx and fx.flash and fx.flash > 0) then return false end
|
||||
return (battle.frame or 0) % 4 < 2
|
||||
end
|
||||
|
||||
-- Both sides, or nil when neither has anything to show.
|
||||
function OverworldBattle.textures(battle)
|
||||
if not battle then return nil end
|
||||
@@ -480,6 +502,7 @@ function OverworldBattle.textures(battle)
|
||||
out.enemy = okE and enemy or nil
|
||||
out.player = okP and player or nil
|
||||
if not (out.enemy or out.player) then return nil end
|
||||
out.flash = OverworldBattle.flashing(battle)
|
||||
return out
|
||||
end
|
||||
|
||||
@@ -622,6 +645,47 @@ function OverworldBattle.install()
|
||||
if not ok then error(err, 0) end
|
||||
end
|
||||
|
||||
-- The engine's flash has a SECOND half, and it is the one that reaches the
|
||||
-- menu. Beside the white rectangle (dropped above) the flash moves are
|
||||
-- driven by a BGP palette fade -- BGP_LIGHT and friends -- which the
|
||||
-- colorized pipeline applies in drawZonePass to the WHOLE background
|
||||
-- canvas. That canvas carries the HUD glyphs and the text box, so a fade
|
||||
-- meant for the two mons washed the menu out with them.
|
||||
--
|
||||
-- The fade is left switched on for the pics, which read it through
|
||||
-- picImage, and switched off for the zone pass alone. So the mons flash
|
||||
-- and the furniture around them does not.
|
||||
--
|
||||
-- The zone pass has a SECOND thing it paints, and this is the one that
|
||||
-- reads as the menu box flashing. A screen shake makes it fill every zone
|
||||
-- with the zone's own color 0 before it draws the offset copy -- the
|
||||
-- hardware showing empty BG in the strip the shake vacated. On a white
|
||||
-- battle field that fill is invisible; over a world it is an opaque white
|
||||
-- sheet across the whole frame, and since a shake program alternates
|
||||
-- offset and no-offset frames (SE_SHAKE_SCREEN steps dx 1, 0, 1, 0...) it
|
||||
-- switches on and off a few times a second. It is dropped: the background
|
||||
-- here is the map, so what the shake vacates should show the map.
|
||||
local innerZone = BattleState.drawZonePass
|
||||
function BattleState:drawZonePass(src, sx, sy)
|
||||
if not self.dramaticShapeShot then return innerZone(self, src, sx, sy) end
|
||||
-- shadow the method on the instance for this call only; putting the
|
||||
-- field back to whatever it was (normally nil) lets the class method be
|
||||
-- found again
|
||||
local had = rawget(self, "activeBgp")
|
||||
self.activeBgp = function() return nil end
|
||||
local g = love.graphics
|
||||
local rectangle = g.rectangle
|
||||
g.rectangle = function(mode, ...)
|
||||
-- the pass draws no other rectangle; the shake still shifts the copy
|
||||
if mode == "fill" then return end
|
||||
return rectangle(mode, ...)
|
||||
end
|
||||
local ok, err = pcall(innerZone, self, src, sx, sy)
|
||||
g.rectangle = rectangle
|
||||
self.activeBgp = had
|
||||
if not ok then error(err, 0) end
|
||||
end
|
||||
|
||||
-- Black glyphs on grass are not readable; over a frosted panel measured
|
||||
-- dark they are not readable either, so they go white. Mapped rather than
|
||||
-- rewritten: the HUD sets pure black for its text and nothing else, and in
|
||||
|
||||
@@ -537,6 +537,27 @@ function Voxel3D.beginGhost()
|
||||
end
|
||||
end
|
||||
|
||||
-- Flatten whatever is drawn next to one solid colour, or nil to stop.
|
||||
--
|
||||
-- The same `ghost` path the silhouette uses, WITHOUT beginGhost's inverted
|
||||
-- depth test and half alpha -- this is for something drawn normally that
|
||||
-- simply wants to come out one colour, which is what a hit flash on a sprite
|
||||
-- is. beginScene resets the uniform every frame, so a pass that forgets to
|
||||
-- clear it cannot leak into the next one.
|
||||
-- `amount` is how far toward that colour, 0..1; omitted is all the way.
|
||||
-- Anything short of 1 leaves the sprite's own shading showing through, which
|
||||
-- is the difference between a hit flash and a white cut-out.
|
||||
function Voxel3D.flatten(color, amount)
|
||||
if not (active and activeShader) then return end
|
||||
local sh = activeShader
|
||||
if color then
|
||||
pcall(sh.send, sh, "ghostColor", color)
|
||||
pcall(sh.send, sh, "ghost", math.max(0, math.min(1, amount or 1)))
|
||||
else
|
||||
pcall(sh.send, sh, "ghost", 0)
|
||||
end
|
||||
end
|
||||
|
||||
function Voxel3D.endGhost()
|
||||
if not active then return end
|
||||
pcall(love.graphics.setDepthMode, "lequal", true)
|
||||
|
||||
+6
-5
@@ -29,10 +29,10 @@ local Voxel = {}
|
||||
-- it rather than assembling it from four rows. It sits directly after OFF
|
||||
-- because that is the order those two get used in.
|
||||
--
|
||||
-- Its ANGLE is 50 degrees, the same as the rung of that name. The duplicate
|
||||
-- Its ANGLE is 35 degrees, the same as the rung of that name. The duplicate
|
||||
-- in the table is deliberate: the ladder is a list of what each rung LOOKS
|
||||
-- like, and two rungs may look the same while meaning different things.
|
||||
Voxel.ANGLES_DEG = { 0, 50, 15, 35, 50, 75 }
|
||||
Voxel.ANGLES_DEG = { 0, 35, 15, 35, 50, 75 }
|
||||
Voxel.ANGLE_LABELS = { "OFF", "FULL", "15", "35", "50", "75" }
|
||||
Voxel.MAX_LEVEL = #Voxel.ANGLES_DEG - 1
|
||||
|
||||
@@ -56,9 +56,10 @@ Voxel.HOTKEY_ORDER = { 0, 2, 3, 4, 5 } -- OFF, 15, 35, 50, 75
|
||||
-- The rung a press moves to from `level`.
|
||||
--
|
||||
-- A level that is not on the key's path -- FULL, reached from the menu --
|
||||
-- steps on from whichever rung shows the SAME camera it does. FULL is 50
|
||||
-- degrees, so a press from it goes to 75 rather than back to 50, and the key
|
||||
-- never appears to do nothing.
|
||||
-- steps on from whichever rung shows the SAME camera it does. FULL is 35
|
||||
-- degrees, so a press from it goes to 50 rather than back to 35, and the key
|
||||
-- never appears to do nothing. Matched by ANGLE rather than by a hardcoded
|
||||
-- rung, so retuning FULL moves the key's answer with it.
|
||||
function Voxel.nextHotkeyLevel(level)
|
||||
level = level or Voxel.level
|
||||
local order = Voxel.HOTKEY_ORDER
|
||||
|
||||
@@ -620,7 +620,7 @@ mod.events:on("battle.ended", function()
|
||||
OverworldBattle.finish()
|
||||
end)
|
||||
|
||||
mod.exports.version = "1.2.0"
|
||||
mod.exports.version = "1.1.1"
|
||||
-- exposed so a companion mod can pin its own tiles' shapes or read the
|
||||
-- camera without reaching into this mod's file layout
|
||||
mod.exports.lib = V
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "DRAMATIC_SHAPE",
|
||||
"name": "Dramatic Shape Voxel Mod",
|
||||
"version": "1.2.0",
|
||||
"version": "1.1.1",
|
||||
"api": 2,
|
||||
"entry": "main.lua",
|
||||
"profile": "content",
|
||||
|
||||
@@ -840,12 +840,15 @@ end
|
||||
T.eq(table.concat(walk, ","), "15,35,50,75,OFF,15",
|
||||
"3 walks OFF -> 15 -> 35 -> 50 -> 75 and wraps, never touching FULL")
|
||||
|
||||
-- FULL is 50 degrees, so a press from it goes ON to 75 rather than back to
|
||||
-- the rung that shows the same camera -- the key never appears to do nothing
|
||||
-- FULL is 35 degrees, so a press from it goes ON to 50 rather than back to
|
||||
-- the rung that shows the same camera -- the key never appears to do nothing.
|
||||
-- Matched by angle, so this follows FULL if it is ever retuned.
|
||||
T.eq(VoxelState.ANGLES_DEG[VoxelState.FULL_LEVEL + 1], 35,
|
||||
"FULL is the 35-degree camera")
|
||||
Pipelines.setLevel("voxel", VoxelState.FULL_LEVEL)
|
||||
Game.keypressed(keyGame, "3")
|
||||
T.eq(Pipelines.levelLabel("voxel"), "75",
|
||||
"a press from FULL goes to 75, since FULL already IS the 50 camera")
|
||||
T.eq(Pipelines.levelLabel("voxel"), "50",
|
||||
"a press from FULL goes to 50, since FULL already IS the 35 camera")
|
||||
|
||||
Pipelines.setLevel("voxel", 0)
|
||||
Game.keypressed(keyGame, "3")
|
||||
@@ -1371,6 +1374,24 @@ T.check(math.abs(sx - px) < 0.2,
|
||||
-- arena floor out from under the two mons pinned to it
|
||||
T.eq(rig.curve, 0, "the battle camera switches the world curve off")
|
||||
|
||||
-- ------- the hit flash belongs to the mons, not the screen
|
||||
--
|
||||
-- The engine draws it as a full-screen white rectangle, which is a flash on
|
||||
-- a white battle field and a whiteout of the map, the HUD and the text box
|
||||
-- over a world. It is dropped on the way past and put back on the two cards.
|
||||
local Battles = run.loader.exports.DRAMATIC_SHAPE.lib.require("OverworldBattle")
|
||||
T.eq(Battles.flashing(nil), false, "no battle, no flash")
|
||||
T.eq(Battles.flashing({ fx = {}, frame = 0 }), false,
|
||||
"a battle with no flash counter is not flashing")
|
||||
T.eq(Battles.flashing({ fx = { flash = 0 }, frame = 0 }), false,
|
||||
"nor one whose counter has run out")
|
||||
T.eq(Battles.flashing({ fx = { flash = 16 }, frame = 0 }), true,
|
||||
"a live counter flashes on the frames the engine would")
|
||||
T.eq(Battles.flashing({ fx = { flash = 16 }, frame = 2 }), false,
|
||||
"and is dark on the others, which is what makes it flicker")
|
||||
T.eq(Battles.flashing({ fx = { flash = 16 }, frame = 5 }), true,
|
||||
"on a four-frame cycle")
|
||||
|
||||
-- ------- the wireframe is forced on in a battle
|
||||
--
|
||||
-- A fight is a staged shot rather than the world being walked through, so it
|
||||
|
||||
Reference in New Issue
Block a user