Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8804bfabaf | |||
| 13117e475b | |||
| 377b0f60ca | |||
| b285201cba | |||
| e14afd8dfa | |||
| 6bcd4d3144 | |||
| 790c34efff | |||
| 43385c8bf6 | |||
| c404c766cd | |||
| 9700806a92 | |||
| 6e9b787e41 |
@@ -1,6 +1,6 @@
|
|||||||
name: Release
|
name: Release
|
||||||
|
|
||||||
# Packs the mod into an installable .zip and publishes it as a Gitea Release,
|
# Packs the mod into an installable .zip and publishes it as a GitHub Release,
|
||||||
# once per push to main.
|
# once per push to main.
|
||||||
#
|
#
|
||||||
# Archive layout: every mod file at the archive root, manifest.json included.
|
# Archive layout: every mod file at the archive root, manifest.json included.
|
||||||
@@ -24,10 +24,10 @@ name: Release
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [master, dev]
|
branches: [master]
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- ".gitea/**"
|
- '.github/**'
|
||||||
- "**.md"
|
- '**.md'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
version:
|
version:
|
||||||
@@ -113,7 +113,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Refuse to clobber an existing release
|
- name: Refuse to clobber an existing release
|
||||||
env:
|
env:
|
||||||
GITEA_TOKEN: ${{ github.token }}
|
GH_TOKEN: ${{ github.token }}
|
||||||
TAG: ${{ steps.ver.outputs.tag }}
|
TAG: ${{ steps.ver.outputs.tag }}
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
@@ -121,13 +121,7 @@ jobs:
|
|||||||
echo "::error::Tag $TAG already exists. Pick a different version."
|
echo "::error::Tag $TAG already exists. Pick a different version."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
if gh release view "$TAG" >/dev/null 2>&1; then
|
||||||
# Replaced `gh release view` with a direct HTTP check to the Gitea API
|
|
||||||
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
|
|
||||||
-H "Authorization: token $GITEA_TOKEN" \
|
|
||||||
"$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/releases/tags/$TAG")
|
|
||||||
|
|
||||||
if [ "$HTTP_STATUS" -eq 200 ]; then
|
|
||||||
echo "::error::Release $TAG already exists. Pick a different version."
|
echo "::error::Release $TAG already exists. Pick a different version."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
@@ -170,16 +164,16 @@ jobs:
|
|||||||
with open(path, encoding="utf-8") as fh:
|
with open(path, encoding="utf-8") as fh:
|
||||||
version = json.load(fh)["version"]
|
version = json.load(fh)["version"]
|
||||||
if version != expected:
|
if version != expected:
|
||||||
raise SystemExit(f":error::packed manifest says {version}, expected {expected}")
|
raise SystemExit(f"::error::packed manifest says {version}, expected {expected}")
|
||||||
print(f"manifest.json is at the archive root and reports {version}")
|
print(f"manifest.json is at the archive root and reports {version}")
|
||||||
PY
|
PY
|
||||||
|
|
||||||
(cd "$out" && sha256sum "${MOD_ID}"-*.zip > sha256sums.txt)
|
(cd "$out" && sha256sum "${MOD_ID}"-*.zip > sha256sums.txt)
|
||||||
cat "$out/sha256sums.txt"
|
cat "$out/sha256sums.txt"
|
||||||
|
|
||||||
- name: Publish Gitea Release
|
- name: Publish GitHub Release
|
||||||
env:
|
env:
|
||||||
GITEA_TOKEN: ${{ github.token }}
|
GH_TOKEN: ${{ github.token }}
|
||||||
VERSION: ${{ steps.ver.outputs.version }}
|
VERSION: ${{ steps.ver.outputs.version }}
|
||||||
TAG: ${{ steps.ver.outputs.tag }}
|
TAG: ${{ steps.ver.outputs.tag }}
|
||||||
MOD_ID: "DRAMATIC_SHAPE"
|
MOD_ID: "DRAMATIC_SHAPE"
|
||||||
@@ -196,34 +190,11 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
printf 'Release notes:\n%s\n' "$notes"
|
printf 'Release notes:\n%s\n' "$notes"
|
||||||
|
|
||||||
# Create the release via Gitea's REST API
|
gh release create "$TAG" \
|
||||||
RESPONSE=$(curl -s -X POST "$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/releases" \
|
--target "$GITHUB_SHA" \
|
||||||
-H "Authorization: token $GITEA_TOKEN" \
|
--title "$VERSION" \
|
||||||
-H "Content-Type: application/json" \
|
--notes "$notes" \
|
||||||
-d "$(jq -n \
|
"dist/${MOD_ID}-${VERSION}.zip" \
|
||||||
--arg tag "$TAG" \
|
"dist/sha256sums.txt"
|
||||||
--arg target "$GITHUB_SHA" \
|
|
||||||
--arg name "$VERSION" \
|
|
||||||
--arg body "$notes" \
|
|
||||||
'{tag_name: $tag, target_commitish: $target, name: $name, body: $body}')"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Extract the numeric release ID needed to upload assets
|
|
||||||
RELEASE_ID=$(echo "$RESPONSE" | jq -r '.id')
|
|
||||||
|
|
||||||
if [ "$RELEASE_ID" = "null" ] || [ -z "$RELEASE_ID" ]; then
|
|
||||||
echo "::error::Failed to create release. Gitea responded with: $RESPONSE"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Upload the Mod .zip asset to the newly created release
|
|
||||||
curl -s -f -X POST "$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/releases/$RELEASE_ID/assets" \
|
|
||||||
-H "Authorization: token $GITEA_TOKEN" \
|
|
||||||
-F "attachment=@dist/${MOD_ID}-${VERSION}.zip"
|
|
||||||
|
|
||||||
# Upload the sha256sums.txt file
|
|
||||||
curl -s -f -X POST "$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/releases/$RELEASE_ID/assets" \
|
|
||||||
-H "Authorization: token $GITEA_TOKEN" \
|
|
||||||
-F "attachment=@dist/sha256sums.txt"
|
|
||||||
|
|
||||||
echo "Published release $TAG"
|
echo "Published release $TAG"
|
||||||
@@ -131,91 +131,6 @@
|
|||||||
strips the way a facade's does) and no voxels: the recess removes as
|
strips the way a facade's does) and no voxels: the recess removes as
|
||||||
many faces as it exposes.
|
many faces as it exposes.
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- **The Pokemon Center's steps climb.** The Cable Club stairs, cut into
|
|
||||||
the back wall of all eleven Centers, were built as a stairwell sunk
|
|
||||||
into the floor -- they lead UP to the Center's second floor. The
|
|
||||||
head-on stair reading was right and stays: a drawn ROW is a step and
|
|
||||||
drawn row is depth row, 1:1 into the opening, the drawing's own band
|
|
||||||
table landing exactly on four steps and its black edge columns walling
|
|
||||||
the opening. Only the sign of the rise was wrong, and two things follow
|
|
||||||
from it. The risers turn around -- a flight descending away from you
|
|
||||||
closes its steps from below and shows you their backs, one climbing
|
|
||||||
away shows you their fronts -- and the black edge columns become the
|
|
||||||
walls of the opening the flight climbs into, running from each tread up
|
|
||||||
to the top of the wall band rather than down from the floor to it. The
|
|
||||||
top step lands level with that band, in the dark rows the artist drew
|
|
||||||
there, so the flight fills the opening it leaves by. The descending
|
|
||||||
class (`stair_down_n`) is unchanged and still available; the Centers
|
|
||||||
now pin `stair_n`.
|
|
||||||
|
|
||||||
- **Nothing is hung on the TOP of an interior wall.** A wall band is 16px
|
|
||||||
of art folded upright over a run two drawn rows deep, so it folds
|
|
||||||
entirely onto its south face and has no drawn row left to lay flat on
|
|
||||||
top -- and the top then repeated the face. The town house's town-map
|
|
||||||
poster (cell (3,0)) and its window ((5,0)), and the Pokemon Center's
|
|
||||||
pokeball poster ((3,0) and (4,0)), each came out lying across the top
|
|
||||||
of the wall as well as hanging on it: a picture you look DOWN on.
|
|
||||||
|
|
||||||
What is really up there is the wall's own capping course, which is
|
|
||||||
exactly the plain panel the decorated column's neighbours draw --
|
|
||||||
`wall_top` in `data/voxel_heights.lua` names it per tileset (HOUSE caps
|
|
||||||
with the blank course, POKECENTER with the striped panel cell (9,0)
|
|
||||||
draws). Per tileset rather than per tile because one room caps with one
|
|
||||||
course, and because "plain" is a fact about the drawing that no
|
|
||||||
measurement of the geometry can recover. Only the top face is
|
|
||||||
redirected; the poster still faces the room.
|
|
||||||
|
|
||||||
Five rooms take it. `HOUSE`, `POKECENTER`, `REDS_HOUSE_1` and
|
|
||||||
`REDS_HOUSE_2` name one course for every wall in the atlas -- each of
|
|
||||||
those dresses one kind of room, and a list keyed by the decorated tiles
|
|
||||||
would need extending every time a map hung something new on the same
|
|
||||||
wall. `LOBBY` names the tiles instead (`{ [40] = 93, [56] = 93 }`): the
|
|
||||||
Rocket lift's car doors cap with the cabin frame, and the department
|
|
||||||
store, the Game Corner, Silph's floors and the roof -- all on that one
|
|
||||||
atlas -- keep exactly the tops they had. The doors are also the reason
|
|
||||||
the cap is applied in the mesher's DETECTED-run branch as well as its
|
|
||||||
pinned one; Structures finds them rather than a pin naming them.
|
|
||||||
|
|
||||||
- **Lance's room is furnished with the badge gyms' bird statue.** It is
|
|
||||||
the gyms' statue tile for tile on the DOJO atlas -- one cell of figure
|
|
||||||
($02/$38/$12/$13) over one cell of plinth ($22/$23/$32/$33) -- and left
|
|
||||||
derived the pair merged into one 32px volume wearing the statue folded
|
|
||||||
onto its face. The extruded picture, the same failure the gyms' statues
|
|
||||||
and the Plateau's avenue had, and it takes the same answer: the plinth a
|
|
||||||
solid 16px block, the bird a per-pixel cutout 5 voxels deep riding its
|
|
||||||
top face. Every placement of those eight tiles in the game is a statue
|
|
||||||
-- 18 in LANCES_ROOM and 2 in FIGHTING_DOJO -- and Oak's Lab, the third
|
|
||||||
map on the atlas, places none of them.
|
|
||||||
|
|
||||||
- **A wall cut into a terrace inherits terrace, never the statue standing
|
|
||||||
on it.** `bookcase_backfill = "above"` hands a collapsed rank's vacated
|
|
||||||
rows the cell above the run, so the League's gate walls have more
|
|
||||||
hillside behind them rather than a trench. Indigo Plateau's avenue
|
|
||||||
statues stand directly on the pilasters that collapse that way, so what
|
|
||||||
every one of them inherited was the BIRD: the figure's shape and art
|
|
||||||
copied onto two more rows down the shaft, and each statue came out two
|
|
||||||
deep behind itself. Only bodies backfill now -- flat, top and upright.
|
|
||||||
A per-pixel standee above (a statue, a sign, a bush) is an object
|
|
||||||
standing ON the terrace rather than terrace, so the row has nothing to
|
|
||||||
inherit and takes the default synthesized ground.
|
|
||||||
|
|
||||||
- **...and a statue on a collapsed pilaster stands ON it.** The duplicate
|
|
||||||
above was masking a second fault. A standee finds its support by reading
|
|
||||||
the cell below its own drawing, and the bookcase collapse MOVES the box
|
|
||||||
it finds: the whole four-row pilaster walks onto its southmost cell,
|
|
||||||
which on the Plateau is a full cell south of where the test looked. So
|
|
||||||
the bird was lifted to the right HEIGHT and left standing over open
|
|
||||||
ground with its pillar behind it -- invisible while the vacated rows
|
|
||||||
were being filled with copies of the bird itself, obvious the moment
|
|
||||||
they were not. Every row of a collapsed rank now records the row its box
|
|
||||||
actually stands on (`S.bookcaseBox`), and a standee supported by one is
|
|
||||||
placed there instead of at its drawn position. Supports that do not move
|
|
||||||
-- the gyms' plinths, furniture, `building` claims -- are unaffected.
|
|
||||||
The plinth keeps its elevation and the statue extends exactly one cell
|
|
||||||
above it.
|
|
||||||
|
|
||||||
## 1.8.0
|
## 1.8.0
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
+18
-94
@@ -50,9 +50,6 @@
|
|||||||
-- stair_down_e / _down_w a sunken stairwell: the cell opens into a
|
-- stair_down_e / _down_w a sunken stairwell: the cell opens into a
|
||||||
-- hole with steps descending toward the named
|
-- hole with steps descending toward the named
|
||||||
-- side -- stairs that lead DOWN a floor
|
-- side -- stairs that lead DOWN a floor
|
||||||
-- stair_n / stair_down_n the same two flights running into the map
|
|
||||||
-- rather than across it, for a staircase drawn
|
|
||||||
-- HEAD-ON: a drawn row is a step, not a column
|
|
||||||
-- relief a prop drawn from above (a console on the
|
-- relief a prop drawn from above (a console on the
|
||||||
-- floor): the drawing stays flat and the
|
-- floor): the drawing stays flat and the
|
||||||
-- pixels inside its black outline extrude a
|
-- pixels inside its black outline extrude a
|
||||||
@@ -132,24 +129,6 @@
|
|||||||
-- measured rather than drawn, by flooding the panel tile's own stripe out
|
-- measured rather than drawn, by flooding the panel tile's own stripe out
|
||||||
-- from behind them; see CLUB below.
|
-- from behind them; see CLUB below.
|
||||||
--
|
--
|
||||||
-- And `wall_top` (not a class either): what a `wall` cell's TOP face
|
|
||||||
-- wears, whatever the cell itself draws. An interior wall band is 16px of
|
|
||||||
-- art folded upright, and a fully folded run has no drawn row left over to
|
|
||||||
-- lay flat on top -- so the top repeated the FACE, and the town-map poster
|
|
||||||
-- and the window of a house came out lying across the top of the wall as
|
|
||||||
-- well as hanging on it. What is really up there is the wall's own
|
|
||||||
-- capping course, which is exactly the plain panel the decorated column's
|
|
||||||
-- neighbours draw. The face is untouched -- the poster still faces the
|
|
||||||
-- room. Two forms:
|
|
||||||
--
|
|
||||||
-- wall_top = <id> every wall in the tileset caps with this,
|
|
||||||
-- for an atlas that dresses one kind of room
|
|
||||||
-- (the houses, the Centers, Red's floors)
|
|
||||||
-- wall_top = { [tile] = id } only the named tiles are redirected, for
|
|
||||||
-- an atlas that dresses several (LOBBY is
|
|
||||||
-- the department store, the Game Corner,
|
|
||||||
-- Silph, the roof AND the Rocket lift)
|
|
||||||
--
|
|
||||||
-- Whole BUILDINGS are not tile pins -- one drawing packs a roof seen from
|
-- Whole BUILDINGS are not tile pins -- one drawing packs a roof seen from
|
||||||
-- above, a facade seen face-on and sloped ends as diagonal silhouettes,
|
-- above, a facade seen face-on and sloped ends as diagonal silhouettes,
|
||||||
-- and no single class covers that. They live in the `buildings` list at
|
-- and no single class covers that. They live in the `buildings` list at
|
||||||
@@ -779,38 +758,12 @@ return {
|
|||||||
signpost = { 33, 34, 49, 50 },
|
signpost = { 33, 34, 49, 50 },
|
||||||
},
|
},
|
||||||
|
|
||||||
-- Oak's Lab, the Fighting Dojo and Lance's room (one tileset). The
|
-- Oak's Lab (the tileset also serves the Fighting Dojo and Lance's
|
||||||
-- lab's free-standing shelf ranks: book rows and base pinned; the
|
-- room, which use none of these tiles). The free-standing shelf
|
||||||
-- shared trim tiles above (41/42, also the lab tables' corners) are
|
-- ranks: book rows and base pinned; the shared trim tiles above
|
||||||
-- adopted as caps by the bookcase builder rather than pinned. The
|
-- (41/42, also the lab tables' corners) are adopted as caps by the
|
||||||
-- other two rooms are furnished with one thing between them -- the
|
-- bookcase builder rather than pinned.
|
||||||
-- BIRD STATUE, and it is the badge gyms' statue exactly.
|
|
||||||
DOJO = {
|
DOJO = {
|
||||||
-- THE STATUES. The same drawing as GYM's, tile for tile on this
|
|
||||||
-- atlas: one cell of figure ($02/$38/$12/$13) over one cell of
|
|
||||||
-- plinth ($22/$23/$32/$33), so it takes the same treatment -- the
|
|
||||||
-- plinth a SOLID 16px block, the figure a per-pixel cutout 5 voxels
|
|
||||||
-- deep (the thin `prop` pool) riding the plinth's top face through
|
|
||||||
-- the authored-box support rule and collapsing to the plinth's
|
|
||||||
-- single cell of footprint.
|
|
||||||
--
|
|
||||||
-- Left derived the pair merged into ONE 32px volume wearing the
|
|
||||||
-- statue art folded onto its face -- the extruded picture, the same
|
|
||||||
-- failure the gyms' statues and the Plateau's avenue had.
|
|
||||||
--
|
|
||||||
-- Every placement of these eight tiles in the game is a statue:
|
|
||||||
-- blocks 49/50/114/115 pack figure over plinth in one 2x2-cell
|
|
||||||
-- block, and they are placed 18 times in LANCES_ROOM (the pairs
|
|
||||||
-- lining his aisle, and the two flanking his dais at cells (6,12)
|
|
||||||
-- and (7,12) over (6,13)/(7,13)) and twice in FIGHTING_DOJO. Oak's
|
|
||||||
-- Lab, the third map on this atlas, places none of them -- and the
|
|
||||||
-- four figure-only and plinth-only blocks are never placed at all.
|
|
||||||
wall = { 34, 35, 50, 51 },
|
|
||||||
prop = { 2, 18, 19, 56 },
|
|
||||||
-- and each stands on the room's main floor ($11) rather than on
|
|
||||||
-- whatever its neighbours vote -- the gyms' rule, for the gyms'
|
|
||||||
-- reason: a statue against a wall would otherwise take the wall.
|
|
||||||
prop_ground = { [2] = 17, [18] = 17, [19] = 17, [56] = 17 },
|
|
||||||
bookcase = { 13, 14, 29, 30 },
|
bookcase = { 13, 14, 29, 30 },
|
||||||
-- the lab tables (the starter-ball display and the north tables):
|
-- the lab tables (the starter-ball display and the north tables):
|
||||||
-- 41/42 are also the shelf trim the bookcase builder adopts as
|
-- 41/42 are also the shelf trim the bookcase builder adopts as
|
||||||
@@ -834,11 +787,6 @@ return {
|
|||||||
REDS_HOUSE_2 = {
|
REDS_HOUSE_2 = {
|
||||||
-- the wall band with its windows stays one 16px face
|
-- the wall band with its windows stays one 16px face
|
||||||
wall = { 0, 36, 37, 52, 53 },
|
wall = { 0, 36, 37, 52, 53 },
|
||||||
-- and caps with the blank course. The windows of Red's 2F sit at
|
|
||||||
-- cells (5,0) and (7,0); without this the panes came out lying
|
|
||||||
-- across the top of the wall as well as glazing its face, where
|
|
||||||
-- (6,0) between them draws the plain panel that belongs up there
|
|
||||||
wall_top = 0,
|
|
||||||
-- the bed: a mattress drawn from above, half a block high
|
-- the bed: a mattress drawn from above, half a block high
|
||||||
bed = { 45, 46, 47, 61, 62, 63 },
|
bed = { 45, 46, 47, 61, 62, 63 },
|
||||||
-- stools: a seat-high box, seat art on top, legs on the front
|
-- stools: a seat-high box, seat art on top, legs on the front
|
||||||
@@ -877,9 +825,6 @@ return {
|
|||||||
-- table rides these heights, not the 8/12px class defaults
|
-- table rides these heights, not the 8/12px class defaults
|
||||||
heights = { stool = 5, table = 6 },
|
heights = { stool = 5, table = 6 },
|
||||||
wall = { 0, 36, 37, 52, 53 },
|
wall = { 0, 36, 37, 52, 53 },
|
||||||
-- the same blank course caps 1F, whose windows are cells (3,0),
|
|
||||||
-- (5,0) and (7,0) -- (6,0) between the last two is the panel
|
|
||||||
wall_top = 0,
|
|
||||||
stool = { 2, 3, 18, 19 },
|
stool = { 2, 3, 18, 19 },
|
||||||
-- the dining table (38-44/58-60); its top row also caps the
|
-- the dining table (38-44/58-60); its top row also caps the
|
||||||
-- bookcases below
|
-- bookcases below
|
||||||
@@ -915,12 +860,6 @@ return {
|
|||||||
-- the wall band stays one 16px face: blank courses, the window,
|
-- the wall band stays one 16px face: blank courses, the window,
|
||||||
-- the framed picture, and the schoolhouse blackboard (72-75/88-91)
|
-- the framed picture, and the schoolhouse blackboard (72-75/88-91)
|
||||||
wall = { 0, 36, 45, 46, 52, 61, 62, 72, 73, 75, 88, 89, 90, 91 },
|
wall = { 0, 36, 45, 46, 52, 61, 62, 72, 73, 75, 88, 89, 90, 91 },
|
||||||
-- and it caps with the blank course. Cells (3,0) and (5,0) of the
|
|
||||||
-- town house are the town-map poster (45/46 over 61/62) and the
|
|
||||||
-- window (36 over 52); without this the top of the wall wore the
|
|
||||||
-- poster and the glass, laid flat, instead of the plain panel their
|
|
||||||
-- own left-hand neighbour draws
|
|
||||||
wall_top = 0,
|
|
||||||
-- stools: a seat-high box that also seats Daisy
|
-- stools: a seat-high box that also seats Daisy
|
||||||
stool = { 2, 3, 18, 19 },
|
stool = { 2, 3, 18, 19 },
|
||||||
-- the dining table (top edge 38/41 also caps the bookcases);
|
-- the dining table (top edge 38/41 also caps the bookcases);
|
||||||
@@ -966,28 +905,21 @@ return {
|
|||||||
-- two flanks -- see `prop` below.
|
-- two flanks -- see `prop` below.
|
||||||
wall = { 2, 3, 4, 5, 6, 16, 18, 19, 20, 21, 22, 40, 41,
|
wall = { 2, 3, 4, 5, 6, 16, 18, 19, 20, 21, 22, 40, 41,
|
||||||
76, 77 },
|
76, 77 },
|
||||||
-- and it caps with the striped panel, the tile cell (9,0) draws.
|
|
||||||
-- The pokeball poster spans cells (3,0) and (4,0) (2/3 over 18/19),
|
|
||||||
-- and without this the top of the wall wore the poster lying flat as
|
|
||||||
-- well as hanging it on the face
|
|
||||||
wall_top = 40,
|
|
||||||
-- THE CABLE CLUB STEPS, cut into the back wall at cells (10,0) and
|
-- THE CABLE CLUB STEPS, cut into the back wall at cells (10,0) and
|
||||||
-- (12,0) of every Center -- a flight going UP, away from the room, to
|
-- (12,0) of every Center -- a flight going DOWN, away from the room,
|
||||||
-- the Center's second floor, and the reason the head-on stair classes
|
-- and the reason `stair_down_n` exists at all: the profile's other
|
||||||
-- exist at all: the profile's other stairs run east or west and are
|
-- stairs run east or west and are drawn from the SIDE, where a drawn
|
||||||
-- drawn from the SIDE, where a drawn column is a step; these are drawn
|
-- column is a step; these are drawn HEAD-ON, where a drawn row is,
|
||||||
-- HEAD-ON, where a drawn row is, and no rotation of the east/west
|
-- and no rotation of the east/west reading produces that.
|
||||||
-- reading produces that.
|
|
||||||
--
|
--
|
||||||
-- The drawing is its own band table, and it lands exactly on an even
|
-- The drawing is its own band table, and it lands exactly on an even
|
||||||
-- four-step division of the cell: 4 white rows (the near tread, the
|
-- four-step division of the cell: 4 white rows (the near tread), a
|
||||||
-- one at floor level), a black nosing, 3 grey, a nosing, 3 checker,
|
-- black nosing, 3 grey, a nosing, 3 checker, then 4 black rows -- the
|
||||||
-- then 4 black rows -- the dark the flight climbs into, which is the
|
-- dark the flight leaves into, which is also what the far end wall
|
||||||
-- top step, level with the wall band it is cut through. Its first and
|
-- wears. Its first and last COLUMNS are the well's black side walls.
|
||||||
-- last COLUMNS are the opening's black side walls. Drawn row = depth
|
-- Drawn row = depth row throughout; the rise is the only number the
|
||||||
-- row throughout; the rise is the only number the head-on view cannot
|
-- head-on view cannot state, and it takes the class height over the
|
||||||
-- state, and it takes the class height over the four steps like every
|
-- four steps like every other flight here.
|
||||||
-- other flight here.
|
|
||||||
--
|
--
|
||||||
-- Pinned as one cell (the class resolves off the top-left tile) but
|
-- Pinned as one cell (the class resolves off the top-left tile) but
|
||||||
-- all four ids carry it, and the scan says they cannot reach anything
|
-- all four ids carry it, and the scan says they cannot reach anything
|
||||||
@@ -995,7 +927,7 @@ return {
|
|||||||
-- Centers, and the Celadon Hotel on the same id places none of them.
|
-- Centers, and the Celadon Hotel on the same id places none of them.
|
||||||
-- 94 is also the map's warp tile; pins are look-only, so the warp is
|
-- 94 is also the map's warp tile; pins are look-only, so the warp is
|
||||||
-- untouched and the steps stay walk-through.
|
-- untouched and the steps stay walk-through.
|
||||||
stair_n = { 92, 93, 94, 95 },
|
stair_down_n = { 92, 93, 94, 95 },
|
||||||
-- the counters, half a cell high: top band (8) and the one cell of
|
-- the counters, half a cell high: top band (8) and the one cell of
|
||||||
-- it that carries the push bell (10, lifted off as a figure below --
|
-- it that carries the push bell (10, lifted off as a figure below --
|
||||||
-- the pin stays as the degradation path), front face (24/25, the
|
-- the pin stays as the degradation path), front face (24/25, the
|
||||||
@@ -1657,14 +1589,6 @@ return {
|
|||||||
wall = { 1, 2, 3, 6, 18, 19, 22, 33, 46, 47,
|
wall = { 1, 2, 3, 6, 18, 19, 22, 33, 46, 47,
|
||||||
62, 63, 68, 70, 71, 72, 73, 75, 76, 77, 78, 79, 84,
|
62, 63, 68, 70, 71, 72, 73, 75, 76, 77, 78, 79, 84,
|
||||||
88, 89, 91, 92, 93 },
|
88, 89, 91, 92, 93 },
|
||||||
-- The Rocket lift's CAR DOORS (40 over 56, cells (2,1) and (3,1) of
|
|
||||||
-- ROCKET_HIDEOUT_ELEVATOR) cap with the cabin frame's lower course
|
|
||||||
-- -- the tile cell (2,0) draws beneath its own top band, and what
|
|
||||||
-- every other column of that wall already caps with. Keyed by tile
|
|
||||||
-- rather than blanket, unlike the houses: this one atlas dresses the
|
|
||||||
-- department store, the Game Corner, Silph's floors and the roof
|
|
||||||
-- too, and none of those wall tops is a lift frame.
|
|
||||||
wall_top = { [40] = 93, [56] = 93 },
|
|
||||||
-- 3F's television sets ($0E/$0F/$1E/$1F): the one drawing in this
|
-- 3F's television sets ($0E/$0F/$1E/$1F): the one drawing in this
|
||||||
-- tileset that is a deliberate object with a body -- a black-framed
|
-- tileset that is a deliberate object with a body -- a black-framed
|
||||||
-- cabinet, a bezel and a lit screen, drawn face-on -- and the same
|
-- cabinet, a bezel and a lit screen, drawn face-on -- and the same
|
||||||
|
|||||||
+1
-19
@@ -301,19 +301,6 @@ local function runGeometry(map, bodyOnly, masks, sink, waterSink)
|
|||||||
return tile
|
return tile
|
||||||
end
|
end
|
||||||
|
|
||||||
-- The capping course an interior wall wears on its TOP face (see
|
|
||||||
-- TileShape.wallTop). A wall band folds entirely onto its own face, so
|
|
||||||
-- the top had nothing left to lay flat and repeated the face -- a house's
|
|
||||||
-- town-map poster and window, a Center's pokeball poster and the Rocket
|
|
||||||
-- lift's doors came out lying across the top of the wall as well as
|
|
||||||
-- standing in it. Only the top is redirected: the face still draws what
|
|
||||||
-- the map draws.
|
|
||||||
local wallTop = TileShape.wallTop(tileset.id)
|
|
||||||
local function capOf(s, tile)
|
|
||||||
if not (wallTop and s.class == "wall") then return nil end
|
|
||||||
return wallTop(tile)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- one atlas-rect UV, optionally cropped to art rows [vTop, vBot] of 8
|
-- one atlas-rect UV, optionally cropped to art rows [vTop, vBot] of 8
|
||||||
local function uvRect(tile, vTop, vBot)
|
local function uvRect(tile, vTop, vBot)
|
||||||
local ax = (tile % perRow) * 8
|
local ax = (tile % perRow) * 8
|
||||||
@@ -599,11 +586,6 @@ local function runGeometry(map, bodyOnly, masks, sink, waterSink)
|
|||||||
{ { u0, v1 }, { u1, v1 }, { u1, v0 }, { u0, v0 } }, 0.95)
|
{ { u0, v1 }, { u1, v1 }, { u1, v0 }, { u0, v0 } }, 0.95)
|
||||||
elseif run then
|
elseif run then
|
||||||
local topTile = map:tileAt(tx, ChunkMesher.flatTopRow(run, ty))
|
local topTile = map:tileAt(tx, ChunkMesher.flatTopRow(run, ty))
|
||||||
-- a DETECTED wall volume caps the same way a pinned one does:
|
|
||||||
-- the Rocket lift's cabin doors are found rather than pinned,
|
|
||||||
-- and their drawing lay across the top of the wall they are set
|
|
||||||
-- into (see wallTop)
|
|
||||||
topTile = capOf(s, tile) or topTile
|
|
||||||
topQuad(x0, z0, h, topTile, VOLUME_TOP_SHADE)
|
topQuad(x0, z0, h, topTile, VOLUME_TOP_SHADE)
|
||||||
else
|
else
|
||||||
local topTile = tile
|
local topTile = tile
|
||||||
@@ -667,7 +649,7 @@ local function runGeometry(map, bodyOnly, masks, sink, waterSink)
|
|||||||
row = (above and above.authored and above.art == "upright")
|
row = (above and above.authored and above.art == "upright")
|
||||||
and (north - 1) or north
|
and (north - 1) or north
|
||||||
end
|
end
|
||||||
topTile = capOf(s, tile) or S.tileAt[keyOf(tx, row)]
|
topTile = S.tileAt[keyOf(tx, row)]
|
||||||
end
|
end
|
||||||
-- water's surface, and only water's: the recessed sheet itself,
|
-- water's surface, and only water's: the recessed sheet itself,
|
||||||
-- never the ground's shoreline bands around it. A cell an object
|
-- never the ground's shoreline bands around it. A cell an object
|
||||||
|
|||||||
+27
-95
@@ -227,11 +227,7 @@ function Structures.forMap(map)
|
|||||||
S = { shapeAt = shapeAt, tileAt = tileAt, outdoor = Map.isOutdoor(def),
|
S = { shapeAt = shapeAt, tileAt = tileAt, outdoor = Map.isOutdoor(def),
|
||||||
hideBareRing = hullRingOnly or nil,
|
hideBareRing = hullRingOnly or nil,
|
||||||
runs = {}, skip = {}, ground = {}, doorFold = {}, objectQuads = {},
|
runs = {}, skip = {}, ground = {}, doorFold = {}, objectQuads = {},
|
||||||
grassQuads = {}, flowerQuads = {}, roundStamps = {}, figures = {},
|
grassQuads = {}, flowerQuads = {}, roundStamps = {}, figures = {} }
|
||||||
-- tile key -> the row a collapsed bookcase rank's box actually
|
|
||||||
-- stands on, so a standee supported by one lands on it rather than
|
|
||||||
-- where the drawing put it (see buildBookcases)
|
|
||||||
bookcaseBox = {} }
|
|
||||||
Buildings.build(S, map, pixels(tileset), perRow)
|
Buildings.build(S, map, pixels(tileset), perRow)
|
||||||
|
|
||||||
-- Fold doors into their buildings. A door cell is WALKABLE (the player
|
-- Fold doors into their buildings. A door cell is WALKABLE (the player
|
||||||
@@ -1886,12 +1882,6 @@ local function bookcaseRank(S, map, perRow, run, i, j, k, pane, srcU, srcV,
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- The arts a `bookcase_backfill = "above"` row may inherit: terrain and
|
|
||||||
-- solid bodies only (see the note at the backfill itself). Everything
|
|
||||||
-- absent here -- billboard, post, cylinder, grass, flower -- is a per-pixel
|
|
||||||
-- object STANDING on terrain rather than terrain.
|
|
||||||
local BACKFILL_ART = { flat = true, top = true, upright = true }
|
|
||||||
|
|
||||||
function Structures.buildBookcases(S, map, x0, x1, y0, y1, data, perRow)
|
function Structures.buildBookcases(S, map, x0, x1, y0, y1, data, perRow)
|
||||||
perRow = perRow or map.tileset.tilesPerRow or 16
|
perRow = perRow or map.tileset.tilesPerRow or 16
|
||||||
-- What to do with the rows a rank VACATES (see TileShape.bookcaseBackfill).
|
-- What to do with the rows a rank VACATES (see TileShape.bookcaseBackfill).
|
||||||
@@ -1938,32 +1928,11 @@ function Structures.buildBookcases(S, map, x0, x1, y0, y1, data, perRow)
|
|||||||
-- shelf standing in a room. `bookcase_backfill = "above"` hands it
|
-- shelf standing in a room. `bookcase_backfill = "above"` hands it
|
||||||
-- the cell above the run instead, shape and art, so a wall cut into
|
-- the cell above the run instead, shape and art, so a wall cut into
|
||||||
-- a terrace has more terrace behind it rather than a trench.
|
-- a terrace has more terrace behind it rather than a trench.
|
||||||
--
|
|
||||||
-- Only BODY above backfills: a vacated row wants more of the
|
|
||||||
-- terrace the wall is cut into, and the terrace is whatever lies
|
|
||||||
-- flat, tops out or stands as a solid face. A per-pixel STANDEE
|
|
||||||
-- above -- a statue, a sign, a bush -- is an object standing ON
|
|
||||||
-- that terrace, and copying it northward builds a second and a
|
|
||||||
-- third of it: Indigo Plateau's avenue statues sit directly on
|
|
||||||
-- the pilasters that collapse here, so every bird came out
|
|
||||||
-- duplicated twice down the shaft behind itself. A standee
|
|
||||||
-- above means the row has no terrace to inherit, so it takes the
|
|
||||||
-- default and is painted with synthesized ground.
|
|
||||||
local covered = math.min(2, front - top + 1)
|
local covered = math.min(2, front - top + 1)
|
||||||
local srcK = keyOf(tx, top - 1)
|
local srcK = keyOf(tx, top - 1)
|
||||||
local src = backfill == "above" and S.shapeAt[srcK] or nil
|
local src = backfill == "above" and S.shapeAt[srcK] or nil
|
||||||
if src and not BACKFILL_ART[src.art] then src = nil end
|
|
||||||
-- Where the box ACTUALLY ends up, remembered for every row of the
|
|
||||||
-- rank: the collapse walks the whole drawn run onto its southmost
|
|
||||||
-- cell, so anything that has to stand ON the box has to be told
|
|
||||||
-- where the box went. A statue keys off the cell below its own
|
|
||||||
-- drawing, which is the run's NORTH end -- two rows away from the
|
|
||||||
-- box on a two-cell pilaster, which is exactly the distance the
|
|
||||||
-- Plateau's birds floated by.
|
|
||||||
local boxTop = front - covered + 1
|
|
||||||
for cy = top, front do
|
for cy = top, front do
|
||||||
local tk = keyOf(tx, cy)
|
local tk = keyOf(tx, cy)
|
||||||
S.bookcaseBox[tk] = boxTop
|
|
||||||
if src and cy <= front - covered then
|
if src and cy <= front - covered then
|
||||||
S.shapeAt[tk] = src
|
S.shapeAt[tk] = src
|
||||||
S.tileAt[tk] = S.tileAt[srcK]
|
S.tileAt[tk] = S.tileAt[srcK]
|
||||||
@@ -2023,10 +1992,6 @@ end
|
|||||||
-- walls) wear the matching slice of that drawing -- the railing's
|
-- walls) wear the matching slice of that drawing -- the railing's
|
||||||
-- diagonal lands along the stepped silhouette -- while treads sample the
|
-- diagonal lands along the stepped silhouette -- while treads sample the
|
||||||
-- art band drawn at their own height.
|
-- art band drawn at their own height.
|
||||||
--
|
|
||||||
-- stair_n / stair_down_n are the same pair of flights running INTO the
|
|
||||||
-- map rather than across it, for a staircase drawn head-on; that changes
|
|
||||||
-- the art reading enough to need its own branch below.
|
|
||||||
local STAIR_STEPS = 4
|
local STAIR_STEPS = 4
|
||||||
|
|
||||||
local STAIR_SHADE = { south = 1.0, north = 0.68, tread = 1.0,
|
local STAIR_SHADE = { south = 1.0, north = 0.68, tread = 1.0,
|
||||||
@@ -2039,8 +2004,8 @@ local function stairCell(S, map, data, cx, cy, s)
|
|||||||
local atlasW = map.tileset.imageWidth or 128
|
local atlasW = map.tileset.imageWidth or 128
|
||||||
local atlasH = map.tileset.imageHeight or 48
|
local atlasH = map.tileset.imageHeight or 48
|
||||||
local quads = S.objectQuads
|
local quads = S.objectQuads
|
||||||
local north = s.class == "stair_n" or s.class == "stair_down_n"
|
local north = s.class == "stair_down_n"
|
||||||
local down = s.class == "stair_down_n" or s.class == "stair_down_e"
|
local down = north or s.class == "stair_down_e"
|
||||||
or s.class == "stair_down_w"
|
or s.class == "stair_down_w"
|
||||||
local east = s.class == "stair_e" or s.class == "stair_down_e"
|
local east = s.class == "stair_e" or s.class == "stair_down_e"
|
||||||
local mx, mz = cx * 16, cy * 16
|
local mx, mz = cx * 16, cy * 16
|
||||||
@@ -2112,17 +2077,6 @@ local function stairCell(S, map, data, cx, cy, s)
|
|||||||
-- COLUMNS are its black side walls, and its top band is the darkness the
|
-- COLUMNS are its black side walls, and its top band is the darkness the
|
||||||
-- flight leaves by, which is what the far end wants to wear.
|
-- flight leaves by, which is what the far end wants to wear.
|
||||||
--
|
--
|
||||||
-- A flight CLIMBING away (`stair_n`) is the same reading with the sign of
|
|
||||||
-- the rise flipped -- bands still run south to north, drawn row is still
|
|
||||||
-- depth row, the nosing still serves twice. Two things follow from the
|
|
||||||
-- sign. The risers turn around: a flight descending away from you closes
|
|
||||||
-- its steps from below and shows you their backs, one climbing away shows
|
|
||||||
-- you their FRONTS, so they face south. And the drawing's black side
|
|
||||||
-- columns stop being a well's walls and become the walls of the opening
|
|
||||||
-- the flight climbs into: they run from each tread UP to the top of the
|
|
||||||
-- wall band rather than down from the floor. At the last step the flight
|
|
||||||
-- has reached that top and there is no opening left to wall.
|
|
||||||
--
|
|
||||||
-- Every quad here is split at the cell's own 8px seam, in x and in rows
|
-- Every quad here is split at the cell's own 8px seam, in x and in rows
|
||||||
-- both: `uv` resolves ONE tile per corner, and these four tiles are not
|
-- both: `uv` resolves ONE tile per corner, and these four tiles are not
|
||||||
-- neighbours in the atlas, so a quad that spans a seam interpolates
|
-- neighbours in the atlas, so a quad that spans a seam interpolates
|
||||||
@@ -2133,8 +2087,7 @@ local function stairCell(S, map, data, cx, cy, s)
|
|||||||
for i = 0, STAIR_STEPS - 1 do
|
for i = 0, STAIR_STEPS - 1 do
|
||||||
local a0 = 16 - (i + 1) * runD -- band i, in art rows
|
local a0 = 16 - (i + 1) * runD -- band i, in art rows
|
||||||
local a1 = a0 + runD
|
local a1 = a0 + runD
|
||||||
local yTop = (down and -1 or 1) * (i + 1) * rise
|
local yTop = -(i + 1) * rise
|
||||||
local ry = (down and -1 or 1) * i * rise -- the step behind it
|
|
||||||
local z0b, z1b = mz + a0, mz + a1
|
local z0b, z1b = mz + a0, mz + a1
|
||||||
|
|
||||||
for _, H in ipairs(HALVES) do
|
for _, H in ipairs(HALVES) do
|
||||||
@@ -2144,56 +2097,44 @@ local function stairCell(S, map, data, cx, cy, s)
|
|||||||
-- lies on its front lip exactly where the artist drew it
|
-- lies on its front lip exactly where the artist drew it
|
||||||
face({ wx0, yTop, z0b }, { wx1, yTop, z0b },
|
face({ wx0, yTop, z0b }, { wx1, yTop, z0b },
|
||||||
{ wx1, yTop, z1b }, { wx0, yTop, z1b },
|
{ wx1, yTop, z1b }, { wx0, yTop, z1b },
|
||||||
ax0, a1, ax1, a0,
|
ax0, a1, ax1, a0, STAIR_SHADE.wellTread)
|
||||||
down and STAIR_SHADE.wellTread or STAIR_SHADE.tread)
|
|
||||||
|
|
||||||
-- the riser at that lip, one art row tall -- so it needs none of
|
-- the riser under that lip. It faces NORTH -- a flight descending
|
||||||
-- `banded`'s row splitting, and written straight keeps the geometry
|
-- away from you turns its risers away with it, and they close the
|
||||||
-- flush at the seam while the art stays inside its tile. Facing
|
-- steps from below rather than being looked at. One art row tall,
|
||||||
-- north when the flight descends (the steps are closed from below,
|
-- so it needs none of `banded`'s row splitting; written straight
|
||||||
-- not looked at) and south when it climbs
|
-- keeps the geometry flush at the seam while the art stays inside
|
||||||
if down then
|
-- its tile
|
||||||
face({ wx1, yTop, z1b }, { wx0, yTop, z1b },
|
local ry = -i * rise
|
||||||
{ wx0, ry, z1b }, { wx1, ry, z1b },
|
face({ wx1, yTop, z1b }, { wx0, yTop, z1b },
|
||||||
ax1, a1 - 1, ax0, a1, STAIR_SHADE.riser)
|
{ wx0, ry, z1b }, { wx1, ry, z1b },
|
||||||
else
|
ax1, a1 - 1, ax0, a1, STAIR_SHADE.riser)
|
||||||
face({ wx0, ry, z1b }, { wx1, ry, z1b },
|
|
||||||
{ wx1, yTop, z1b }, { wx0, yTop, z1b },
|
|
||||||
ax0, a1 - 1, ax1, a1, STAIR_SHADE.riser)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- the deep end, closing the opening this flight is cut into: from
|
-- the deep end, closing the opening this flight is cut into: from
|
||||||
-- the floor of the well up to the top of the wall band beside it,
|
-- the floor of the well up to the top of the wall band beside it,
|
||||||
-- in the drawing's own black top rows. A climbing flight has no
|
-- in the drawing's own black top rows
|
||||||
-- such end -- its top tread stands at the wall's own height and
|
if i == STAIR_STEPS - 1 then
|
||||||
-- fills the opening
|
|
||||||
if down and i == STAIR_STEPS - 1 then
|
|
||||||
face({ wx1, -h, mz }, { wx0, -h, mz },
|
face({ wx1, -h, mz }, { wx0, -h, mz },
|
||||||
{ wx0, h, mz }, { wx1, h, mz },
|
{ wx0, h, mz }, { wx1, h, mz },
|
||||||
ax1, 3.9, ax0, 0.1, STAIR_SHADE.wellEnd)
|
ax1, 3.9, ax0, 0.1, STAIR_SHADE.wellEnd)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- the opening's side walls beside this tread, wearing the drawing's
|
-- the well's side walls above this tread, wearing the drawing's own
|
||||||
-- own black edge columns -- excavation or recess, it is walled in its
|
-- black edge columns -- the excavation is walled in its own texels
|
||||||
-- own texels. Descending they run from the tread up to the floor,
|
|
||||||
-- climbing from the tread up to the top of the wall band
|
|
||||||
local wallTop = down and 0 or h
|
|
||||||
local function sideWall(px, sx0, sx1, inward)
|
local function sideWall(px, sx0, sx1, inward)
|
||||||
local c
|
local c
|
||||||
if inward then -- west wall, faces E
|
if inward then -- west wall, faces E
|
||||||
c = { { px, yTop, z1b }, { px, yTop, z0b },
|
c = { { px, yTop, z1b }, { px, yTop, z0b },
|
||||||
{ px, wallTop, z0b }, { px, wallTop, z1b } }
|
{ px, 0, z0b }, { px, 0, z1b } }
|
||||||
else -- east wall, faces W
|
else -- east wall, faces W
|
||||||
c = { { px, yTop, z0b }, { px, yTop, z1b },
|
c = { { px, yTop, z0b }, { px, yTop, z1b },
|
||||||
{ px, wallTop, z1b }, { px, wallTop, z0b } }
|
{ px, 0, z1b }, { px, 0, z0b } }
|
||||||
end
|
end
|
||||||
face(c[1], c[2], c[3], c[4], sx0, a1, sx1, a0, STAIR_SHADE.wellN)
|
face(c[1], c[2], c[3], c[4], sx0, a1, sx1, a0, STAIR_SHADE.wellN)
|
||||||
end
|
end
|
||||||
if wallTop > yTop then
|
sideWall(mx, 0.1, 1.3, true)
|
||||||
sideWall(mx, 0.1, 1.3, true)
|
sideWall(mx + 16, 14.7, 15.9, false)
|
||||||
sideWall(mx + 16, 14.7, 15.9, false)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
@@ -2871,10 +2812,9 @@ function Structures.buildObject(S, map, region, cluster,
|
|||||||
-- Town is where it showed: pinning the cliff's slope chain gave the
|
-- Town is where it showed: pinning the cliff's slope chain gave the
|
||||||
-- posts along the cliff edge an authored 16px box to their south, and
|
-- posts along the cliff edge an authored 16px box to their south, and
|
||||||
-- they were hoisted to stand on the clifftop instead of the path.
|
-- they were hoisted to stand on the clifftop instead of the path.
|
||||||
local baseY, support, supportRow = 0, nil, nil
|
local baseY, support = 0, nil
|
||||||
if force and force ~= "opaque" then
|
if force and force ~= "opaque" then
|
||||||
local belowK = keyOf(cluster.minX, cluster.maxY + 1)
|
local bs = S.shapeAt[keyOf(cluster.minX, cluster.maxY + 1)]
|
||||||
local bs = S.shapeAt[belowK]
|
|
||||||
local blocked = not map:isWalkableCell(math.floor(cluster.minX / 2),
|
local blocked = not map:isWalkableCell(math.floor(cluster.minX / 2),
|
||||||
math.floor(cluster.maxY / 2))
|
math.floor(cluster.maxY / 2))
|
||||||
-- `bookcase` supports as well as `upright`. A prop drawn above an
|
-- `bookcase` supports as well as `upright`. A prop drawn above an
|
||||||
@@ -2891,13 +2831,6 @@ function Structures.buildObject(S, map, region, cluster,
|
|||||||
and (bs.art == "upright" or bs.art == "bookcase"
|
and (bs.art == "upright" or bs.art == "bookcase"
|
||||||
or bs.class == "building") then
|
or bs.class == "building") then
|
||||||
baseY, support = bs.h, bs
|
baseY, support = bs.h, bs
|
||||||
-- A bookcase support has MOVED: the collapse walks the whole drawn
|
|
||||||
-- run onto its southmost cell, and the cell tested above is the run's
|
|
||||||
-- north end. On the Plateau's two-cell pilasters that is a full cell
|
|
||||||
-- away, and the bird stood at the right HEIGHT over open ground with
|
|
||||||
-- its pillar behind it -- floating. Stand it on the box's own north
|
|
||||||
-- row instead of one row south of its drawing.
|
|
||||||
supportRow = S.bookcaseBox[belowK]
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
local atlasW = map.tileset.imageWidth or 128
|
local atlasW = map.tileset.imageWidth or 128
|
||||||
@@ -2949,9 +2882,8 @@ function Structures.buildObject(S, map, region, cluster,
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
for _, c in ipairs(comps) do
|
for _, c in ipairs(comps) do
|
||||||
c.z0 = supportRow and (supportRow * 8 + (8 - depth) / 2)
|
c.z0 = cluster.minY * 8 + math.floor(c.lowY / 8) * 8
|
||||||
or (cluster.minY * 8 + math.floor(c.lowY / 8) * 8
|
+ (support and 8 or 0) + (8 - depth) / 2
|
||||||
+ (support and 8 or 0) + (8 - depth) / 2)
|
|
||||||
c.z1 = c.z0 + depth
|
c.z1 = c.z0 + depth
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
+5
-52
@@ -125,13 +125,11 @@ local FALLBACK_HEIGHTS = {
|
|||||||
stair_w = 16,
|
stair_w = 16,
|
||||||
stair_down_e = 16,
|
stair_down_e = 16,
|
||||||
stair_down_w = 16,
|
stair_down_w = 16,
|
||||||
-- a flight running toward the BACK of the map, drawn head-on instead of
|
-- a stairwell descending toward the BACK of the map, drawn head-on
|
||||||
-- from the side (the Centers' Cable Club steps). Its own class because
|
-- instead of from the side (the Centers' Cable Club steps). Its own
|
||||||
-- the art reading is not the east/west one turned: there a drawn COLUMN
|
-- class because the art reading is not the east/west one turned: there
|
||||||
-- is a step and a drawn row is height, here a drawn ROW is a step and
|
-- a drawn COLUMN is a step and a drawn row is height, here a drawn ROW
|
||||||
-- drawn row = depth row, 1:1 into the opening. `stair_n` climbs away
|
-- is a step and drawn row = depth row, 1:1 down the well
|
||||||
-- from the room, `stair_down_n` descends into a well
|
|
||||||
stair_n = 16,
|
|
||||||
stair_down_n = 16,
|
stair_down_n = 16,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,7 +221,6 @@ local ART = {
|
|||||||
stair_w = "stair",
|
stair_w = "stair",
|
||||||
stair_down_e = "stair",
|
stair_down_e = "stair",
|
||||||
stair_down_w = "stair",
|
stair_down_w = "stair",
|
||||||
stair_n = "stair",
|
|
||||||
stair_down_n = "stair",
|
stair_down_n = "stair",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -764,50 +761,6 @@ function TileShape.bookcaseRelief(tilesetId)
|
|||||||
return not (entry and entry.bookcase_relief == false)
|
return not (entry and entry.bookcase_relief == false)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- What a `wall` cell's TOP face wears in this tileset (a tileset entry's
|
|
||||||
--- wall_top). Returns a function tile -> cap tile id (nil for "leave it
|
|
||||||
--- alone"), or nil when the tileset says nothing at all.
|
|
||||||
---
|
|
||||||
--- A wall band is 16px of art folded upright over a run two drawn rows
|
|
||||||
--- deep, so it folds ENTIRELY onto its face and has no row left to lay
|
|
||||||
--- flat on top -- the top then repeats the face, and a house's town-map
|
|
||||||
--- poster and window came out lying across the top of the wall as well as
|
|
||||||
--- hanging on it. What is up there is the wall's capping course, which is
|
|
||||||
--- the plain panel the decorated column's own neighbours draw; naming it
|
|
||||||
--- is the whole fix, because "plain" is a fact about the drawing that
|
|
||||||
--- nothing in the geometry can measure.
|
|
||||||
---
|
|
||||||
--- Two forms, because tilesets differ in how far one answer reaches:
|
|
||||||
---
|
|
||||||
--- wall_top = <id> EVERY wall cell caps with this course.
|
|
||||||
--- Right where one atlas dresses one kind of
|
|
||||||
--- room -- the town house, the Centers, Red's
|
|
||||||
--- two floors all cap with their own blank
|
|
||||||
--- panel, and a list keyed by the decorated
|
|
||||||
--- tiles would need extending every time a
|
|
||||||
--- map hung something new on the same wall.
|
|
||||||
--- wall_top = { [tile] = id } only these tiles are redirected. Right
|
|
||||||
--- where one atlas dresses several rooms:
|
|
||||||
--- LOBBY is the department store, the Game
|
|
||||||
--- Corner, Silph's floors, the roof AND the
|
|
||||||
--- Rocket lift, and the lift's cabin frame is
|
|
||||||
--- not what a shop wall caps with.
|
|
||||||
function TileShape.wallTop(tilesetId)
|
|
||||||
local s = load()
|
|
||||||
local entry = s and s.tilesets and s.tilesets[tilesetId]
|
|
||||||
local spec = entry and entry.wall_top
|
|
||||||
if type(spec) == "number" then
|
|
||||||
return function() return spec end
|
|
||||||
end
|
|
||||||
if type(spec) == "table" then
|
|
||||||
return function(tile)
|
|
||||||
local cap = spec[tile]
|
|
||||||
return type(cap) == "number" and cap or nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Drop the cache: a mod that shadows data/voxel_heights.lua or a tileset
|
-- Drop the cache: a mod that shadows data/voxel_heights.lua or a tileset
|
||||||
-- record needs the next lookup to re-resolve (hot reload, mod toggle).
|
-- record needs the next lookup to re-resolve (hot reload, mod toggle).
|
||||||
function TileShape.invalidate()
|
function TileShape.invalidate()
|
||||||
|
|||||||
+1
-8
@@ -89,14 +89,7 @@ local SHADER = [[
|
|||||||
varying vec3 vSun; // this fragment's place in the sun's view
|
varying vec3 vSun; // this fragment's place in the sun's view
|
||||||
varying float vFog; // how deep into the map's haze it stands
|
varying float vFog; // how deep into the map's haze it stands
|
||||||
varying float vFirefly; // zero normally, night glow on firefly cards
|
varying float vFirefly; // zero normally, night glow on firefly cards
|
||||||
// Explicitly qualified, and it has to be: an unqualified float takes each
|
uniform float fireflyNight; // shared safely by vertex and pixel stages
|
||||||
// STAGE's default precision, and those do not agree -- highp in the vertex
|
|
||||||
// stage, mediump in the fragment one. LOVE 11 linked the pair anyway;
|
|
||||||
// LOVE 12 holds both declarations to the same qualifier and refuses the
|
|
||||||
// whole shader over it, which costs the entire 3D pass -- Voxel3D.available()
|
|
||||||
// is a shader that compiled, and the overworld falls back to flat 2D with
|
|
||||||
// nothing said anywhere. Same macro the varyings below already use.
|
|
||||||
uniform LOVE_HIGHP_OR_MEDIUMP float fireflyNight;
|
|
||||||
#ifdef VOXEL_CULL
|
#ifdef VOXEL_CULL
|
||||||
// where this fragment stands in the FLAT world, for the diorama's
|
// where this fragment stands in the FLAT world, for the diorama's
|
||||||
// viewport to measure. Same precision reasoning as vGrid below: a
|
// viewport to measure. Same precision reasoning as vGrid below: a
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"id": "DRAMATIC_SHAPE",
|
"id": "DRAMATIC_SHAPE",
|
||||||
"name": "Dramatic Shape Voxel Mod",
|
"name": "Dramatic Shape Voxel Mod",
|
||||||
"version": "1.8.2",
|
"version": "1.8.1",
|
||||||
"api": 2,
|
"api": 2,
|
||||||
"entry": "main.lua",
|
"entry": "main.lua",
|
||||||
"profile": "content",
|
"profile": "content",
|
||||||
|
|||||||
@@ -7002,258 +7002,6 @@ end)()
|
|||||||
T.check(ball.stars == nil, "and the stars burn out on their own")
|
T.check(ball.stars == nil, "and the stars burn out on their own")
|
||||||
end)()
|
end)()
|
||||||
|
|
||||||
-- ------- an interior wall caps with its own plain course
|
|
||||||
--
|
|
||||||
-- A wall band is 16px of art over a run two drawn rows deep, so it folds
|
|
||||||
-- ENTIRELY onto its south face and has no drawn row left to lay flat on
|
|
||||||
-- top. The top therefore repeated the face: the town house's town-map
|
|
||||||
-- poster and window, and the Pokemon Center's pokeball poster, came out
|
|
||||||
-- lying across the top of the wall as well as hanging on it -- a picture
|
|
||||||
-- you look DOWN on from the voxel camera.
|
|
||||||
--
|
|
||||||
-- `wall_top` names the capping course per tileset (the plain panel the
|
|
||||||
-- decorated column's own neighbours draw), and it is a fact about the
|
|
||||||
-- drawing that no measurement of the geometry can recover -- nothing
|
|
||||||
-- distinguishes "poster" from "panel" but knowing which is which.
|
|
||||||
;(function()
|
|
||||||
local Mesher = run.loader.exports.DRAMATIC_SHAPE.lib.require("ChunkMesher")
|
|
||||||
local Struct = run.loader.exports.DRAMATIC_SHAPE.lib.require("Structures")
|
|
||||||
local Shape = run.loader.exports.DRAMATIC_SHAPE.lib.require("TileShape")
|
|
||||||
|
|
||||||
-- the blanket form: whatever a wall cell draws, it caps with the one
|
|
||||||
-- course the atlas's rooms are panelled in
|
|
||||||
T.eq(Shape.wallTop("HOUSE")(45), 0,
|
|
||||||
"the town house caps its walls with the blank course")
|
|
||||||
T.eq(Shape.wallTop("POKECENTER")(2), 40,
|
|
||||||
"and a Center with its striped panel -- the tile cell (9,0) draws")
|
|
||||||
T.eq(Shape.wallTop("REDS_HOUSE_2")(36), 0,
|
|
||||||
"Red's bedroom with its own blank panel, over the window")
|
|
||||||
T.eq(Shape.wallTop("REDS_HOUSE_1")(36), 0, "and so does the floor below")
|
|
||||||
-- the keyed form, for an atlas that dresses more than one kind of room
|
|
||||||
T.eq(Shape.wallTop("LOBBY")(40), 93,
|
|
||||||
"the Rocket lift's car door caps with the cabin frame")
|
|
||||||
T.eq(Shape.wallTop("LOBBY")(1), nil,
|
|
||||||
"but the department store's own panel is left exactly as it was -- "
|
|
||||||
.. "one atlas, several rooms, and only the lift is a lift")
|
|
||||||
T.eq(Shape.wallTop("DS_NO_SUCH_TILESET"), nil,
|
|
||||||
"a tileset that says nothing keeps the top it always had")
|
|
||||||
|
|
||||||
local FLOOR = 1
|
|
||||||
local function topTiles(tilesetId, rows)
|
|
||||||
local map = {
|
|
||||||
id = "DS_TEST_WALLTOP_" .. tilesetId,
|
|
||||||
tileset = { id = tilesetId, image = "gfx/tilesets/ds_test.png",
|
|
||||||
tilesPerRow = 16, imageWidth = 128, imageHeight = 48,
|
|
||||||
blocks = {}, grassTile = -1 },
|
|
||||||
def = { width = 4, height = 2, tileset = tilesetId },
|
|
||||||
walkable = { [FLOOR] = true },
|
|
||||||
waterTiles = {},
|
|
||||||
doorTiles = {},
|
|
||||||
tileAt = function(_, tx, ty)
|
|
||||||
local r = rows[ty + 1]
|
|
||||||
return (r and r[tx + 1]) or FLOOR
|
|
||||||
end,
|
|
||||||
cellTile = function(self, cx, cy) return self:tileAt(cx * 2, cy * 2 + 1) end,
|
|
||||||
isWaterCell = function() return false end,
|
|
||||||
isWalkableCell = function(self, cx, cy)
|
|
||||||
return self:cellTile(cx, cy) == FLOOR
|
|
||||||
end,
|
|
||||||
inBounds = function(_, cx, cy)
|
|
||||||
return cx >= 0 and cy >= 0 and cx < 4 and cy < 2
|
|
||||||
end,
|
|
||||||
}
|
|
||||||
Struct.invalidate(map.id)
|
|
||||||
local verts = Mesher.geometry(map, true, nil)
|
|
||||||
-- the flat quads standing at wall height, keyed by the column they cap
|
|
||||||
local out = {}
|
|
||||||
for i = 1, #verts, 4 do
|
|
||||||
local a, b, c, d = verts[i], verts[i + 1], verts[i + 2], verts[i + 3]
|
|
||||||
if a[2] == b[2] and b[2] == c[2] and c[2] == d[2] and a[2] == 16 then
|
|
||||||
local ax = math.floor(math.min(a[4], b[4], c[4], d[4]) * 128 + 0.5)
|
|
||||||
local ay = math.floor(math.min(a[5], b[5], c[5], d[5]) * 48 + 0.5)
|
|
||||||
local tx = math.floor(math.min(a[1], b[1], c[1], d[1]) / 8)
|
|
||||||
local ty = math.floor(math.min(a[3], b[3], c[3], d[3]) / 8)
|
|
||||||
-- keyed by the TILE ROW too: a wall band is two drawn rows deep
|
|
||||||
-- and they can wear different art
|
|
||||||
out[ty * 100 + tx] = out[ty * 100 + tx]
|
|
||||||
or (math.floor(ay / 8) * 16 + math.floor(ax / 8))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return out, verts
|
|
||||||
end
|
|
||||||
|
|
||||||
-- BLUES_HOUSE's back wall: blank panel, the town-map poster (45/46 over
|
|
||||||
-- 61/62) at cell (3,0)'s block, the window (36 over 52) at (5,0)'s
|
|
||||||
local houseTops, houseVerts = topTiles("HOUSE", {
|
|
||||||
{ 0, 0, 45, 46, 36, 36, 0, 0 },
|
|
||||||
{ 0, 0, 61, 62, 52, 52, 0, 0 },
|
|
||||||
})
|
|
||||||
for ty = 0, 1 do
|
|
||||||
for tx = 0, 7 do
|
|
||||||
T.eq(houseTops[ty * 100 + tx], 0,
|
|
||||||
("(%d,%d) of the house's back wall caps with the blank course")
|
|
||||||
:format(tx, ty))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- and the FACE is untouched: the poster still hangs in the room, which is
|
|
||||||
-- the half of this the fix must not take with it
|
|
||||||
local hung = {}
|
|
||||||
for _, v in ipairs(houseVerts) do
|
|
||||||
hung[math.floor(math.floor(v[5] * 48 + 0.5) / 8) * 16
|
|
||||||
+ math.floor(math.floor(v[4] * 128 + 0.5) / 8)] = true
|
|
||||||
end
|
|
||||||
T.check(hung[45] and hung[46] and hung[61] and hung[62],
|
|
||||||
"the town-map poster is still drawn on the wall it hangs on")
|
|
||||||
T.check(hung[36] and hung[52], "and so is the window")
|
|
||||||
|
|
||||||
-- VIRIDIAN_POKECENTER's back wall: the striped panel with the pokeball
|
|
||||||
-- poster (2/3 over 18/19) spanning cells (3,0) and (4,0)
|
|
||||||
local pcTops = topTiles("POKECENTER", {
|
|
||||||
{ 40, 40, 40, 2, 3, 40, 40, 40 },
|
|
||||||
{ 40, 40, 40, 18, 19, 40, 40, 40 },
|
|
||||||
})
|
|
||||||
for ty = 0, 1 do
|
|
||||||
for tx = 0, 7 do
|
|
||||||
T.eq(pcTops[ty * 100 + tx], 40,
|
|
||||||
("(%d,%d) of the Center's back wall caps with the striped panel")
|
|
||||||
:format(tx, ty))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- ------- the Rocket lift's car doors
|
|
||||||
--
|
|
||||||
-- Detected rather than pinned -- the doors are a volume Structures finds
|
|
||||||
-- -- so the cap has to reach the RUN branch too, not only the pinned one.
|
|
||||||
local liftTops = topTiles("LOBBY", {
|
|
||||||
{ 92, 92, 92, 92, 92, 92, 92, 92 },
|
|
||||||
{ 93, 93, 93, 93, 93, 93, 93, 93 },
|
|
||||||
{ 1, 1, 40, 40, 40, 40, 1, 1 },
|
|
||||||
{ 33, 33, 56, 56, 56, 56, 33, 33 },
|
|
||||||
})
|
|
||||||
-- rows 2 and 3 are the doors' own band; the doors span columns 2-5
|
|
||||||
for ty = 2, 3 do
|
|
||||||
for tx = 2, 5 do
|
|
||||||
T.eq(liftTops[ty * 100 + tx], 93,
|
|
||||||
("(%d,%d) of the lift's door caps with the cabin frame"):format(tx, ty))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
-- and the panelling either side of them is left alone, which is what the
|
|
||||||
-- keyed form buys: the same atlas panels the department store
|
|
||||||
T.eq(liftTops[2 * 100], 1, "the blank course beside the door is untouched")
|
|
||||||
T.eq(liftTops[3 * 100], 33, "and so is its skirting")
|
|
||||||
end)()
|
|
||||||
|
|
||||||
-- ------- Lance's room is furnished with the badge gyms' statue
|
|
||||||
--
|
|
||||||
-- The same drawing on a different atlas: one cell of bird over one cell of
|
|
||||||
-- plinth. Left derived the pair merged into one 32px volume wearing the
|
|
||||||
-- statue folded onto its face -- the extruded picture. The gyms' reading
|
|
||||||
-- (solid plinth, per-pixel standee on top) is the right one here too, and
|
|
||||||
-- these eight tiles draw nothing else anywhere in the game.
|
|
||||||
;(function()
|
|
||||||
local Shape = run.loader.exports.DRAMATIC_SHAPE.lib.require("TileShape")
|
|
||||||
local shapes = Shape.forMap({
|
|
||||||
tileset = { id = "DOJO", tilesPerRow = 16,
|
|
||||||
imageWidth = 128, imageHeight = 48, grassTile = -1 },
|
|
||||||
walkable = { [17] = true },
|
|
||||||
})
|
|
||||||
for _, t in ipairs({ 2, 18, 19, 56 }) do
|
|
||||||
T.eq(shapes[t].class, "prop",
|
|
||||||
("the statue's figure tile %d stands as a per-pixel cutout"):format(t))
|
|
||||||
T.eq(shapes[t].authored, true, "and it is an authored answer, not derived")
|
|
||||||
end
|
|
||||||
for _, t in ipairs({ 34, 35, 50, 51 }) do
|
|
||||||
T.eq(shapes[t].class, "wall",
|
|
||||||
("the plinth tile %d stays a solid 16px block"):format(t))
|
|
||||||
T.eq(shapes[t].authored, true,
|
|
||||||
"authored, which is what the standee's support rule tests -- and "
|
|
||||||
.. "what keeps tile 50 ($32) out of the water-fallback trap")
|
|
||||||
end
|
|
||||||
T.eq(shapes[13].class, "bookcase",
|
|
||||||
"and Oak's shelf ranks on the same atlas are untouched")
|
|
||||||
end)()
|
|
||||||
|
|
||||||
-- ------- a wall cut into a terrace inherits TERRACE, never a statue
|
|
||||||
--
|
|
||||||
-- `bookcase_backfill = "above"` hands a vacated row the cell above the run,
|
|
||||||
-- so masonry set into a hillside has more hillside behind it rather than a
|
|
||||||
-- trench. Indigo Plateau's avenue statues stand directly on the pilasters
|
|
||||||
-- that collapse this way, so what every one of them inherited was the BIRD:
|
|
||||||
-- the figure's shape and art copied onto two more rows down the shaft, and
|
|
||||||
-- the statue came out two deep behind itself. A standee above is an object
|
|
||||||
-- standing ON the terrace, not terrace -- so there is nothing to inherit.
|
|
||||||
;(function()
|
|
||||||
local Struct = run.loader.exports.DRAMATIC_SHAPE.lib.require("Structures")
|
|
||||||
-- the real west-edge statue of INDIGO_PLATEAU, tile for tile: the bird
|
|
||||||
-- (37/38 over 40/41) on the pilaster (cap 21/22, shaft 5/6 twice, foot
|
|
||||||
-- 21/22), standing on the plateau's paving (35)
|
|
||||||
local rows = {
|
|
||||||
{ 46, 47 }, { 46, 47 },
|
|
||||||
{ 37, 38 }, { 40, 41 },
|
|
||||||
{ 21, 22 }, { 5, 6 },
|
|
||||||
{ 5, 6 }, { 21, 22 },
|
|
||||||
{ 35, 35 }, { 35, 35 },
|
|
||||||
{ 35, 35 }, { 35, 35 },
|
|
||||||
}
|
|
||||||
local map = {
|
|
||||||
id = "DS_TEST_PLATEAU_STATUE",
|
|
||||||
tileset = { id = "PLATEAU", image = "gfx/tilesets/ds_test.png",
|
|
||||||
tilesPerRow = 16, imageWidth = 128, imageHeight = 40,
|
|
||||||
blocks = {}, grassTile = -1 },
|
|
||||||
def = { width = 1, height = 3, tileset = "PLATEAU" },
|
|
||||||
walkable = { [35] = true },
|
|
||||||
waterTiles = {},
|
|
||||||
doorTiles = {},
|
|
||||||
tileAt = function(_, tx, ty)
|
|
||||||
local r = rows[math.max(1, math.min(#rows, ty + 1))]
|
|
||||||
return r[(tx % 2) + 1]
|
|
||||||
end,
|
|
||||||
cellTile = function(self, cx, cy) return self:tileAt(cx * 2, cy * 2 + 1) end,
|
|
||||||
isWaterCell = function() return false end,
|
|
||||||
isWalkableCell = function(self, cx, cy)
|
|
||||||
return self:cellTile(cx, cy) == 35
|
|
||||||
end,
|
|
||||||
isDoorTileCell = function() return false end,
|
|
||||||
isGrassCell = function() return false end,
|
|
||||||
inBounds = function(_, cx, cy)
|
|
||||||
return cx >= 0 and cy >= 0 and cx < 2 and cy < 6
|
|
||||||
end,
|
|
||||||
}
|
|
||||||
Struct.invalidate(map.id)
|
|
||||||
local S = Struct.forMap(map)
|
|
||||||
local function keyOf(tx, ty) return (ty + 64) * 4096 + (tx + 64) end
|
|
||||||
|
|
||||||
-- the bird stands, once, where it is drawn
|
|
||||||
for _, ty in ipairs({ 2, 3 }) do
|
|
||||||
T.eq(S.shapeAt[keyOf(0, ty)].art, "billboard",
|
|
||||||
"the statue's own rows carry the standee")
|
|
||||||
end
|
|
||||||
-- and the pilaster rows it vacates carry NOTHING -- not a second bird
|
|
||||||
for _, ty in ipairs({ 4, 5, 6, 7 }) do
|
|
||||||
local s = S.shapeAt[keyOf(0, ty)]
|
|
||||||
T.check(s == nil or s.art ~= "billboard",
|
|
||||||
("pilaster row %d did not inherit the statue standing on it"):format(ty))
|
|
||||||
end
|
|
||||||
|
|
||||||
-- ...and the bird stands on the box rather than where it is DRAWN. The
|
|
||||||
-- collapse walks the whole four-row pilaster onto its southmost cell, so
|
|
||||||
-- a standee that trusted its own drawn position ended up a full cell
|
|
||||||
-- north of the pillar holding it up -- at the right height, over open
|
|
||||||
-- ground. Every row of the rank records where the box actually went.
|
|
||||||
for _, ty in ipairs({ 4, 5, 6, 7 }) do
|
|
||||||
T.eq(S.bookcaseBox[keyOf(0, ty)], 6,
|
|
||||||
("rank row %d knows the box stands on row 6"):format(ty))
|
|
||||||
end
|
|
||||||
local zlo = math.huge
|
|
||||||
for _, q in ipairs(S.objectQuads) do
|
|
||||||
for i = 1, 4 do zlo = math.min(zlo, q[i][3]) end
|
|
||||||
end
|
|
||||||
T.eq(math.floor(zlo / 8), 6,
|
|
||||||
"and the statue's own geometry starts on that row -- standing on the "
|
|
||||||
.. "pillar, not hanging in the air two cells in front of it")
|
|
||||||
end)()
|
|
||||||
|
|
||||||
Pipelines.reset()
|
Pipelines.reset()
|
||||||
run.release()
|
run.release()
|
||||||
|
|
||||||
|
|||||||
@@ -1,137 +0,0 @@
|
|||||||
-- Scratch driver: one shot of each fixture the wall-top / statue pass
|
|
||||||
-- touched, so the change can be looked at rather than reasoned about.
|
|
||||||
--
|
|
||||||
-- POKEPORT_DRIVER=mods/DramaticShapeVoxelMod/tests/voxel_fix_shots.lua \
|
|
||||||
-- SHOT_DIR=mods/DramaticShapeVoxelMod/.claude/voxel_fix AB_TAG=. lovec.exe .
|
|
||||||
--
|
|
||||||
-- SHOT_DIR is relative to the PROJECT ROOT, which is where lovec runs from --
|
|
||||||
-- the mod's own .claude/ is where these belong, so it has to be spelt out.
|
|
||||||
--
|
|
||||||
-- Every scene stands the player on the nearest WALKABLE cell to the vantage
|
|
||||||
-- named below (teleporting into a wall puts the close cameras inside the
|
|
||||||
-- geometry), faces the fixture, and shoots at voxel rung 3 -- the higher
|
|
||||||
-- top-down camera, which is the one that shows what a wall wears on TOP.
|
|
||||||
return function(game)
|
|
||||||
local U = dofile("tests/drivers/util.lua")
|
|
||||||
local Pipelines = require("src.render.Pipelines")
|
|
||||||
|
|
||||||
local ROOT = (os.getenv("SHOT_DIR")
|
|
||||||
or "mods/DramaticShapeVoxelMod/.claude/voxel_fix")
|
|
||||||
.. "/" .. (os.getenv("AB_TAG") or ".")
|
|
||||||
|
|
||||||
local handle = game.mods.exports["DRAMATIC_SHAPE"]
|
|
||||||
if not (handle and handle.lib) then
|
|
||||||
print("[voxfix] DRAMATIC_SHAPE mod not loaded")
|
|
||||||
return
|
|
||||||
end
|
|
||||||
local V = handle.lib
|
|
||||||
local DayNight = V.require("DayNight")
|
|
||||||
local ChunkMesher = V.require("ChunkMesher")
|
|
||||||
local Voxel = V.require("VoxelState")
|
|
||||||
local TileShape = V.require("TileShape")
|
|
||||||
|
|
||||||
-- prove the game is reading THIS tree, not a stale installed copy
|
|
||||||
local ht = TileShape.wallTop and TileShape.wallTop("HOUSE")
|
|
||||||
print(("[voxfix] wallTop present=%s HOUSE(45)=%s")
|
|
||||||
:format(tostring(TileShape.wallTop ~= nil),
|
|
||||||
tostring(ht and ht(45))))
|
|
||||||
|
|
||||||
require("src.world.OverworldController").rollEncounter = function() return nil end
|
|
||||||
local TileRenderer = require("src.render.TileRenderer")
|
|
||||||
TileRenderer.tick = function() end
|
|
||||||
TileRenderer.animFrame = function() return 0 end
|
|
||||||
DayNight.setting:sync("day")
|
|
||||||
|
|
||||||
pcall(os.execute, 'mkdir -p "' .. ROOT .. '" 2>/dev/null')
|
|
||||||
pcall(os.execute, 'mkdir "' .. ROOT:gsub("/", "\\") .. '" 2>nul')
|
|
||||||
|
|
||||||
local Zoom = require("src.render.Zoom")
|
|
||||||
pcall(function()
|
|
||||||
game.save.options.zoom = 1
|
|
||||||
Zoom.applyOptions(game.save.options)
|
|
||||||
end)
|
|
||||||
|
|
||||||
local function settle()
|
|
||||||
for _ = 1, 900 do
|
|
||||||
if ChunkMesher.pending() == 0 then break end
|
|
||||||
U.wait(1)
|
|
||||||
end
|
|
||||||
for _ = 1, 300 do
|
|
||||||
if Voxel.t >= 1 and Voxel.ready and ChunkMesher.pending() == 0 then break end
|
|
||||||
U.wait(1)
|
|
||||||
end
|
|
||||||
U.wait(40)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- the nearest walkable cell to the vantage, searched in rings -- a
|
|
||||||
-- vantage picked off a map dump is often the fixture itself
|
|
||||||
local function place(x, y, face)
|
|
||||||
local m = game.overworld.map
|
|
||||||
for r = 0, 8 do
|
|
||||||
for dy = -r, r do
|
|
||||||
for dx = -r, r do
|
|
||||||
if math.max(math.abs(dx), math.abs(dy)) == r then
|
|
||||||
local px, py = x + dx, y + dy
|
|
||||||
if m:inBounds(px, py) and m:isWalkableCell(px, py) then
|
|
||||||
game.overworld.player.cellX = px
|
|
||||||
game.overworld.player.cellY = py
|
|
||||||
game.overworld.player.facing = face
|
|
||||||
return px, py
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return x, y
|
|
||||||
end
|
|
||||||
|
|
||||||
local SCENES = {
|
|
||||||
{ map = "BLUES_HOUSE", x = 4, y = 3, face = "up",
|
|
||||||
label = "house_wall_top" },
|
|
||||||
{ map = "VIRIDIAN_POKECENTER", x = 4, y = 3, face = "up",
|
|
||||||
label = "pokecenter_wall_top" },
|
|
||||||
{ map = "ROCKET_HIDEOUT_ELEVATOR", x = 2, y = 3, face = "up",
|
|
||||||
label = "rocket_lift_wall_top" },
|
|
||||||
{ map = "REDS_HOUSE_2F", x = 6, y = 3, face = "up",
|
|
||||||
label = "reds_2f_wall_top" },
|
|
||||||
{ map = "REDS_HOUSE_1F", x = 5, y = 3, face = "up",
|
|
||||||
label = "reds_1f_wall_top" },
|
|
||||||
{ map = "LANCES_ROOM", x = 6, y = 15, face = "up",
|
|
||||||
label = "lances_room_statues" },
|
|
||||||
{ map = "INDIGO_PLATEAU", x = 2, y = 5, face = "up",
|
|
||||||
label = "indigo_plateau_statue" },
|
|
||||||
-- the west-edge statue itself, close enough to count the birds on it
|
|
||||||
{ map = "INDIGO_PLATEAU", x = 1, y = 4, face = "up",
|
|
||||||
label = "indigo_plateau_statue_close", rung = 5 },
|
|
||||||
{ map = "INDIGO_PLATEAU", x = 2, y = 4, face = "up",
|
|
||||||
label = "indigo_plateau_statue_near" },
|
|
||||||
}
|
|
||||||
|
|
||||||
local only = os.getenv("VF_ONLY")
|
|
||||||
local shots = 0
|
|
||||||
for _, s in ipairs(SCENES) do
|
|
||||||
if not (only and only ~= "" and not only:find(s.label, 1, true)) then
|
|
||||||
local ok, err = pcall(function()
|
|
||||||
U.teleport(game, s.map, s.x, s.y, s.face)
|
|
||||||
local px, py = place(s.x, s.y, s.face)
|
|
||||||
Pipelines.setLevel("voxel", s.rung or 3)
|
|
||||||
Pipelines.setLevel("tiltshift", 0)
|
|
||||||
settle()
|
|
||||||
local path = ("%s/%s.png"):format(ROOT, s.label)
|
|
||||||
game.capturePath = path
|
|
||||||
U.wait(8)
|
|
||||||
local f = io.open(path, "rb")
|
|
||||||
if f then
|
|
||||||
f:close()
|
|
||||||
shots = shots + 1
|
|
||||||
print(("[voxfix] %s -> %s (stood %d,%d)"):format(s.map, path, px, py))
|
|
||||||
else
|
|
||||||
print("[voxfix] capture missed: " .. path)
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
if not ok then print("[voxfix] " .. s.map .. " failed: " .. tostring(err)) end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
print(("[voxfix] %d/%d shots into %s"):format(shots, #SCENES, ROOT))
|
|
||||||
love.event.quit()
|
|
||||||
end
|
|
||||||
Reference in New Issue
Block a user