mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-17 11:11:10 +02:00
CLOSES #1090, CLOSES #1093, CLOSES #1094, CLOSES #1095, CLOSES #1098, CLOSES #1102, CLOSES #1105, CLOSES #1106, CLOSES #1108, CLOSES #1109, CLOSES #1110, CLOSES #1111, CLOSES #1113, CLOSES #1114, CLOSES #1117, CLOSES #1118, CLOSES #1121, CLOSES #1122, CLOSES #1123, CLOSES #1124, CLOSES #1126, CLOSES #1127, CLOSES #1128, CLOSES #1131, CLOSES #1132, CLOSES #1134, CLOSES #1137, CLOSES #1141
This commit is contained in:
@@ -175,6 +175,20 @@ function Permissions.currentDirection(coll)
|
||||
return CURRENT_DIR[coll % 4]
|
||||
end
|
||||
|
||||
-- DoPlayerMovement .CheckTile, HI_NYBBLE_WARPS arm (.warps): landing on a
|
||||
-- door/staircase/cave forces a walk DOWN off it (engine/overworld/player_movement.asm).
|
||||
local DOOR_FORCED = {
|
||||
[0x71] = true, -- COLL_DOOR
|
||||
[0x79] = true, -- COLL_DOOR_79 (unused)
|
||||
[0x7a] = true, -- COLL_STAIRCASE
|
||||
[0x7b] = true, -- COLL_CAVE
|
||||
}
|
||||
|
||||
function Permissions.doorForcedDirection(coll)
|
||||
if DOOR_FORCED[coll] then return "down" end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- CheckCutCollision (engine/overworld/tile_events.asm): the collisions CUT is
|
||||
-- allowed to swing at. Both grasses are in it, which is why CUT mows a patch
|
||||
-- of tall grass down to bare ground and not only trees.
|
||||
|
||||
@@ -140,6 +140,9 @@ function Player:facingCell()
|
||||
end
|
||||
|
||||
function Player:walkPhase()
|
||||
-- pokegold engine/overworld/map_objects.asm StepFunction_Turn: forces the
|
||||
-- walking leg frame for the whole 4-frame turn-in-place.
|
||||
if self.turnTimer > 0 then return 1 end
|
||||
if not self.moving then return 0 end
|
||||
local p = self.animClock % STEP_FRAMES
|
||||
return (p >= 4 and p < 12) and 1 or 0
|
||||
@@ -167,9 +170,9 @@ function Player:update()
|
||||
self.px = self.cellX * 16 + dx * adv
|
||||
self.py = self.cellY * 16 + dy * adv
|
||||
if self.jumping then
|
||||
-- The hop arc. Cosmetic: the grid position is the straight-line
|
||||
-- interpolation above, only the drawn pixels rise.
|
||||
self.py = self.py - math.floor(6 * math.sin(math.pi * self.progress / frames))
|
||||
-- pokegold engine/overworld/map_objects.asm: UpdateJumpPosition's
|
||||
-- y_offsets table peaks at -12.
|
||||
self.py = self.py - math.floor(12 * math.sin(math.pi * self.progress / frames))
|
||||
end
|
||||
if self.progress >= frames then
|
||||
self.cellX, self.cellY = self.targetX, self.targetY
|
||||
|
||||
+140
-52
@@ -46,6 +46,7 @@ local Music = require("src.core.Music")
|
||||
local NPC = require("src.world.gen2.Npc")
|
||||
local Party = require("src.pokemon.Party")
|
||||
local Permissions = require("src.world.gen2.Permissions")
|
||||
local Pipelines = require("src.render.Pipelines")
|
||||
local Player = require("src.world.gen2.Player")
|
||||
local Pokerus = require("src.core.gen2.Pokerus")
|
||||
local Roamers = require("src.core.gen2.Roamers")
|
||||
@@ -9555,11 +9556,13 @@ function World:stepBody()
|
||||
-- is a scripted step under World:busy, which returns above this line.
|
||||
local dir = self.heldDir
|
||||
if not p.moving then
|
||||
local current = Permissions.currentDirection(self:playerCollision())
|
||||
local coll = self:playerCollision()
|
||||
local current = Permissions.currentDirection(coll)
|
||||
or Permissions.doorForcedDirection(coll)
|
||||
if current then
|
||||
dir = current
|
||||
elseif self.turningDirection
|
||||
and Permissions.isIce(self:playerCollision()) then
|
||||
and Permissions.isIce(coll) then
|
||||
dir = self.turningDirection
|
||||
elseif not dir then
|
||||
self.turningDirection = nil
|
||||
@@ -9615,7 +9618,17 @@ function World:drawGround(s)
|
||||
-- clear colour. LoadMetatiles fills it with wMapBorderBlock instead, and
|
||||
-- the connection strips and the map draw straight over the top of it.
|
||||
if self.map then
|
||||
local bw, bh = G.getDimensions()
|
||||
-- Destination size must be the CURRENT canvas (tilt grows it past the
|
||||
-- window). getDimensions() is always the window, so a grown tilt capture
|
||||
-- used to tile the void against the wrong view and the fill drifted off
|
||||
-- the map grid as the camera moved.
|
||||
local canvas = G.getCanvas()
|
||||
local bw, bh
|
||||
if canvas then
|
||||
bw, bh = canvas:getDimensions()
|
||||
else
|
||||
bw, bh = G.getDimensions()
|
||||
end
|
||||
BorderFill.draw(self, self:borderImageFor(self.map.id),
|
||||
cam.x, cam.y, bw, bh, s, self.map.id)
|
||||
end
|
||||
@@ -9691,45 +9704,51 @@ function World:drawPeople(s, billboard)
|
||||
end
|
||||
end
|
||||
|
||||
if self.emote and self.emote.image then
|
||||
local e = self.emote
|
||||
local ex = math.floor((e.entity.px - cam.x) * s)
|
||||
local ey = math.floor((e.entity.py - 16 - cam.y) * s)
|
||||
-- SpawnEmote.EmoteObject (engine/overworld/map_objects.asm:2029) spawns the
|
||||
-- bubble as an OBJ on PAL_OW_EMOTE, which LoadMapPals resolves to the
|
||||
-- "silver" row of gfx/overworld/npc_sprites.pal (white / white / RGB
|
||||
-- 13,13,13 / black). That row is byte-identical in all four daytime
|
||||
-- blocks, so the bubble is the same at any hour, but it still goes through
|
||||
-- the daytime lookup because that is what LoadMapPals does and it keeps the
|
||||
-- emote on the same path as every other OW sprite. Blitting the extracted
|
||||
-- sheet raw left the interior at the DMG ramp's shade 1 (170 grey) instead
|
||||
-- of white: the Gen 2 repeat of #505.
|
||||
local emoteColors = Palettes.spritePalette(self.palettes,
|
||||
self.daytime or Palettes.daytimeFor(self.map and self.map.def,
|
||||
self:hour(), self.flashUsed),
|
||||
{ paletteId = 5 })
|
||||
local function blit()
|
||||
G.setColor(1, 1, 1, 1)
|
||||
G.draw(e.image, ex, ey, 0, s, s)
|
||||
end
|
||||
local function body()
|
||||
-- GbcPalette.with, not useRaw: the DMG and CLASSIC colour modes still
|
||||
-- have to collapse the row to their own ramps, and it restores whatever
|
||||
-- shader the billboard pass had set rather than assuming none.
|
||||
if emoteColors and GbcPalette.available() then
|
||||
GbcPalette.with(emoteColors, blit)
|
||||
else
|
||||
blit()
|
||||
end
|
||||
end
|
||||
if billboard then
|
||||
billboard(ex + 8 * s, ey + 32 * s, body)
|
||||
self:drawEmote(s, billboard)
|
||||
self:drawHealAnim(s, billboard)
|
||||
end
|
||||
|
||||
-- Split out of drawPeople so World:drawPipeline composites the one copy the
|
||||
-- flat and tilt paths draw, not a second transcription of it.
|
||||
function World:drawEmote(s, billboard)
|
||||
if not (self.emote and self.emote.image) then return end
|
||||
local G = love.graphics
|
||||
local cam = self.camera
|
||||
local e = self.emote
|
||||
local ex = math.floor((e.entity.px - cam.x) * s)
|
||||
local ey = math.floor((e.entity.py - 16 - cam.y) * s)
|
||||
-- SpawnEmote.EmoteObject (engine/overworld/map_objects.asm:2029) spawns the
|
||||
-- bubble as an OBJ on PAL_OW_EMOTE, which LoadMapPals resolves to the
|
||||
-- "silver" row of gfx/overworld/npc_sprites.pal (white / white / RGB
|
||||
-- 13,13,13 / black). That row is byte-identical in all four daytime
|
||||
-- blocks, so the bubble is the same at any hour, but it still goes through
|
||||
-- the daytime lookup because that is what LoadMapPals does and it keeps the
|
||||
-- emote on the same path as every other OW sprite. Blitting the extracted
|
||||
-- sheet raw left the interior at the DMG ramp's shade 1 (170 grey) instead
|
||||
-- of white: the Gen 2 repeat of #505.
|
||||
local emoteColors = Palettes.spritePalette(self.palettes,
|
||||
self.daytime or Palettes.daytimeFor(self.map and self.map.def,
|
||||
self:hour(), self.flashUsed),
|
||||
{ paletteId = 5 })
|
||||
local function blit()
|
||||
G.setColor(1, 1, 1, 1)
|
||||
G.draw(e.image, ex, ey, 0, s, s)
|
||||
end
|
||||
local function body()
|
||||
-- GbcPalette.with, not useRaw: the DMG and CLASSIC colour modes still
|
||||
-- have to collapse the row to their own ramps, and it restores whatever
|
||||
-- shader the billboard pass had set rather than assuming none.
|
||||
if emoteColors and GbcPalette.available() then
|
||||
GbcPalette.with(emoteColors, blit)
|
||||
else
|
||||
body()
|
||||
blit()
|
||||
end
|
||||
end
|
||||
|
||||
self:drawHealAnim(s, billboard)
|
||||
if billboard then
|
||||
billboard(ex + 8 * s, ey + 32 * s, body)
|
||||
else
|
||||
body()
|
||||
end
|
||||
end
|
||||
|
||||
function World:drawWorldBody(s)
|
||||
@@ -9737,6 +9756,52 @@ function World:drawWorldBody(s)
|
||||
self:drawPeople(s)
|
||||
end
|
||||
|
||||
-- Gold's half of the world-pipeline seam: same ctx keys, same order and the
|
||||
-- same nil-falls-back-to-2D rule as src/world/OverworldController.lua:4867.
|
||||
function World:drawPipeline(id, w, h, s)
|
||||
local G = love.graphics
|
||||
local cam = self.camera
|
||||
local ctx = {
|
||||
state = self, cam = cam,
|
||||
vw = self.viewW, vh = self.viewH,
|
||||
-- No BG-only shake here: World:draw slides the whole frame through
|
||||
-- camera.y, so the ground row IS the camera row.
|
||||
bgY = cam.y,
|
||||
width = w, height = h, scale = s,
|
||||
level = Pipelines.level(id),
|
||||
-- imageFor keys its bakes by GbcPalette.mode, so the colour is already in
|
||||
-- the art: nil, like Gen 1 returns in its true-colour modes.
|
||||
paletteFor = function() return nil end,
|
||||
spriteColors = function() return nil end,
|
||||
-- Gold's only standing effects; it has no dust/cutTree/bird/rod overlay,
|
||||
-- and Gen 1's `at` skips a nil body, so those keys are simply absent.
|
||||
fx = {
|
||||
emote = function() self:drawEmote(1, nil) end,
|
||||
heal = function() self:drawHealAnim(1, nil) end,
|
||||
},
|
||||
}
|
||||
-- `project(wx, wy)` -> canvas pixels, nil behind the camera. s = 1 lays the
|
||||
-- closures out in world pixels off the flat foot, the unit Gen 1 uses.
|
||||
ctx.drawFx = function(project, scale)
|
||||
scale = scale or s
|
||||
local function at(fx, fy, body)
|
||||
local sx, sy = project(fx + cam.x, fy + cam.y)
|
||||
if not sx then return end -- behind the camera
|
||||
G.push()
|
||||
G.scale(scale, scale)
|
||||
G.translate(sx / scale - fx, sy / scale - fy)
|
||||
body()
|
||||
G.pop()
|
||||
end
|
||||
self:drawEmote(1, at)
|
||||
self:drawHealAnim(1, at)
|
||||
end
|
||||
local override = Pipelines.drawWorld(id, ctx)
|
||||
-- world post-processes fold in here, so they never touch the text box on top
|
||||
if override then override = Pipelines.worldPresent(override, ctx) end
|
||||
return override
|
||||
end
|
||||
|
||||
-- The perspective quad TILT draws the ground onto. The shader and the
|
||||
-- 4-vertex mesh are the renderer's -- the projection is the same one the Gen 1
|
||||
-- world pass uses, so there is no reason for a second copy of either.
|
||||
@@ -9748,21 +9813,25 @@ function World:tiltMesh()
|
||||
return mesh, shader
|
||||
end
|
||||
|
||||
function World:drawTilted(w, h, s)
|
||||
-- `gw, gh` are the grown capture size from World:draw (Tilt.viewGrowth),
|
||||
-- matching Renderer:worldViewSize. Camera is already followed for that view.
|
||||
function World:drawTilted(w, h, s, gw, gh)
|
||||
local mesh, shader = self:tiltMesh()
|
||||
if not mesh then
|
||||
self:drawWorldBody(s)
|
||||
return
|
||||
end
|
||||
local G = love.graphics
|
||||
gw = gw or w
|
||||
gh = gh or h
|
||||
-- Linear sampling on the tilt canvas softens the shimmer the perspective
|
||||
-- warp would otherwise put on every pixel edge; the flat path keeps nearest.
|
||||
if not self.tiltCanvas or self.tiltCanvas:getWidth() ~= w
|
||||
or self.tiltCanvas:getHeight() ~= h then
|
||||
if not self.tiltCanvas or self.tiltCanvas:getWidth() ~= gw
|
||||
or self.tiltCanvas:getHeight() ~= gh then
|
||||
if self.tiltCanvas and self.tiltCanvas.release then
|
||||
self.tiltCanvas:release()
|
||||
end
|
||||
self.tiltCanvas = G.newCanvas(w, h)
|
||||
self.tiltCanvas = G.newCanvas(gw, gh)
|
||||
self.tiltCanvas:setFilter("linear", "linear")
|
||||
end
|
||||
|
||||
@@ -9778,11 +9847,14 @@ function World:drawTilted(w, h, s)
|
||||
G.setCanvas(previous)
|
||||
|
||||
mesh:setTexture(self.tiltCanvas)
|
||||
mesh:setVertices(Tilt.meshCorners(w, h))
|
||||
mesh:setVertices(Tilt.meshCorners(gw, gh))
|
||||
G.push()
|
||||
G.translate((w - gw) / 2, (h - gh) / 2)
|
||||
G.setColor(1, 1, 1, 1)
|
||||
G.setShader(shader)
|
||||
G.draw(mesh)
|
||||
G.setShader()
|
||||
G.pop()
|
||||
|
||||
-- ...and the standing things over it, each translated from its flat foot
|
||||
-- onto that foot's projection. Nothing here is sheared or resized: tilt
|
||||
@@ -9791,10 +9863,10 @@ function World:drawTilted(w, h, s)
|
||||
-- The ground quad carries the flat canvas and nothing else, so a foot
|
||||
-- outside it has no ground under it; drawing it anyway put NPCs from two
|
||||
-- screens away over the border fill, where the map stops being drawn.
|
||||
if not Tilt.onGround(fx, fy, w, h, 32 * s) then return end
|
||||
local sx, sy = Tilt.groundPoint(fx, fy, w, h)
|
||||
if not Tilt.onGround(fx, fy, gw, gh, 32 * s) then return end
|
||||
local sx, sy = Tilt.groundPoint(fx, fy, gw, gh)
|
||||
G.push()
|
||||
G.translate(sx - fx, sy - fy)
|
||||
G.translate(sx - fx + (w - gw) / 2, sy - fy + (h - gh) / 2)
|
||||
body()
|
||||
G.pop()
|
||||
end)
|
||||
@@ -9832,8 +9904,18 @@ function World:draw()
|
||||
end
|
||||
|
||||
local s = self:zoomScale()
|
||||
local vw = math.ceil(w / s)
|
||||
local vh = math.ceil(h / s)
|
||||
-- Decided before sizing the view: a world pipeline wins over tilt, and tilt
|
||||
-- grows the capture the way Renderer:worldViewSize does on Gen 1 so the
|
||||
-- camera, BorderFill and tilt canvas all share one grid.
|
||||
local pipelineId = Pipelines.worldPipeline()
|
||||
local tilt = (not pipelineId) and Tilt.active() and self:tiltMesh() ~= nil
|
||||
local gw, gh = w, h
|
||||
if tilt then
|
||||
local g = Tilt.viewGrowth()
|
||||
gw, gh = math.ceil(w * g), math.ceil(h * g)
|
||||
end
|
||||
local vw = math.ceil(gw / s)
|
||||
local vh = math.ceil(gh / s)
|
||||
if vw % 2 ~= 0 then vw = vw + 1 end
|
||||
if vh % 2 ~= 0 then vh = vh + 1 end
|
||||
if vw ~= self.viewW or vh ~= self.viewH then
|
||||
@@ -9850,12 +9932,18 @@ function World:draw()
|
||||
self.camera.y = self.camera.y + (self.shake.phase or 0)
|
||||
end
|
||||
|
||||
local override = pipelineId and self:drawPipeline(pipelineId, w, h, s) or nil
|
||||
|
||||
-- TILT projects the finished world frame, so with it on the map, people and
|
||||
-- emote go into a canvas first and that canvas is drawn as a perspective
|
||||
-- quad. Everything after -- the encounter pic and the survey HUD -- stays
|
||||
-- flat, the same split the Gen 1 renderer makes.
|
||||
if Tilt.active() and self:tiltMesh() then
|
||||
self:drawTilted(w, h, s)
|
||||
-- flat, the same split the Gen 1 renderer makes; a pipeline's finished image
|
||||
-- lands in exactly the same place.
|
||||
if override then
|
||||
G.setColor(1, 1, 1, 1)
|
||||
G.draw(override, 0, 0)
|
||||
elseif tilt then
|
||||
self:drawTilted(w, h, s, gw, gh)
|
||||
else
|
||||
self:drawWorldBody(s)
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user