feat(mods): expose map tile shading

This commit is contained in:
AverageConsumer
2026-08-08 11:59:57 +02:00
parent 983bea61aa
commit 371edd1a02
2 changed files with 62 additions and 3 deletions
+46 -3
View File
@@ -6,6 +6,7 @@
-- stays unsupported; anything a mod legitimately needs belongs here. -- stays unsupported; anything a mod legitimately needs belongs here.
local Logger = require("src.core.Logger") local Logger = require("src.core.Logger")
local Assets = require("src.render.Assets")
local MapLoader = require("src.world.MapLoader") local MapLoader = require("src.world.MapLoader")
local Runtime = require("src.mods.Runtime") local Runtime = require("src.mods.Runtime")
@@ -13,6 +14,45 @@ local WorldAPI = {}
WorldAPI.__index = WorldAPI WorldAPI.__index = WorldAPI
local NO_OVERWORLD = "no overworld" local NO_OVERWORLD = "no overworld"
local overviewShades = {}
Assets.register(function() overviewShades = {} end)
local function mapTileRows(map)
local tileset = map.tileset
if not (tileset and tileset.image and tileset.tilesPerRow) then return nil end
local cached = overviewShades[tileset.image]
if not cached then
local ok, pixels = pcall(Assets.imageData, tileset.image)
if not ok then return nil end
cached = { pixels = pixels, shades = {} }
overviewShades[tileset.image] = cached
end
local rows, perRow = {}, tileset.tilesPerRow
for ty = 0, map.heightCells * 2 - 1 do
local row = {}
for tx = 0, map.widthCells * 2 - 1 do
local tile = map:tileAt(tx, ty)
local shade = cached.shades[tile]
if shade == nil then
local sum = 0
local ox, oy = (tile % perRow) * 8, math.floor(tile / perRow) * 8
for py = 0, 7 do
for px = 0, 7 do
local r, g, b = cached.pixels:getPixel(ox + px, oy + py)
sum = sum + r * 0.2126 + g * 0.7152 + b * 0.0722
end
end
shade = tostring(math.max(0, math.min(3,
math.floor((1 - sum / 64) * 3 + 0.5))))
cached.shades[tile] = shade
end
row[#row + 1] = shade
end
rows[#rows + 1] = table.concat(row)
end
return rows
end
function WorldAPI.new(game, modId) function WorldAPI.new(game, modId)
return setmetatable({ game = game, modId = modId }, WorldAPI) return setmetatable({ game = game, modId = modId }, WorldAPI)
@@ -44,8 +84,8 @@ function WorldAPI:current()
end end
-- A compact, read-only view of the active map for minimaps and companion UIs. -- A compact, read-only view of the active map for minimaps and companion UIs.
-- Rows use " " for blocked terrain, "." for walkable land, "~" for water -- `rows` describes collision terrain; optional `tileRows` reduces each real
-- and "+" for a door or warp. -- 8x8 map tile to its average Game Boy shade ("0" lightest, "3" darkest).
function WorldAPI:mapOverview() function WorldAPI:mapOverview()
local ow = self:overworld() local ow = self:overworld()
if not ow or not ow.map then return nil, NO_OVERWORLD end if not ow or not ow.map then return nil, NO_OVERWORLD end
@@ -59,8 +99,11 @@ function WorldAPI:mapOverview()
end end
rows[#rows + 1] = table.concat(row) rows[#rows + 1] = table.concat(row)
end end
local tileRows = mapTileRows(map)
return { mapId = map.id, width = map.widthCells, return { mapId = map.id, width = map.widthCells,
height = map.heightCells, rows = rows } height = map.heightCells, rows = rows, tileRows = tileRows,
tileWidth = tileRows and map.widthCells * 2,
tileHeight = tileRows and map.heightCells * 2 }
end end
-- opts.arrive = "fly" | "teleport" picks the arrival FX; anything else -- opts.arrive = "fly" | "teleport" picks the arrival FX; anything else
+16
View File
@@ -1,8 +1,16 @@
package.path = "./?.lua;./?/init.lua;" .. package.path package.path = "./?.lua;./?/init.lua;" .. package.path
local T = require("tests.harness") local T = require("tests.harness")
local Assets = require("src.render.Assets")
local WorldAPI = require("src.world.WorldAPI") local WorldAPI = require("src.world.WorldAPI")
Assets.imageData = function()
return { getPixel = function(_, x)
local shade = x < 8 and 1 or 0
return shade, shade, shade, 1
end }
end
local api = WorldAPI.new({ stack = { states = {} } }, "tester") local api = WorldAPI.new({ stack = { states = {} } }, "tester")
local overview, err = api:mapOverview() local overview, err = api:mapOverview()
T.eq(overview, nil, "map overview is unavailable outside the overworld") T.eq(overview, nil, "map overview is unavailable outside the overworld")
@@ -12,6 +20,7 @@ local map = { id = "TEST_MAP", widthCells = 2, heightCells = 2 }
function map:isWarpTileCell(x, y) return x == 1 and y == 0 end function map:isWarpTileCell(x, y) return x == 1 and y == 0 end
function map:isWaterCell(x, y) return x == 0 and y == 1 end function map:isWaterCell(x, y) return x == 0 and y == 1 end
function map:isWalkableCell(x, y) return x == 0 and y == 0 end function map:isWalkableCell(x, y) return x == 0 and y == 0 end
function map:tileAt(x) return x % 2 end
api = WorldAPI.new({ stack = { states = { api = WorldAPI.new({ stack = { states = {
{ isOverworld = true, map = map }, { isOverworld = true, map = map },
@@ -22,5 +31,12 @@ T.eq(overview.width, 2, "map overview reports its width")
T.eq(overview.height, 2, "map overview reports its height") T.eq(overview.height, 2, "map overview reports its height")
T.eq(overview.rows[1], ".+", "walkable land and warps are distinct") T.eq(overview.rows[1], ".+", "walkable land and warps are distinct")
T.eq(overview.rows[2], "~ ", "water and blocked terrain are distinct") T.eq(overview.rows[2], "~ ", "water and blocked terrain are distinct")
T.eq(overview.tileRows, nil, "tile overview is optional")
map.tileset = { image = "test.png", tilesPerRow = 2 }
overview = api:mapOverview()
T.eq(overview.tileWidth, 4, "tile overview reports its width")
T.eq(overview.tileHeight, 4, "tile overview reports its height")
T.eq(overview.tileRows[1], "0303", "tile overview preserves map shading")
T.finish("world map overview") T.finish("world map overview")