mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-12 00:10:56 +02:00
update for tiled stuff
This commit is contained in:
@@ -35,3 +35,7 @@ mobile/ios/build/
|
||||
|
||||
# Legacy manual convenience-copy location (superseded by /dist/android/)
|
||||
mobile/dist/
|
||||
|
||||
# Tiled map-editing workspace (tools/tiled_export.py). Derived from the imported
|
||||
# ROM cache exactly like data/generated/, so it is never committable.
|
||||
/build/tiled/
|
||||
|
||||
@@ -150,6 +150,11 @@ reference — lives on the
|
||||
|
||||
Shipped example mods, one per kind of author, live in `[mods/](mods/)`.
|
||||
|
||||
Maps can be edited in our own build of [Tiled](https://www.mapeditor.org),
|
||||
[bryanthaboi/tiled_gen1recomp](https://github.com/bryanthaboi/tiled_gen1recomp/releases),
|
||||
and exported back out as a mod; see
|
||||
[docs/tiled-map-editing.md](docs/tiled-map-editing.md).
|
||||
|
||||
## Bugs and Ideas
|
||||
|
||||
Found a bug? A warp dropping you somewhere it shouldn't, a battle doing math
|
||||
|
||||
@@ -18,6 +18,23 @@ Regenerate the reference straight into a wiki checkout:
|
||||
luajit tools/gen_registry_docs.lua ../gen1recomp.wiki
|
||||
```
|
||||
|
||||
## Editing maps in Tiled
|
||||
|
||||
Maps are data, not assets, so they can be authored in a real map editor and
|
||||
exported as a mod. `tools/tiled_export.py` builds a
|
||||
[Tiled](https://www.mapeditor.org) workspace out of the imported ROM cache:
|
||||
|
||||
```sh
|
||||
python3 tools/tiled_export.py # -> build/tiled/ (gitignored)
|
||||
```
|
||||
|
||||
Open `build/tiled/gen1.tiled-project`, edit any of the 222 maps (or
|
||||
`kanto.world` for the stitched overworld), and export with the
|
||||
`gen1-mod-export` extension — one map file, or a whole loadable mod folder.
|
||||
An edited vanilla map becomes a `mod.content.maps:patch` carrying only the
|
||||
fields that moved; a new map becomes a `:register`. See
|
||||
`docs/new-features.md` and the extension's own README.
|
||||
|
||||
## Rendering pipelines
|
||||
|
||||
Most registries hand the engine *content*. `render_pipelines` hands it
|
||||
|
||||
@@ -338,3 +338,9 @@ second, relabelling itself to `Confirm?` in between.
|
||||
|
||||
A validation pill in the tab rail mirrors what the running game would
|
||||
quarantine on load; clicking it jumps to the tab holding the first problem.
|
||||
|
||||
## Tiled map editing (mod authoring)
|
||||
|
||||
`tools/tiled_export.py` turns the imported ROM cache into a Tiled workspace,
|
||||
so maps can be edited in a real map editor and exported back out as a mod.
|
||||
It has its own document: docs/tiled-map-editing.md.
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
# Tiled map editing (mod authoring)
|
||||
|
||||
`tools/tiled_export.py` turns the imported ROM cache into a
|
||||
[Tiled](https://www.mapeditor.org) workspace, so maps can be edited in a
|
||||
real map editor and exported back out as a mod. The original had no map
|
||||
editor at all; the port's own map data is plain Lua, which is what makes
|
||||
this a data path rather than an asset path.
|
||||
|
||||
Editing is done in our own Tiled build,
|
||||
[bryanthaboi/tiled_gen1recomp](https://github.com/bryanthaboi/tiled_gen1recomp/releases),
|
||||
which ships the `gen1-mod-export` extension the workspace relies on. Grab it
|
||||
from that repo's releases; upstream Tiled opens the workspace but cannot
|
||||
export a mod out of it.
|
||||
|
||||
```sh
|
||||
python3 tools/tiled_export.py # -> build/tiled/ (gitignored)
|
||||
```
|
||||
|
||||
Then open `build/tiled/gen1.tiled-project` in that build of Tiled.
|
||||
|
||||
- **The overworld is one surface.** All 222 maps become `maps/*.tmj`, and
|
||||
`kanto.world` places the 36 connected overworld maps at their real
|
||||
connection offsets. That world is pre-loaded (seeded into the workspace's
|
||||
Tiled session), so opening any one overworld map draws its neighbors around
|
||||
it and you scroll and edit straight across the seams. Everything else is a
|
||||
double-click away in Tiled's project panel.
|
||||
- **Extending Kanto wires both ends.** A connection lives on both maps, so
|
||||
hooking a new map onto a base map also emits the return connection as a
|
||||
patch on that base map, keeping its other directions intact. The return
|
||||
offset is derived, not guessed: all 78 vanilla reciprocal pairs satisfy
|
||||
`back.offset == -offset`.
|
||||
- **A Tiled tile is a gen1 block.** Each of the 24 tilesets becomes a Tiled
|
||||
tileset whose tiles are its 32x32 blocks, composited from the 8x8 sheet,
|
||||
so a tile layer *is* the map's `blocks` array. Warps, signs and objects
|
||||
sit on the 16px cell grid in object layers, which is the grid the engine
|
||||
addresses them on.
|
||||
- **Collision is visible.** View > Show Tile Collision Shapes draws the real
|
||||
walkability: a rectangle covers each cell whose feet tile is not in the
|
||||
tileset's `walkable` list, which is the rule `src/world/Map.lua` applies.
|
||||
- **Maps are shown in their real colors.** Each map is atlased in the SGB
|
||||
palette it renders with, so Cerulean is blue and Lavender is purple in the
|
||||
editor exactly as in game. Vanilla resolves that through a cascade with
|
||||
interiors inheriting the last outdoor map, so the workspace mirrors the
|
||||
cascade and walks the warp graph to colour interiors. Changing a map's
|
||||
`palette` exports `palette = "..."` on the record, which beats the cascade,
|
||||
and the editor offers the real palette names as a dropdown.
|
||||
- **New blocks and new tilesets.** `blocksets/*.tmj` show a tileset's blocks
|
||||
as raw 8x8 tiles, four by four, so new blocks can be composed there;
|
||||
per-tile flags on `tilesets/tiles_*.tsj` become `walkable`, `waterTiles`,
|
||||
`doorTiles` and the rest.
|
||||
- **Export is a diff, not a fork of the data.** The `gen1-mod-export`
|
||||
extension (shipped in `tiled_gen1recomp`) writes either one map file or a whole
|
||||
loadable mod folder. An edited vanilla map diffs against the imported data
|
||||
and emits `mod.content.maps:patch` carrying *only* the fields that moved, so
|
||||
a mod covers the parts it changes and leaves the rest to the base game; a
|
||||
new map gets `:register` at an index of 1000 or above. An unchanged map
|
||||
exports nothing at all. Exports pass `tools/modkit.py validate` and `lint`.
|
||||
- **Or the whole record, on request.** Ticking `exactExport` on a map switches
|
||||
it to `mod.content.maps:override`, pinning the map to exactly what the
|
||||
editor shows. It is off by default because an override wins outright over
|
||||
any other mod patching that map, where a patch composes.
|
||||
|
||||
No ROM-derived art travels into an exported mod: a tileset still drawing on
|
||||
the player's own imported sheet references that path rather than shipping the
|
||||
pixels, and only a sheet the author supplied is copied in.
|
||||
@@ -493,6 +493,12 @@ R.maps = {
|
||||
width = f.int(1), height = f.int(1),
|
||||
blocks = f.list(f.int(0, 255)),
|
||||
borderBlock = f.opt(f.int(0, 255)),
|
||||
-- A named SGB palette, which wins over the field.palettes cascade
|
||||
-- (OverworldController.lua:506 reads map.def.palette first). Deliberately
|
||||
-- a plain string rather than f.id("palettes"): the ROM-free fixture base
|
||||
-- carries no palettes at all, so an id reference would fail validation for
|
||||
-- a perfectly good mod wherever there is no imported dataset.
|
||||
palette = f.opt(f.str),
|
||||
warps = f.opt(f.list(f.rec{ x = f.int(0), y = f.int(0),
|
||||
destMap = f.str, destWarp = f.int(0) })),
|
||||
objects = f.opt(f.list(f.any)),
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
-- Dump one generated data table as JSON.
|
||||
--
|
||||
-- luajit tools/lua_to_json.lua data/generated/maps.lua
|
||||
--
|
||||
-- The generated tables are plain data (the extractor serializes them with no
|
||||
-- functions or cycles), so a straight recursive encode is enough. This exists
|
||||
-- so Python tooling -- tools/tiled_export.py -- can read the same records the
|
||||
-- engine reads without carrying a Lua parser of its own, which is the usual
|
||||
-- way those two drift apart.
|
||||
|
||||
local path = ...
|
||||
if not path then
|
||||
io.stderr:write("usage: luajit tools/lua_to_json.lua <data/generated/x.lua>\n")
|
||||
os.exit(2)
|
||||
end
|
||||
|
||||
local chunk, err = loadfile(path)
|
||||
if not chunk then
|
||||
io.stderr:write("cannot load " .. path .. ": " .. tostring(err) .. "\n")
|
||||
os.exit(1)
|
||||
end
|
||||
local root = chunk()
|
||||
|
||||
local ESCAPES = {
|
||||
['"'] = '\\"', ["\\"] = "\\\\", ["\b"] = "\\b", ["\f"] = "\\f",
|
||||
["\n"] = "\\n", ["\r"] = "\\r", ["\t"] = "\\t",
|
||||
}
|
||||
|
||||
local function encodeString(s)
|
||||
return '"' .. s:gsub('[%c"\\]', function(c)
|
||||
return ESCAPES[c] or string.format("\\u%04x", c:byte())
|
||||
end) .. '"'
|
||||
end
|
||||
|
||||
local function encodeNumber(n)
|
||||
if n ~= n or n == math.huge or n == -math.huge then
|
||||
error("non-finite number in " .. path)
|
||||
end
|
||||
-- integers must not pick up a ".0": tile and block ids are compared as
|
||||
-- integers on the Python side and in the exported Lua
|
||||
if n % 1 == 0 then return string.format("%d", n) end
|
||||
return string.format("%.17g", n)
|
||||
end
|
||||
|
||||
-- A table is an array when its keys are exactly 1..#t. Empty tables encode as
|
||||
-- [] and the reader coerces; nothing in the generated data distinguishes an
|
||||
-- empty list from an empty map.
|
||||
local function isArray(t)
|
||||
local count = 0
|
||||
for k in pairs(t) do
|
||||
if type(k) ~= "number" or k % 1 ~= 0 or k < 1 then return false end
|
||||
count = count + 1
|
||||
end
|
||||
return count == #t
|
||||
end
|
||||
|
||||
local out = {}
|
||||
local function emit(s) out[#out + 1] = s end
|
||||
|
||||
local function encode(value)
|
||||
local kind = type(value)
|
||||
if value == nil then
|
||||
emit("null")
|
||||
elseif kind == "boolean" then
|
||||
emit(value and "true" or "false")
|
||||
elseif kind == "number" then
|
||||
emit(encodeNumber(value))
|
||||
elseif kind == "string" then
|
||||
emit(encodeString(value))
|
||||
elseif kind == "table" then
|
||||
if isArray(value) then
|
||||
emit("[")
|
||||
for i = 1, #value do
|
||||
if i > 1 then emit(",") end
|
||||
encode(value[i])
|
||||
end
|
||||
emit("]")
|
||||
else
|
||||
-- sort keys so a regenerated dump is byte-stable and diffable
|
||||
local keys = {}
|
||||
for k in pairs(value) do keys[#keys + 1] = k end
|
||||
table.sort(keys, function(a, b)
|
||||
if type(a) == type(b) then return a < b end
|
||||
return type(a) == "number"
|
||||
end)
|
||||
emit("{")
|
||||
for i, k in ipairs(keys) do
|
||||
if i > 1 then emit(",") end
|
||||
emit(encodeString(tostring(k)))
|
||||
emit(":")
|
||||
encode(value[k])
|
||||
end
|
||||
emit("}")
|
||||
end
|
||||
else
|
||||
error("cannot encode a " .. kind .. " in " .. path)
|
||||
end
|
||||
end
|
||||
|
||||
encode(root)
|
||||
io.write(table.concat(out))
|
||||
io.write("\n")
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user