diff --git a/src/world/WorldAPI.lua b/src/world/WorldAPI.lua index 66aa493c..dadf91d6 100644 --- a/src/world/WorldAPI.lua +++ b/src/world/WorldAPI.lua @@ -6,6 +6,7 @@ -- stays unsupported; anything a mod legitimately needs belongs here. local Logger = require("src.core.Logger") +local Assets = require("src.render.Assets") local MapLoader = require("src.world.MapLoader") local Runtime = require("src.mods.Runtime") @@ -13,6 +14,45 @@ local WorldAPI = {} WorldAPI.__index = WorldAPI 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) return setmetatable({ game = game, modId = modId }, WorldAPI) @@ -44,8 +84,8 @@ function WorldAPI:current() end -- A compact, read-only view of the active map for minimaps and companion UIs. --- Rows use " " for blocked terrain, "." for walkable land, "~" for water --- and "+" for a door or warp. +-- `rows` describes collision terrain; optional `tileRows` reduces each real +-- 8x8 map tile to its average Game Boy shade ("0" lightest, "3" darkest). function WorldAPI:mapOverview() local ow = self:overworld() if not ow or not ow.map then return nil, NO_OVERWORLD end @@ -59,8 +99,11 @@ function WorldAPI:mapOverview() end rows[#rows + 1] = table.concat(row) end + local tileRows = mapTileRows(map) 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 -- opts.arrive = "fly" | "teleport" picks the arrival FX; anything else diff --git a/tests/engine/world_map_overview_test.lua b/tests/engine/world_map_overview_test.lua index 054fea68..d0a4b825 100644 --- a/tests/engine/world_map_overview_test.lua +++ b/tests/engine/world_map_overview_test.lua @@ -1,8 +1,16 @@ package.path = "./?.lua;./?/init.lua;" .. package.path local T = require("tests.harness") +local Assets = require("src.render.Assets") 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 overview, err = api:mapOverview() 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:isWaterCell(x, y) return x == 0 and y == 1 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 = { { 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.rows[1], ".+", "walkable land and warps 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")