@dreadwood-types/types 0.1.47 → 0.1.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adventure.d.ts +6 -6
- package/dist/adventure.d.ts.map +1 -1
- package/dist/appearance.d.ts +16 -6
- package/dist/appearance.d.ts.map +1 -1
- package/dist/appearance.js +43 -31
- package/dist/appearance.js.map +1 -1
- package/dist/areaTransitions.d.ts +1 -1
- package/dist/areaTransitions.d.ts.map +1 -1
- package/dist/areaTransitions.js +13 -0
- package/dist/areaTransitions.js.map +1 -1
- package/dist/bank.d.ts.map +1 -1
- package/dist/bearFloes.d.ts +83 -0
- package/dist/bearFloes.d.ts.map +1 -0
- package/dist/bearFloes.js +126 -0
- package/dist/bearFloes.js.map +1 -0
- package/dist/blacksmithing.d.ts +3 -0
- package/dist/blacksmithing.d.ts.map +1 -1
- package/dist/blacksmithing.js +81 -1
- package/dist/blacksmithing.js.map +1 -1
- package/dist/bloodRiver.d.ts +9 -6
- package/dist/bloodRiver.d.ts.map +1 -1
- package/dist/bloodRiver.js +19 -17
- package/dist/bloodRiver.js.map +1 -1
- package/dist/brokenRoad.d.ts.map +1 -1
- package/dist/capital.d.ts +541 -165
- package/dist/capital.d.ts.map +1 -1
- package/dist/capital.js +934 -163
- package/dist/capital.js.map +1 -1
- package/dist/charcoal.d.ts +43 -0
- package/dist/charcoal.d.ts.map +1 -0
- package/dist/charcoal.js +78 -0
- package/dist/charcoal.js.map +1 -0
- package/dist/clientConfig.d.ts +7 -2
- package/dist/clientConfig.d.ts.map +1 -1
- package/dist/clientConfig.js +14 -5
- package/dist/clientConfig.js.map +1 -1
- package/dist/constants.d.ts +196 -11
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +372 -21
- package/dist/constants.js.map +1 -1
- package/dist/cooking.d.ts.map +1 -1
- package/dist/cooking.js +2 -0
- package/dist/cooking.js.map +1 -1
- package/dist/dragonfire.d.ts +75 -0
- package/dist/dragonfire.d.ts.map +1 -0
- package/dist/dragonfire.js +73 -0
- package/dist/dragonfire.js.map +1 -0
- package/dist/dungeonCharts.d.ts +18 -0
- package/dist/dungeonCharts.d.ts.map +1 -0
- package/dist/dungeonCharts.js +67 -0
- package/dist/dungeonCharts.js.map +1 -0
- package/dist/equipment.d.ts +2 -0
- package/dist/equipment.d.ts.map +1 -1
- package/dist/equipment.js +37 -1
- package/dist/equipment.js.map +1 -1
- package/dist/frozenSet.d.ts +36 -0
- package/dist/frozenSet.d.ts.map +1 -0
- package/dist/frozenSet.js +58 -0
- package/dist/frozenSet.js.map +1 -0
- package/dist/goblinCave.d.ts +4 -4
- package/dist/goblinCave.d.ts.map +1 -1
- package/dist/goblinCave.js +1 -1
- package/dist/groundItems.d.ts.map +1 -1
- package/dist/groundItems.js +27 -0
- package/dist/groundItems.js.map +1 -1
- package/dist/harbour.d.ts +223 -0
- package/dist/harbour.d.ts.map +1 -0
- package/dist/harbour.js +264 -0
- package/dist/harbour.js.map +1 -0
- package/dist/iceDepths.d.ts +150 -0
- package/dist/iceDepths.d.ts.map +1 -0
- package/dist/iceDepths.js +132 -0
- package/dist/iceDepths.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -1
- package/dist/interiorFurniture.d.ts +57 -0
- package/dist/interiorFurniture.d.ts.map +1 -0
- package/dist/interiorFurniture.js +159 -0
- package/dist/interiorFurniture.js.map +1 -0
- package/dist/landmarks.d.ts +83 -59
- package/dist/landmarks.d.ts.map +1 -1
- package/dist/landmarks.js +194 -134
- package/dist/landmarks.js.map +1 -1
- package/dist/messages.d.ts +77 -5
- package/dist/messages.d.ts.map +1 -1
- package/dist/messages.js.map +1 -1
- package/dist/mineCave.d.ts +7 -7
- package/dist/mineCave.d.ts.map +1 -1
- package/dist/mining.d.ts +8 -0
- package/dist/mining.d.ts.map +1 -1
- package/dist/mining.js +8 -0
- package/dist/mining.js.map +1 -1
- package/dist/questGuide.d.ts +30 -0
- package/dist/questGuide.d.ts.map +1 -0
- package/dist/questGuide.js +3 -0
- package/dist/questGuide.js.map +1 -0
- package/dist/quests.d.ts +29 -0
- package/dist/quests.d.ts.map +1 -1
- package/dist/quests.js +70 -0
- package/dist/quests.js.map +1 -1
- package/dist/snow.d.ts +2 -2
- package/dist/snow.d.ts.map +1 -1
- package/dist/snow.js +8 -5
- package/dist/snow.js.map +1 -1
- package/dist/spiderCave.d.ts.map +1 -1
- package/dist/travel.d.ts +7 -4
- package/dist/travel.d.ts.map +1 -1
- package/dist/travel.js +20 -4
- package/dist/travel.js.map +1 -1
- package/dist/woodwork.d.ts +8 -2
- package/dist/woodwork.d.ts.map +1 -1
- package/dist/woodwork.js +13 -3
- package/dist/woodwork.js.map +1 -1
- package/dist/xp.d.ts +18 -7
- package/dist/xp.d.ts.map +1 -1
- package/dist/xp.js +20 -9
- package/dist/xp.js.map +1 -1
- package/dist/zones.d.ts +5 -0
- package/dist/zones.d.ts.map +1 -1
- package/dist/zones.js +8 -3
- package/dist/zones.js.map +1 -1
- package/package.json +1 -1
package/dist/capital.js
CHANGED
|
@@ -2,22 +2,47 @@
|
|
|
2
2
|
* THE CAPITAL
|
|
3
3
|
*
|
|
4
4
|
* The walled capital of the northern kingdoms, in the open green east of the
|
|
5
|
-
* Thaw
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
5
|
+
* Thaw. Redesigned 2026-09-25 from James's masterplan and district sheets
|
|
6
|
+
* (design-references/capital-*; the layout spec and its checking plan script
|
|
7
|
+
* are summarised in the phase-1 commit). The plan in one breath:
|
|
8
|
+
*
|
|
9
|
+
* - One north-south spine, the Royal Way (u 149-155), from the North Gate
|
|
10
|
+
* past Victory Plaza, the Crown Cross and the Noble Cross to the South
|
|
11
|
+
* Gate. Both of those gates are built and barred.
|
|
12
|
+
* - Two east-west primaries: Crown Street (v 62-66) the full width of the
|
|
13
|
+
* city on the painted road, and the King's Road (v 82-86, the keep axis,
|
|
14
|
+
* CAPITAL_AVENUE_Z) from the Great Gate in the west wall to the Crown
|
|
15
|
+
* Cross and up the ramp to the keep. The Harbour Road runs the same rows
|
|
16
|
+
* from the East Gate to the mound's foot.
|
|
17
|
+
* - The keep on its mound east of the Way, still facing west; the districts
|
|
18
|
+
* where the masterplan paints them: Trade NW, Temple Gardens NE, the
|
|
19
|
+
* Westgate Outpost, Forge and Military Ward west of the Way, the Keep and
|
|
20
|
+
* the Royal Quarter centre-east, the Noble Quarter SE.
|
|
21
|
+
*
|
|
22
|
+
* CITY-LOCAL TILES. Every plan table below is written in city-local tiles,
|
|
23
|
+
* `u = x - CAPITAL_MIN_X` (0..280, west to east) and `v = z - CAPITAL_NORTH_Z`
|
|
24
|
+
* (0..168, north to south): the walls are u 0-1, u 279-280, v 0-1 and
|
|
25
|
+
* v 167-168, the buildable interior u 2-278, v 2-166. `cu()` / `cv()` turn
|
|
26
|
+
* them into world tiles; every row also carries its world rect or tile, so
|
|
27
|
+
* nothing downstream has to convert by hand. Never write a world literal for
|
|
28
|
+
* a Capital coordinate: the map has already moved under the city once.
|
|
11
29
|
*
|
|
12
30
|
* World tiles; the north pad is part of the grid, so no toWorldZ.
|
|
13
31
|
* Backend builder: capitalCity.ts. Assets: capitalAssets.ts (keep, statue,
|
|
14
|
-
* fountain), capitalFabric.ts (walls, gatehouse, bridge, buildings, props)
|
|
32
|
+
* fountain), capitalFabric.ts (walls, gatehouse, bridge, buildings, props),
|
|
33
|
+
* buildingModels.ts (the Blender buildings, kind `capitalbuilding`).
|
|
15
34
|
*/
|
|
35
|
+
import { CASTLE_KEEP_HALF_X, CASTLE_KEEP_HALF_Z, CASTLE_PASSAGE_HALF_W, SurfaceType, legacyZ, statueStyleWithPlinth } from './constants.js';
|
|
36
|
+
import { BANK_RANGE } from './bank.js';
|
|
37
|
+
import { BLACKSMITH_RANGE, TEMPLE_PRAYER_RANGE, TRAINING_DUMMY_RANGE } from './blacksmithing.js';
|
|
38
|
+
import { CHARCOAL_MOUND_RANGE } from './charcoal.js';
|
|
39
|
+
import { CAMPFIRE_COOK_RANGE } from './cooking.js';
|
|
40
|
+
import { DEADLANDS_MIN_Z } from './zones.js';
|
|
16
41
|
/** The outer wall: two tiles thick, these are the OUTER rows. */
|
|
17
42
|
export const CAPITAL_MIN_X = 480;
|
|
18
43
|
export const CAPITAL_MAX_X = 760;
|
|
19
|
-
export const CAPITAL_NORTH_Z =
|
|
20
|
-
export const CAPITAL_SOUTH_Z =
|
|
44
|
+
export const CAPITAL_NORTH_Z = 76;
|
|
45
|
+
export const CAPITAL_SOUTH_Z = 244;
|
|
21
46
|
export const CAPITAL_WALL_THICK = 2;
|
|
22
47
|
export const CAPITAL_BASTION_EVERY = 16;
|
|
23
48
|
/** Inside the wall: the first row a building can stand on. */
|
|
@@ -25,36 +50,127 @@ export const CAPITAL_INNER_MIN_X = CAPITAL_MIN_X + CAPITAL_WALL_THICK;
|
|
|
25
50
|
export const CAPITAL_INNER_MAX_X = CAPITAL_MAX_X - CAPITAL_WALL_THICK;
|
|
26
51
|
export const CAPITAL_INNER_NORTH_Z = CAPITAL_NORTH_Z + CAPITAL_WALL_THICK;
|
|
27
52
|
export const CAPITAL_INNER_SOUTH_Z = CAPITAL_SOUTH_Z - CAPITAL_WALL_THICK;
|
|
53
|
+
/* ------------------------------------------------------------------ *
|
|
54
|
+
* City-local tiles.
|
|
55
|
+
* ------------------------------------------------------------------ */
|
|
56
|
+
/** City-local column to world x: u 0 is the west wall's outer row. */
|
|
57
|
+
export function cu(u) {
|
|
58
|
+
return CAPITAL_MIN_X + u;
|
|
59
|
+
}
|
|
60
|
+
/** City-local row to world z: v 0 is the north wall's outer row. */
|
|
61
|
+
export function cv(v) {
|
|
62
|
+
return CAPITAL_NORTH_Z + v;
|
|
63
|
+
}
|
|
64
|
+
/** World x back to a city-local column. */
|
|
65
|
+
export function capU(x) {
|
|
66
|
+
return x - CAPITAL_MIN_X;
|
|
67
|
+
}
|
|
68
|
+
/** World z back to a city-local row. */
|
|
69
|
+
export function capV(z) {
|
|
70
|
+
return z - CAPITAL_NORTH_Z;
|
|
71
|
+
}
|
|
72
|
+
/** A city-local tile as a world tile. */
|
|
73
|
+
export function capWorld(u, v) {
|
|
74
|
+
return { x: cu(u), z: cv(v) };
|
|
75
|
+
}
|
|
76
|
+
/** A city-local rect (inclusive) as a world rect (inclusive). */
|
|
77
|
+
export function capWorldRect(r) {
|
|
78
|
+
return { minX: cu(r.u0), maxX: cu(r.u1), minZ: cv(r.v0), maxZ: cv(r.v1) };
|
|
79
|
+
}
|
|
80
|
+
/** The city's size in tiles, outer row to outer row. */
|
|
81
|
+
const SPAN_U = CAPITAL_MAX_X - CAPITAL_MIN_X; // 280
|
|
82
|
+
const SPAN_V = CAPITAL_SOUTH_Z - CAPITAL_NORTH_Z; // 168
|
|
83
|
+
/** The buildable interior, city-local (u 2-278, v 2-166). */
|
|
84
|
+
const IN_U0 = CAPITAL_WALL_THICK;
|
|
85
|
+
const IN_U1 = SPAN_U - CAPITAL_WALL_THICK;
|
|
86
|
+
const IN_V0 = CAPITAL_WALL_THICK;
|
|
87
|
+
const IN_V1 = SPAN_V - CAPITAL_WALL_THICK;
|
|
88
|
+
/** The plan's axes, city-local. */
|
|
89
|
+
const WAY_U = 152; // the Royal Way's centre column
|
|
90
|
+
const AVENUE_V = 84; // the King's Road, the keep axis, the Harbour Road
|
|
91
|
+
const CROWN_V = 64; // Crown Street
|
|
92
|
+
const NOBLE_V = 104; // the Noble Walk
|
|
93
|
+
const KEEP_U = 196; // the keep's centre tile
|
|
94
|
+
const PLATEAU_U0 = 166; // the plateau's west edge (the curtain)
|
|
95
|
+
const PLATEAU_U1 = 214; // the plateau's east edge
|
|
96
|
+
/* ------------------------------------------------------------------ *
|
|
97
|
+
* Walls, moat, heights, works.
|
|
98
|
+
* ------------------------------------------------------------------ */
|
|
28
99
|
/** The moat: eight tiles of water outside the wall; the north side runs into the sea. */
|
|
29
100
|
export const CAPITAL_MOAT_W = 8;
|
|
30
101
|
export const CAPITAL_MOAT_MIN_X = CAPITAL_MIN_X - CAPITAL_MOAT_W;
|
|
31
102
|
export const CAPITAL_MOAT_MAX_X = CAPITAL_MAX_X + CAPITAL_MOAT_W;
|
|
32
|
-
export const CAPITAL_MOAT_NORTH_Z =
|
|
103
|
+
export const CAPITAL_MOAT_NORTH_Z = CAPITAL_NORTH_Z - 9;
|
|
33
104
|
export const CAPITAL_MOAT_SOUTH_Z = CAPITAL_SOUTH_Z + CAPITAL_MOAT_W;
|
|
105
|
+
/** The King's Road's rows: the keep axis, the Great Gate and the East Gate. 160 after the northern extension. */
|
|
106
|
+
export const CAPITAL_AVENUE_Z = cv(AVENUE_V);
|
|
107
|
+
export const CAPITAL_AVENUE_HALF_W = 2;
|
|
34
108
|
/** The basin before the main gate: the moat widened west so the bridge is long. */
|
|
35
|
-
export const CAPITAL_POOL = {
|
|
109
|
+
export const CAPITAL_POOL = {
|
|
110
|
+
minX: CAPITAL_MIN_X - 24,
|
|
111
|
+
maxX: CAPITAL_MOAT_MIN_X - 1,
|
|
112
|
+
minZ: CAPITAL_AVENUE_Z - 22,
|
|
113
|
+
maxZ: CAPITAL_AVENUE_Z + 22,
|
|
114
|
+
};
|
|
36
115
|
/** Ground inside the walls; the bank outside the moat; the water. */
|
|
37
116
|
export const CAPITAL_FLOOR_H = 2;
|
|
38
117
|
export const CAPITAL_BANK_H = 1;
|
|
39
118
|
export const CAPITAL_MOAT_H = 0;
|
|
40
119
|
/** Everything the city owns, for the sweep, the levelling and the discovery zone. */
|
|
41
|
-
export const CAPITAL_WORKS = {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
120
|
+
export const CAPITAL_WORKS = {
|
|
121
|
+
minX: CAPITAL_MIN_X - 44,
|
|
122
|
+
maxX: CAPITAL_MOAT_MAX_X + 8,
|
|
123
|
+
minZ: CAPITAL_MOAT_NORTH_Z,
|
|
124
|
+
maxZ: CAPITAL_MOAT_SOUTH_Z + 3,
|
|
125
|
+
};
|
|
126
|
+
/* ------------------------------------------------------------------ *
|
|
127
|
+
* The axes and the gates.
|
|
128
|
+
* ------------------------------------------------------------------ */
|
|
129
|
+
/** The Royal Way: seven wide (u 149-155, Path kerbs on its outer columns), a grass verge four wide either side. */
|
|
130
|
+
export const CAPITAL_ROYAL_WAY_X = cu(WAY_U);
|
|
131
|
+
export const CAPITAL_ROYAL_WAY_HALF_W = 3;
|
|
132
|
+
export const CAPITAL_ROYAL_WAY_VERGE = 4;
|
|
133
|
+
/** Crown Street, five wide on the painted road (v 62-66). */
|
|
134
|
+
export const CAPITAL_CROWN_Z = cv(CROWN_V);
|
|
135
|
+
/** The Noble Walk, five wide along the mound's south foot (v 102-106). */
|
|
136
|
+
export const CAPITAL_NOBLE_WALK_Z = cv(NOBLE_V);
|
|
137
|
+
/** Primary routes are five wide: half-width two, Stone with Path kerb rows. */
|
|
138
|
+
export const CAPITAL_PRIMARY_HALF_W = 2;
|
|
139
|
+
/** Every gate passage is five wide. */
|
|
46
140
|
export const CAPITAL_GATE_HALF_W = 2;
|
|
141
|
+
/** The Great Gate in the west wall, open: the land arrival (decision D1-B, unchanged). */
|
|
47
142
|
export const CAPITAL_MAIN_GATE = { x: CAPITAL_MIN_X, z: CAPITAL_AVENUE_Z };
|
|
143
|
+
/** The East Gate, open: the harbour's Dock Gate. */
|
|
48
144
|
export const CAPITAL_EAST_GATE = { x: CAPITAL_MAX_X, z: CAPITAL_AVENUE_Z };
|
|
49
|
-
|
|
50
|
-
export const
|
|
145
|
+
/** The North Gate at the Way's north end: built, barred (the sea is beyond it). */
|
|
146
|
+
export const CAPITAL_NORTH_GATE = { x: CAPITAL_ROYAL_WAY_X, z: CAPITAL_NORTH_Z };
|
|
147
|
+
/**
|
|
148
|
+
* The South Gate at the Way's south end, the Dreadwoods gate. Open since
|
|
149
|
+
* James's word on D2 (2026-09-25, "yes we should add a pathway to
|
|
150
|
+
* dreadwoods"): the South Bridge carries the Way over the moat to the
|
|
151
|
+
* Dreadwoods north gate, straight into the PvP woods.
|
|
152
|
+
*/
|
|
153
|
+
export const CAPITAL_SOUTH_GATE = { x: CAPITAL_ROYAL_WAY_X, z: CAPITAL_SOUTH_Z };
|
|
154
|
+
/**
|
|
155
|
+
* The four city gates as the builder places them. `style` is the gatehouse
|
|
156
|
+
* style (constants.ts `visitGatehouseOffsets`): a `shut` token keeps the
|
|
157
|
+
* passage blocked in the shared collision, so a barred gate needs nothing
|
|
158
|
+
* else from the builder, and the wall rows under it stay unpaved. The
|
|
159
|
+
* facing letter is always the LAST token (the frontend turns by it).
|
|
160
|
+
*/
|
|
161
|
+
export const CAPITAL_GATES = [
|
|
162
|
+
{ id: 'west', name: 'The Great Gate', x: CAPITAL_MAIN_GATE.x, z: CAPITAL_MAIN_GATE.z, style: 'main-w', open: true },
|
|
163
|
+
{ id: 'east', name: 'The East Gate', x: CAPITAL_EAST_GATE.x, z: CAPITAL_EAST_GATE.z, style: 'side-e', open: true },
|
|
164
|
+
{ id: 'north', name: 'The North Gate', x: CAPITAL_NORTH_GATE.x, z: CAPITAL_NORTH_GATE.z, style: 'main-shut-n', open: false },
|
|
165
|
+
{ id: 'south', name: 'The South Gate', x: CAPITAL_SOUTH_GATE.x, z: CAPITAL_SOUTH_GATE.z, style: 'main-s', open: true },
|
|
166
|
+
];
|
|
167
|
+
/** The first interior tile inside each open gate's passage, where the walks are measured from. */
|
|
168
|
+
export const CAPITAL_WEST_MOUTH = { u: IN_U0, v: AVENUE_V, x: cu(IN_U0), z: cv(AVENUE_V) };
|
|
169
|
+
export const CAPITAL_EAST_MOUTH = { u: IN_U1, v: AVENUE_V, x: cu(IN_U1), z: cv(AVENUE_V) };
|
|
51
170
|
/** The bridge over the pool: seven rows, the outer two carry the parapet and the knights. */
|
|
52
171
|
export const CAPITAL_BRIDGE_MIN_X = CAPITAL_POOL.minX - 2;
|
|
53
172
|
/** The bridge's crown: the middle third of the deck stands one height up. */
|
|
54
173
|
export const CAPITAL_BRIDGE_CROWN_H = CAPITAL_FLOOR_H + 1;
|
|
55
|
-
/** Small plazas where the cross streets meet the Royal Way: the Way widened by two rows each side. */
|
|
56
|
-
export const CAPITAL_WAY_PLAZA_HALF_X = 4;
|
|
57
|
-
export const CAPITAL_WAY_PLAZA_HALF_Z = CAPITAL_AVENUE_HALF_W + 2;
|
|
58
174
|
export const CAPITAL_BRIDGE_MAX_X = CAPITAL_MIN_X - 1;
|
|
59
175
|
export const CAPITAL_BRIDGE_HALF_W = 3;
|
|
60
176
|
/** The barbican on the far bank, astride the road before the bridge. */
|
|
@@ -65,114 +181,703 @@ export const CAPITAL_BRIDGE_KNIGHTS = [
|
|
|
65
181
|
{ x: CAPITAL_BRIDGE_MIN_X + 17, z: CAPITAL_AVENUE_Z - CAPITAL_BRIDGE_HALF_W, facing: 's' },
|
|
66
182
|
{ x: CAPITAL_BRIDGE_MIN_X + 17, z: CAPITAL_AVENUE_Z + CAPITAL_BRIDGE_HALF_W, facing: 'n' },
|
|
67
183
|
];
|
|
68
|
-
/**
|
|
184
|
+
/**
|
|
185
|
+
* The South Bridge (D2): the Way's seven rows carried on over the south moat,
|
|
186
|
+
* from the South Gate's passage to the grass strip under the Dreadwoods
|
|
187
|
+
* north wall. The deck is terrain at the city floor, the parapets run along
|
|
188
|
+
* its east and west edges.
|
|
189
|
+
*/
|
|
190
|
+
export const CAPITAL_SOUTH_BRIDGE = {
|
|
191
|
+
minX: CAPITAL_ROYAL_WAY_X - CAPITAL_BRIDGE_HALF_W,
|
|
192
|
+
maxX: CAPITAL_ROYAL_WAY_X + CAPITAL_BRIDGE_HALF_W,
|
|
193
|
+
minZ: CAPITAL_SOUTH_Z + 1,
|
|
194
|
+
maxZ: DEADLANDS_MIN_Z - 1,
|
|
195
|
+
};
|
|
196
|
+
/** The rows that cross water (the moat); the rest of the bridge is its landing on the strip. */
|
|
197
|
+
export const CAPITAL_SOUTH_BRIDGE_DECK_MAX_Z = CAPITAL_MOAT_SOUTH_Z;
|
|
198
|
+
export function onCapitalSouthBridge(x, z) {
|
|
199
|
+
return x >= CAPITAL_SOUTH_BRIDGE.minX && x <= CAPITAL_SOUTH_BRIDGE.maxX && z >= CAPITAL_SOUTH_BRIDGE.minZ && z <= CAPITAL_SOUTH_BRIDGE.maxZ;
|
|
200
|
+
}
|
|
201
|
+
/** The Dreadwoods gate the South Bridge meets: five tiles cut from the woods' north wall on the Way's line. */
|
|
202
|
+
export const DREADWOODS_NORTH_GATE_X = CAPITAL_ROYAL_WAY_X;
|
|
203
|
+
export const DREADWOODS_NORTH_GATE_HALF_W = 2;
|
|
204
|
+
export function inDreadwoodsNorthGate(x, z) {
|
|
205
|
+
return z === DEADLANDS_MIN_Z && Math.abs(x - DREADWOODS_NORTH_GATE_X) <= DREADWOODS_NORTH_GATE_HALF_W;
|
|
206
|
+
}
|
|
207
|
+
/** The road in, gate throat included, until it gives out in the ash. */
|
|
208
|
+
export const DREADWOODS_NORTH_ROAD_INNER_TILES = 8;
|
|
209
|
+
export function inDreadwoodsNorthRoad(x, z) {
|
|
210
|
+
return (Math.abs(x - DREADWOODS_NORTH_GATE_X) <= DREADWOODS_NORTH_GATE_HALF_W &&
|
|
211
|
+
z >= DEADLANDS_MIN_Z &&
|
|
212
|
+
z <= DEADLANDS_MIN_Z + DREADWOODS_NORTH_ROAD_INNER_TILES);
|
|
213
|
+
}
|
|
214
|
+
/** The ring lane inside the wall: three wide, one tile in (u 2-4, u 276-278, v 2-4, v 164-166). */
|
|
69
215
|
export const CAPITAL_RING_INSET = 1;
|
|
70
216
|
export const CAPITAL_STREET_HALF_W = 1;
|
|
71
|
-
|
|
217
|
+
/* ------------------------------------------------------------------ *
|
|
218
|
+
* The mound and the keep.
|
|
219
|
+
* ------------------------------------------------------------------ */
|
|
72
220
|
/**
|
|
73
|
-
* The
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
221
|
+
* The keep's mound: a plateau at CAPITAL_MOUND_H with a slope of one height
|
|
222
|
+
* a tile all round it (u 160-220, v 66-102), the King's Road climbing the
|
|
223
|
+
* west slope as the ramp (u 160-165) to the Inner Gate. Crown Street runs on
|
|
224
|
+
* its north foot, the Noble Walk on its south foot, the Harbour Road stops at
|
|
225
|
+
* its east foot.
|
|
78
226
|
*/
|
|
79
|
-
export const
|
|
80
|
-
export const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
227
|
+
export const CAPITAL_MOUND_H = 8;
|
|
228
|
+
export const CAPITAL_PLATEAU = {
|
|
229
|
+
minX: cu(PLATEAU_U0),
|
|
230
|
+
maxX: cu(PLATEAU_U1),
|
|
231
|
+
minZ: CAPITAL_AVENUE_Z - 12,
|
|
232
|
+
maxZ: CAPITAL_AVENUE_Z + 12,
|
|
233
|
+
};
|
|
234
|
+
/** The inner gatehouse on the plateau's west edge; the curtain runs round the edge with this one gap. */
|
|
235
|
+
export const CAPITAL_INNER_GATE = { x: CAPITAL_PLATEAU.minX + 2, z: CAPITAL_AVENUE_Z };
|
|
236
|
+
/**
|
|
237
|
+
* The keep, thirty tiles in from the plateau's west edge, door west onto the
|
|
238
|
+
* Audience Court (decision D3 default). Its half-extents are constants.ts's
|
|
239
|
+
* CASTLE_KEEP_HALF_X/Z (the collision and the asset read those).
|
|
240
|
+
*/
|
|
241
|
+
export const CAPITAL_KEEP = { x: cu(KEEP_U), z: CAPITAL_AVENUE_Z };
|
|
242
|
+
/** The King's Statue in the Audience Court, between the Inner Gate and the keep door. */
|
|
243
|
+
export const CAPITAL_KING_STATUE = { x: CAPITAL_PLATEAU.minX + 10, z: CAPITAL_AVENUE_Z };
|
|
244
|
+
/** The King's Fountain on the Way's axis in the Crown Cross; the Way splits round it. */
|
|
245
|
+
export const CAPITAL_KINGS_FOUNTAIN = { x: CAPITAL_ROYAL_WAY_X, z: cv(74) };
|
|
246
|
+
/**
|
|
247
|
+
* The stand in the Drill Square with a clear three-tile ring round it: the
|
|
248
|
+
* outfit capture pass, walk-capture and the smokes' closing /tp use it.
|
|
249
|
+
*/
|
|
250
|
+
export const CAPITAL_DRILL_STAND = { x: cu(140), z: cv(116) };
|
|
251
|
+
/** The road in: from the Thaw arch's east road, round to the barbican. */
|
|
252
|
+
export const CAPITAL_ROAD = [
|
|
253
|
+
{ x: CAPITAL_MIN_X - 194, z: CAPITAL_AVENUE_Z - 46 },
|
|
254
|
+
{ x: CAPITAL_MIN_X - 150, z: CAPITAL_AVENUE_Z - 44 },
|
|
255
|
+
{ x: CAPITAL_MIN_X - 90, z: CAPITAL_AVENUE_Z - 26 },
|
|
256
|
+
{ x: CAPITAL_MIN_X - 50, z: CAPITAL_AVENUE_Z - 4 },
|
|
257
|
+
{ x: CAPITAL_BARBICAN.x - 4, z: CAPITAL_AVENUE_Z },
|
|
258
|
+
];
|
|
259
|
+
function capRect(id, u0, u1, v0, v1) {
|
|
260
|
+
return { id, u0, u1, v0, v1, minX: cu(u0), maxX: cu(u1), minZ: cv(v0), maxZ: cv(v1) };
|
|
261
|
+
}
|
|
262
|
+
function capTile(u, v) {
|
|
263
|
+
return { u, v, x: cu(u), z: cv(v) };
|
|
264
|
+
}
|
|
265
|
+
export function capitalSurface(ground) {
|
|
266
|
+
switch (ground) {
|
|
267
|
+
case 'stone':
|
|
268
|
+
case 'flag':
|
|
269
|
+
return SurfaceType.Stone;
|
|
270
|
+
case 'setts':
|
|
271
|
+
case 'path':
|
|
272
|
+
case 'gravel':
|
|
273
|
+
return SurfaceType.Path;
|
|
274
|
+
case 'dirt':
|
|
275
|
+
return SurfaceType.Dirt;
|
|
276
|
+
default:
|
|
277
|
+
return SurfaceType.Grass;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
function route(id, u0, u1, v0, v1, spec) {
|
|
281
|
+
return { ...capRect(id, u0, u1, v0, v1), ...spec };
|
|
282
|
+
}
|
|
283
|
+
const LAMPS16 = { every: 16, alternate: true };
|
|
284
|
+
export const CAPITAL_ROUTES = [
|
|
285
|
+
// The spine and the primaries.
|
|
286
|
+
route('royal-way', WAY_U - CAPITAL_ROYAL_WAY_HALF_W, WAY_U + CAPITAL_ROYAL_WAY_HALF_W, IN_V0, IN_V1, { name: 'The Royal Way', kind: 'way', surface: 'stone', kerb: true, verge: CAPITAL_ROYAL_WAY_VERGE }),
|
|
287
|
+
route('crown-street-w', 5, 140, CROWN_V - 2, CROWN_V + 2, { name: 'Crown Street', kind: 'primary', surface: 'stone', kerb: true, lamps: LAMPS16 }),
|
|
288
|
+
route('crown-street-e', 160, 225, CROWN_V - 2, CROWN_V + 2, { name: 'Crown Street', kind: 'primary', surface: 'stone', kerb: true, lamps: LAMPS16 }),
|
|
289
|
+
route('kings-road', IN_U0, 148, AVENUE_V - 2, AVENUE_V + 2, { name: "The King's Road", kind: 'primary', surface: 'stone', kerb: true, lamps: LAMPS16 }),
|
|
290
|
+
route('kings-road-ramp', 160, PLATEAU_U0 - 1, AVENUE_V - 2, AVENUE_V + 2, { name: "The King's Road", kind: 'primary', surface: 'stone', kerb: true }),
|
|
291
|
+
route('harbour-road', 221, IN_U1, AVENUE_V - 2, AVENUE_V + 2, { name: 'The Harbour Road', kind: 'primary', surface: 'stone', kerb: true, lamps: LAMPS16 }),
|
|
292
|
+
// The ring lane: the guards' beat and the back way round.
|
|
293
|
+
route('ring-w', IN_U0, IN_U0 + 2, IN_V0, IN_V1, { name: 'The Ring Lane', kind: 'lane', surface: 'path' }),
|
|
294
|
+
route('ring-e', IN_U1 - 2, IN_U1, IN_V0, IN_V1, { name: 'The Ring Lane', kind: 'lane', surface: 'path' }),
|
|
295
|
+
route('ring-n', IN_U0, IN_U1, IN_V0, IN_V0 + 2, { name: 'The Ring Lane', kind: 'lane', surface: 'path' }),
|
|
296
|
+
route('ring-s', IN_U0, IN_U1, IN_V1 - 2, IN_V1, { name: 'The Ring Lane', kind: 'lane', surface: 'path' }),
|
|
297
|
+
// Westgate.
|
|
298
|
+
route('cooks-lane', 20, 25, 67, 77, { name: "Cook's Lane", kind: 'lane', surface: 'path' }),
|
|
299
|
+
route('outpost-lane', 30, 32, 99, 116, { name: 'Outpost Lane', kind: 'lane', surface: 'path' }),
|
|
300
|
+
// Forge and Military.
|
|
301
|
+
route('colliers-lane', 91, 93, 107, 119, { name: "Collier's Lane", kind: 'lane', surface: 'dirt' }),
|
|
302
|
+
route('forge-lane', 45, 104, 120, 122, { name: 'Forge Lane', kind: 'lane', surface: 'dirt' }),
|
|
303
|
+
route('timber-path', 55, 57, 112, 119, { name: 'Timber Yard path', kind: 'walk', surface: 'dirt' }),
|
|
304
|
+
route('drill-lane', 105, 107, 87, 163, { name: 'Drill Lane', kind: 'lane', surface: 'stone' }),
|
|
305
|
+
// Temple Gardens.
|
|
306
|
+
route('temple-walk', 164, 262, 29, 31, { name: 'Temple Walk', kind: 'walk', surface: 'gravel' }),
|
|
307
|
+
route('graveyard-path', 191, 195, 34, 37, { name: 'Graveyard path', kind: 'walk', surface: 'gravel' }),
|
|
308
|
+
route('shrine-walk', 216, 220, 38, 61, { name: 'Shrine Walk', kind: 'walk', surface: 'flag' }),
|
|
309
|
+
route('memorial-path', 252, 256, 32, 37, { name: 'Memorial path', kind: 'walk', surface: 'gravel' }),
|
|
310
|
+
route('cloister-path', 249, 251, 20, 28, { name: 'Cloister Garth path', kind: 'walk', surface: 'gravel' }),
|
|
311
|
+
// The Keep and the Royal Quarter.
|
|
312
|
+
route('keep-east-walk', 221, 225, 67, 101, { name: 'Keep East Walk', kind: 'walk', surface: 'stone' }),
|
|
313
|
+
// Noble Quarter.
|
|
314
|
+
route('noble-walk', 160, 276, NOBLE_V - 2, NOBLE_V + 2, { name: 'The Noble Walk', kind: 'walk', surface: 'gravel' }),
|
|
315
|
+
route('manor-walk', 166, 250, 131, 137, { name: 'Manor Walk', kind: 'walk', surface: 'gravel' }),
|
|
316
|
+
route('garden-walk', 203, 213, 107, 163, { name: 'Garden Walk', kind: 'walk', surface: 'gravel' }),
|
|
317
|
+
route('blue-garden-path', 181, 183, 124, 129, { name: 'Blue Manor garden path', kind: 'walk', surface: 'gravel' }),
|
|
318
|
+
route('green-garden-path', 233, 235, 124, 129, { name: 'Green Manor garden path', kind: 'walk', surface: 'gravel' }),
|
|
319
|
+
route('red-garden-path', 181, 183, 139, 146, { name: 'Red Manor garden path', kind: 'walk', surface: 'gravel' }),
|
|
320
|
+
route('purple-garden-path', 233, 235, 139, 146, { name: 'Purple Manor garden path', kind: 'walk', surface: 'gravel' }),
|
|
321
|
+
];
|
|
322
|
+
function plaza(id, u0, u1, v0, v1, spec) {
|
|
323
|
+
return { ...capRect(id, u0, u1, v0, v1), ...spec };
|
|
324
|
+
}
|
|
325
|
+
export const CAPITAL_PLAZAS = [
|
|
326
|
+
// Westgate.
|
|
327
|
+
plaza('gate-square', 5, 44, 78, 98, { name: 'Gate Square', surface: 'stone' }),
|
|
328
|
+
plaza('gate-apron-w', 9, 19, 99, 99, { name: 'Gate Square aprons', surface: 'stone', apron: true, forLandmark: 'wg-guard' }),
|
|
329
|
+
plaza('gate-apron-depot', 19, 29, 99, 99, { name: 'Gate Square aprons', surface: 'stone', apron: true, forLandmark: 'wg-depot' }),
|
|
330
|
+
plaza('gate-apron-e', 33, 43, 99, 99, { name: 'Gate Square aprons', surface: 'stone', apron: true, forLandmark: 'wg-ranger' }),
|
|
331
|
+
// Trade.
|
|
332
|
+
plaza('warehouse-apron', 31, 37, 11, 17, { name: 'Warehouse apron', surface: 'setts', apron: true }),
|
|
333
|
+
plaza('market-square', 42, 92, 16, 52, { name: 'Market Square', surface: 'setts' }),
|
|
334
|
+
plaza('market-link-w', 38, 41, 28, 50, { name: 'Shop fronts', surface: 'setts', apron: true }),
|
|
335
|
+
plaza('market-link-e', 93, 96, 27, 41, { name: 'Coin Square link', surface: 'setts', apron: true }),
|
|
336
|
+
plaza('warehouse-link-w', 38, 41, 11, 17, { name: 'Warehouse link', surface: 'setts', apron: true }),
|
|
337
|
+
plaza('warehouse-link-n', 42, 60, 12, 15, { name: 'Warehouse link', surface: 'setts', apron: true }),
|
|
338
|
+
plaza('market-gate-walk', 64, 70, 53, 56, { name: 'Market Gate walk', surface: 'setts', apron: true }),
|
|
339
|
+
plaza('shop-aprons', 35, 37, 28, 50, { name: 'Shop fronts', surface: 'setts', apron: true }),
|
|
340
|
+
plaza('coin-square', 97, 140, 27, 41, { name: 'Coin Square', surface: 'stone' }),
|
|
341
|
+
plaza('food-court', 17, 40, 50, 61, { name: 'Food Court', surface: 'setts' }),
|
|
342
|
+
plaza('market-gate-apron', 60, 74, 57, 61, { name: 'Market Gate', surface: 'setts', apron: true }),
|
|
343
|
+
// The Royal Way.
|
|
344
|
+
plaza('north-gate-court', 141, 163, 4, 10, { name: 'North Gate court', surface: 'stone' }),
|
|
345
|
+
plaza('victory-promenade-w', 141, 144, 11, 58, { name: 'Victory Plaza', surface: 'stone' }),
|
|
346
|
+
plaza('victory-promenade-e', 160, 163, 11, 58, { name: 'Victory Plaza', surface: 'stone' }),
|
|
347
|
+
plaza('victory-plinth-n', 145, 159, 15, 25, { name: 'Victory Plaza', surface: 'stone' }),
|
|
348
|
+
plaza('victory-plinth-s', 145, 159, 42, 50, { name: 'Victory Plaza', surface: 'stone' }),
|
|
349
|
+
plaza('crown-cross', 141, 159, 60, 90, { name: 'The Crown Cross', surface: 'stone' }),
|
|
350
|
+
plaza('noble-cross', 145, 159, 99, 109, { name: 'The Noble Cross', surface: 'stone' }),
|
|
351
|
+
plaza('south-gate-court', 141, 163, 154, 163, { name: 'South Gate court', surface: 'stone' }),
|
|
352
|
+
// Forge.
|
|
353
|
+
plaza('anvil-square', 59, 89, 87, 106, { name: 'Anvil Square', surface: 'stone' }),
|
|
354
|
+
plaza('artisan-apron', 58, 66, 123, 123, { name: 'Forge Lane aprons', surface: 'stone', apron: true, forLandmark: 'fg-artisan' }),
|
|
355
|
+
plaza('alchemy-apron', 82, 90, 123, 123, { name: 'Forge Lane aprons', surface: 'stone', apron: true, forLandmark: 'fg-alchemy' }),
|
|
356
|
+
// Military.
|
|
357
|
+
plaza('barracks-apron', 108, 140, 87, 87, { name: 'Barracks apron', surface: 'stone', apron: true }),
|
|
358
|
+
plaza('drill-square', 108, 144, 101, 121, { name: 'Drill Square', surface: 'stone' }),
|
|
359
|
+
// Temple Gardens.
|
|
360
|
+
plaza('temple-close', 176, 208, 27, 33, { name: 'Temple Close', surface: 'flag' }),
|
|
361
|
+
plaza('garden-hub', 211, 225, 24, 37, { name: 'Garden Hub', surface: 'flag' }),
|
|
362
|
+
plaza('shrine-apron', 221, 222, 49, 55, { name: 'Healing Shrine apron', surface: 'flag', apron: true }),
|
|
363
|
+
plaza('guild-apron', 164, 164, 39, 49, { name: 'Guild Hall apron', surface: 'stone', apron: true }),
|
|
364
|
+
// The Keep and the Royal Quarter.
|
|
365
|
+
plaza('audience-court', 171, 183, 73, 95, { name: 'The Audience Court', surface: 'stone' }),
|
|
366
|
+
plaza('royal-square', 226, 255, 62, 81, { name: 'Royal Square', surface: 'stone' }),
|
|
367
|
+
plaza('east-gate-court', 256, IN_U1, 77, 94, { name: 'East Gate court', surface: 'stone' }),
|
|
368
|
+
plaza('townhouse-apron-w', 232, 240, 87, 88, { name: 'Townhouse aprons', surface: 'stone', apron: true, forLandmark: 'kp-town1' }),
|
|
369
|
+
plaza('townhouse-apron-e', 244, 252, 87, 88, { name: 'Townhouse aprons', surface: 'stone', apron: true, forLandmark: 'kp-town2' }),
|
|
370
|
+
// Noble Quarter.
|
|
371
|
+
plaza('fountain-plaza', 202, 214, 128, 140, { name: 'Fountain Plaza', surface: 'gravel' }),
|
|
372
|
+
// Door aprons laid last in the plan.
|
|
373
|
+
plaza('mainforge-apron', 62, 78, 81, 81, { name: 'Main Forge apron', surface: 'stone', apron: true, forLandmark: 'fg-mainforge' }),
|
|
374
|
+
plaza('stables-apron', 15, 31, 118, 118, { name: 'Stables apron', surface: 'stone', apron: true, forLandmark: 'wg-stables' }),
|
|
375
|
+
plaza('store-apron-w', 109, 115, 74, 81, { name: 'Garrison Store apron', surface: 'stone', apron: true, forLandmark: 'ml-store1' }),
|
|
376
|
+
plaza('store-apron-e', 129, 135, 74, 81, { name: 'Garrison Store apron', surface: 'stone', apron: true, forLandmark: 'ml-store2' }),
|
|
377
|
+
];
|
|
378
|
+
function green(id, u0, u1, v0, v1, spec) {
|
|
379
|
+
return { ...capRect(id, u0, u1, v0, v1), ...spec };
|
|
380
|
+
}
|
|
381
|
+
/*
|
|
382
|
+
* The greenery is the city's mass (the masterplan's defining look is density:
|
|
383
|
+
* built-up districts, trees in every garden and along the roads), and the
|
|
384
|
+
* hero-buildings rule forbids filling emptiness with filler buildings, so the
|
|
385
|
+
* 2026-09-25 review round spent the grove budget (spec E: at most 120 grove
|
|
386
|
+
* entities inside the walls) before anything else: more groves in the walled
|
|
387
|
+
* gardens, lawns with groves between the market and Crown Street, south of
|
|
388
|
+
* the Gate Square, round the Artisan plot, and three grass islands in the
|
|
389
|
+
* Drill Square's corners (an island green lies wholly inside its plaza, so it
|
|
390
|
+
* stays grass; the SE corner is the Drill stand's clear ring). The mound's
|
|
391
|
+
* slope takes none: a grove's trunks share its anchor's height, and the slope
|
|
392
|
+
* rises a height a tile.
|
|
393
|
+
*/
|
|
394
|
+
export const CAPITAL_GREENS = [
|
|
395
|
+
// Westgate.
|
|
396
|
+
green('paddock', 5, 43, 117, 138, { name: 'The Paddock', kind: 'paddock', wall: 'fence', gates: [[31, 117], [43, 128]], groves: 2 }),
|
|
397
|
+
green('westgate-grove', 33, 44, 139, 163, { name: 'Westgate grove', kind: 'orchard', groves: 7 }),
|
|
398
|
+
// The lawn south of the Gate Square, behind the Guardhouse and Depot plots.
|
|
399
|
+
green('westgate-lawn', 5, 29, 107, 116, { name: 'Westgate lawn', kind: 'lawn', groves: 3 }),
|
|
400
|
+
// Trade.
|
|
401
|
+
green('merchants-garden', 5, 25, 28, 49, { name: "Merchants' Garden", kind: 'garden', groves: 6 }),
|
|
402
|
+
green('trade-north-strip', 42, 108, 5, 15, { name: 'Trade north strip', kind: 'orchard', groves: 8 }),
|
|
403
|
+
green('exchange-garden', 128, 140, 5, 26, { name: 'Exchange Garden', kind: 'garden', groves: 4 }),
|
|
404
|
+
green('bank-lawn-w', 97, 111, 42, 61, { name: 'Bank lawns', kind: 'lawn', groves: 4 }),
|
|
405
|
+
green('bank-lawn-e', 125, 140, 42, 61, { name: 'Bank lawns', kind: 'lawn', groves: 4 }),
|
|
406
|
+
// The lawns between Market Square and Crown Street, either side of the Market Gate walk.
|
|
407
|
+
green('market-lawn-w', 42, 63, 53, 61, { name: 'Market lawns', kind: 'lawn', groves: 3 }),
|
|
408
|
+
green('market-lawn-e', 71, 96, 53, 61, { name: 'Market lawns', kind: 'lawn', groves: 3 }),
|
|
409
|
+
// The Royal Way's verges.
|
|
410
|
+
green('verge-w', WAY_U - CAPITAL_ROYAL_WAY_HALF_W - CAPITAL_ROYAL_WAY_VERGE, WAY_U - CAPITAL_ROYAL_WAY_HALF_W - 1, IN_V0, IN_V1, { name: 'The Royal Way verge', kind: 'verge' }),
|
|
411
|
+
green('verge-e', WAY_U + CAPITAL_ROYAL_WAY_HALF_W + 1, WAY_U + CAPITAL_ROYAL_WAY_HALF_W + CAPITAL_ROYAL_WAY_VERGE, IN_V0, IN_V1, { name: 'The Royal Way verge', kind: 'verge' }),
|
|
412
|
+
// Forge.
|
|
413
|
+
green('herb-garden', 92, 103, 124, 140, { name: 'Herb Garden', kind: 'garden' }),
|
|
414
|
+
// The lawn round the Artisan plot, north of the Ore Yard (phase 3; lawn until then).
|
|
415
|
+
green('artisan-lawn', 45, 72, 124, 135, { name: 'Artisan lawn', kind: 'lawn', groves: 3 }),
|
|
416
|
+
green('forge-lawn', 73, 91, 134, 150, { name: 'Forge lawn', kind: 'lawn', groves: 4 }),
|
|
417
|
+
green('forge-grove', 45, 104, 151, 163, { name: 'Forge Grove', kind: 'orchard', groves: 12 }),
|
|
418
|
+
// Military: grass islands in three corners of the Drill Square, a grove on each.
|
|
419
|
+
green('drill-grove-nw', 111, 115, 102, 106, { name: 'Drill Square trees', kind: 'lawn', groves: 1 }),
|
|
420
|
+
green('drill-grove-ne', 137, 141, 102, 106, { name: 'Drill Square trees', kind: 'lawn', groves: 1 }),
|
|
421
|
+
green('drill-grove-sw', 111, 115, 116, 120, { name: 'Drill Square trees', kind: 'lawn', groves: 1 }),
|
|
422
|
+
// Temple Gardens.
|
|
423
|
+
green('graveyard', 180, 206, 38, 58, { name: 'The Graveyard', kind: 'graveyard', wall: 'garden', gates: [[193, 38]] }),
|
|
424
|
+
green('memorial-garden', 236, 272, 38, 58, { name: 'Memorial Garden', kind: 'garden', wall: 'noble', gates: [[254, 38], [236, 48]], groves: 8 }),
|
|
425
|
+
green('cloister-garth', 238, 262, 20, 28, { name: 'Cloister Garth', kind: 'garden', wall: 'garden', gates: [[250, 28], [250, 20]], groves: 2 }),
|
|
426
|
+
green('temple-orchard-w', 204, 234, 4, 22, { name: 'Temple Orchard', kind: 'orchard', groves: 10 }),
|
|
427
|
+
green('temple-orchard-e', 263, 275, 4, 27, { name: 'Temple Orchard', kind: 'orchard', groves: 6 }),
|
|
428
|
+
green('temple-lawn-w', 165, 179, 50, 61, { name: 'Temple lawns', kind: 'lawn', groves: 2 }),
|
|
429
|
+
green('temple-lawn-e', 207, 215, 38, 61, { name: 'Temple lawns', kind: 'lawn', groves: 2 }),
|
|
430
|
+
// The Keep and the Royal Quarter.
|
|
431
|
+
green('royal-square-lawn', 234, 248, 68, 75, { name: 'Royal Square lawn', kind: 'lawn', wall: 'hedge', gates: [[241, 68], [241, 75], [234, 72], [248, 72]] }),
|
|
432
|
+
green('kings-garden', 252, 275, 108, 163, { name: "The King's Garden", kind: 'garden', groves: 14 }),
|
|
433
|
+
// Noble Quarter: the four walled manor gardens, 2 x 2 round the Noble Fountain.
|
|
434
|
+
green('blue-garden', 166, 201, 109, 130, { name: 'Blue Manor garden', kind: 'garden', wall: 'noble', gates: [[182, 130], [166, 118]] }),
|
|
435
|
+
green('green-garden', 215, 250, 109, 130, { name: 'Green Manor garden', kind: 'garden', wall: 'noble', gates: [[234, 130], [250, 118]] }),
|
|
436
|
+
green('red-garden', 166, 201, 138, 162, { name: 'Red Manor garden', kind: 'garden', wall: 'noble', gates: [[182, 138]] }),
|
|
437
|
+
green('purple-garden', 215, 250, 138, 162, { name: 'Purple Manor garden', kind: 'garden', wall: 'noble', gates: [[234, 138]] }),
|
|
85
438
|
];
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
439
|
+
function yard(id, u0, u1, v0, v1, spec) {
|
|
440
|
+
return { ...capRect(id, u0, u1, v0, v1), ...spec };
|
|
441
|
+
}
|
|
442
|
+
export const CAPITAL_YARDS = [
|
|
443
|
+
yard('warehouse-yard-w', 5, 17, 5, 26, { name: 'Warehouse Yard', surface: 'dirt' }),
|
|
444
|
+
yard('warehouse-yard-s', 5, 40, 20, 26, { name: 'Warehouse Yard', surface: 'dirt' }),
|
|
445
|
+
yard('expedition-camp', 5, 32, 142, 162, { name: 'Expedition Camp', surface: 'dirt' }),
|
|
446
|
+
yard('furnace-yard', 45, 104, 67, 81, { name: 'Furnace Yard', surface: 'dirt' }),
|
|
447
|
+
yard('timber-yard', 45, 57, 104, 119, { name: 'Timber Yard', surface: 'dirt' }),
|
|
448
|
+
yard('charcoal-yard', 58, 90, 107, 118, {
|
|
449
|
+
name: 'Charcoal Yard',
|
|
450
|
+
surface: 'dirt',
|
|
451
|
+
fence: true,
|
|
452
|
+
gates: [[70, 107], [71, 107], [72, 107], [73, 107], [74, 107], [75, 107], [76, 107], [77, 107], [78, 107], [90, 113]],
|
|
453
|
+
}),
|
|
454
|
+
// The Ore Yard's ore crates and ingot stacks are phase-3 props: lawn until then.
|
|
455
|
+
yard('ore-yard', 45, 72, 136, 150, { name: 'Ore Yard', surface: 'dirt', phase: 3 }),
|
|
456
|
+
yard('muster-yard', 106, 140, 67, 80, { name: 'Muster Yard', surface: 'dirt' }),
|
|
457
|
+
yard('training-yard', 108, 144, 124, 142, {
|
|
458
|
+
name: 'Training Yard',
|
|
459
|
+
surface: 'dirt',
|
|
460
|
+
fence: true,
|
|
461
|
+
gates: [[126, 124], [127, 124], [107, 133], [108, 133], [144, 133]],
|
|
462
|
+
}),
|
|
463
|
+
yard('archery-range', 108, 144, 145, 163, { name: 'Archery Range', surface: 'dirt' }),
|
|
95
464
|
];
|
|
96
465
|
/**
|
|
97
|
-
*
|
|
98
|
-
*
|
|
466
|
+
* Eight districts, ids kept from the old wards so no character is paid the
|
|
467
|
+
* Explorer XP twice; the labels and rects are new. The rects do not overlap
|
|
468
|
+
* and together they tile the interior (u 2-278, v 2-166), so lookup order
|
|
469
|
+
* does not matter. The catch-all `capital` zone keeps CAPITAL_WORKS.
|
|
99
470
|
*/
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
export const CAPITAL_MARKET_FOUNTAIN = { x: 512, z: 35 };
|
|
113
|
-
export const CAPITAL_COIN_SQUARE = { minX: 622, maxX: 658, minZ: 38, maxZ: 53 };
|
|
114
|
-
export const CAPITAL_COIN_FOUNTAIN = { x: 640, z: 46 };
|
|
115
|
-
export const CAPITAL_TEMPLE_SQUARE = { minX: 684, maxX: 716, minZ: 40, maxZ: 74 };
|
|
116
|
-
export const CAPITAL_DRILL_SQUARE = { minX: 604, maxX: 636, minZ: 120, maxZ: 136 };
|
|
117
|
-
export const CAPITAL_TRAINING_YARD = { minX: 598, maxX: 642, minZ: 140, maxZ: 154 };
|
|
118
|
-
/** The greens: the graveyard and cloister by the temple, and the noble gardens. */
|
|
119
|
-
export const CAPITAL_GRAVEYARD = { minX: 664, maxX: 688, minZ: 16, maxZ: 32 };
|
|
120
|
-
export const CAPITAL_CLOISTER = { minX: 712, maxX: 750, minZ: 16, maxZ: 32 };
|
|
121
|
-
export const CAPITAL_NOBLE_GARDENS = [
|
|
122
|
-
{ minX: 666, maxX: 692, minZ: 121, maxZ: 144, manor: { x: 679, z: 131, roof: 'red' } },
|
|
123
|
-
{ minX: 706, maxX: 732, minZ: 121, maxZ: 144, manor: { x: 719, z: 131, roof: 'blue' } },
|
|
124
|
-
{ minX: 666, maxX: 692, minZ: 150, maxZ: 173, manor: { x: 679, z: 160, roof: 'blue' } },
|
|
125
|
-
{ minX: 706, maxX: 732, minZ: 150, maxZ: 173, manor: { x: 719, z: 160, roof: 'red' } },
|
|
471
|
+
function district(id, u0, u1, v0, v1, spec) {
|
|
472
|
+
return { ...capRect(id, u0, u1, v0, v1), ...spec, id };
|
|
473
|
+
}
|
|
474
|
+
export const CAPITAL_DISTRICTS = [
|
|
475
|
+
district('capital-castle', 160, IN_U1, 62, 101, { label: 'The Keep', street: 'stone', roofs: ['blue'], emblem: 'trident' }),
|
|
476
|
+
district('capital-merchant', 145, 159, IN_V0, IN_V1, { label: 'The Royal Way', street: 'stone', roofs: ['blue'], emblem: 'crown' }),
|
|
477
|
+
district('capital-gate', IN_U0, 44, 62, IN_V1, { label: 'Westgate Outpost', street: 'stone', roofs: ['blue'], emblem: 'tree' }),
|
|
478
|
+
district('capital-market', IN_U0, 144, IN_V0, 61, { label: 'Trade District', street: 'path', roofs: ['blue'], emblem: 'scales' }),
|
|
479
|
+
district('capital-smiths', 45, 104, 62, IN_V1, { label: 'Forge and Artisan Quarter', street: 'dirt', roofs: ['blue'], emblem: 'anvil' }),
|
|
480
|
+
district('capital-garrison', 105, 144, 62, IN_V1, { label: 'Military Ward', street: 'stone', roofs: ['blue'], emblem: 'lion' }),
|
|
481
|
+
district('capital-temple', 160, IN_U1, IN_V0, 61, { label: 'Temple Gardens', street: 'gravel', roofs: ['blue'], emblem: 'cross' }),
|
|
482
|
+
district('capital-noble', 160, IN_U1, 102, IN_V1, { label: 'Noble Quarter', street: 'gravel', roofs: ['blue', 'green', 'red', 'purple'], emblem: 'trident' }),
|
|
126
483
|
];
|
|
484
|
+
function districtAtLocal(u, v) {
|
|
485
|
+
return CAPITAL_DISTRICTS.find((d) => u >= d.u0 && u <= d.u1 && v >= d.v0 && v <= d.v1)?.id;
|
|
486
|
+
}
|
|
487
|
+
/* ------------------------------------------------------------------ *
|
|
488
|
+
* Buildings and monuments.
|
|
489
|
+
* ------------------------------------------------------------------ */
|
|
490
|
+
/**
|
|
491
|
+
* The phase the city is built to. A plan row whose `phase` is later than
|
|
492
|
+
* this stands as its `standIn` if the stand-in's phase has come, and
|
|
493
|
+
* otherwise is not built yet; its footprint is still reserved, so the later
|
|
494
|
+
* phase lands on a clear plot and reshuffles nothing.
|
|
495
|
+
*/
|
|
496
|
+
export const CAPITAL_BUILD_PHASE = 1;
|
|
497
|
+
/** Exactly the plan script's `b(id, ch, name, cu, cv, halfU, halfV, face, entry)`. */
|
|
498
|
+
function landmark(id, name, u, v, hu, hv, face, mouth, spec) {
|
|
499
|
+
let door = null;
|
|
500
|
+
if (face) {
|
|
501
|
+
const du = face === 'e' ? u + hu : face === 'w' ? u - hu : u;
|
|
502
|
+
const dv = face === 's' ? v + hv : face === 'n' ? v - hv : v;
|
|
503
|
+
door = { ...capTile(du, dv), facing: face, mouth };
|
|
504
|
+
}
|
|
505
|
+
const district = districtAtLocal(u, v);
|
|
506
|
+
if (!district)
|
|
507
|
+
throw new Error(`capital landmark ${id} is outside every district`);
|
|
508
|
+
return {
|
|
509
|
+
id,
|
|
510
|
+
name,
|
|
511
|
+
district,
|
|
512
|
+
...spec,
|
|
513
|
+
u,
|
|
514
|
+
v,
|
|
515
|
+
x: cu(u),
|
|
516
|
+
z: cv(v),
|
|
517
|
+
footprint: capRect(id, u - hu, u + hu, v - hv, v + hv),
|
|
518
|
+
door,
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
const SOLID = null;
|
|
127
522
|
/**
|
|
128
|
-
* The
|
|
129
|
-
*
|
|
523
|
+
* The buildings, in placing order (spec B, plan script `B`). Phase-1 rows
|
|
524
|
+
* are REUSE kinds on their final plots; the Grand Exchange stands as its
|
|
525
|
+
* Blender building (decision D5); the Capital Bank, the Royal
|
|
526
|
+
* Administration and the Barracks stand as their stand-ins; the sheds, the
|
|
527
|
+
* new stonehouse variants and the crypt wait for phases 2 and 3.
|
|
130
528
|
*/
|
|
131
529
|
export const CAPITAL_LANDMARKS = [
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
530
|
+
// Westgate Outpost.
|
|
531
|
+
landmark('wg-bank', 'Outpost Bank', 14, 73, 4, 4, 's', 1, { kind: 'stonehouse', style: 'bank-s', phase: 1, heroFront: true, emblem: 'tree' }),
|
|
532
|
+
landmark('wg-inn', 'Adventurer Inn', 32, 72, 5, 5, 's', 1, { kind: 'stonehouse', style: 'inn-s', phase: 1 }),
|
|
533
|
+
landmark('wg-guard', 'Checkpoint Guardhouse', 10, 103, 3, 3, 'n', 1, { kind: 'stonehouse', style: 'guardhouse-n', phase: 2 }),
|
|
534
|
+
landmark('wg-depot', 'Supply Depot', 24, 103, 5, 3, 'n', 5, { kind: 'shed', style: 'depot-mouth5-n', phase: 2 }),
|
|
535
|
+
landmark('wg-ranger', 'Ranger Hall', 38, 107, 5, 7, 'n', 1, { kind: 'stonehouse', style: 'lodge-n', phase: 1 }),
|
|
536
|
+
landmark('wg-stables', 'Stables and Pack Shed', 18, 122, 6, 3, 'n', 7, { kind: 'shed', style: 'stable-mouth7-n', phase: 2 }),
|
|
537
|
+
// Trade District.
|
|
538
|
+
landmark('tr-warehouse', 'Trade Warehouse', 24, 14, 6, 4, 'e', 1, { kind: 'warehouse', style: 'store-e', phase: 1 }),
|
|
539
|
+
landmark('tr-clothier', 'Clothier', 30, 33, 4, 4, 'e', 1, { kind: 'stonehouse', style: 'clothes-e', phase: 1 }),
|
|
540
|
+
landmark('tr-general', 'General Store', 30, 45, 4, 4, 'e', 1, { kind: 'stonehouse', style: 'shop-e', phase: 1 }),
|
|
541
|
+
landmark('tr-cookhouse', 'Cookhouse', 12, 55, 4, 4, 'e', 1, { kind: 'stonehouse', style: 'kitchen-e', phase: 1 }),
|
|
542
|
+
landmark('tr-ge', 'Grand Exchange', 118, 20, 9, 6, 's', 1, { kind: 'capitalbuilding', style: 'grandexchange-s', phase: 1, emblem: 'scales' }),
|
|
543
|
+
landmark('tr-bank', 'Capital Bank', 118, 48, 6, 6, 'n', 1, {
|
|
544
|
+
kind: 'stonehouse',
|
|
545
|
+
style: 'exchequer-n',
|
|
546
|
+
phase: 2,
|
|
547
|
+
// Centred on the plot, door two rows in from the plot's: pave (u 118, v 42-43) to join it.
|
|
548
|
+
standIn: { kind: 'stonehouse', style: 'bank-n' },
|
|
549
|
+
heroFront: true,
|
|
550
|
+
emblem: 'scales',
|
|
551
|
+
}),
|
|
552
|
+
// The Royal Way's civic hall, on Victory Plaza's east promenade.
|
|
553
|
+
landmark('rw-guild', 'Guild Hall', 170, 44, 5, 5, 'w', 1, { kind: 'stonehouse', style: 'hall-w', phase: 1, heroFront: true, emblem: 'crown' }),
|
|
554
|
+
// Forge and Artisan Quarter.
|
|
555
|
+
landmark('fg-mainforge', 'Main Forge', 70, 74, 8, 6, 's', 5, { kind: 'capitalbuilding', style: 'mainforge-mouth5-s', phase: 1 }),
|
|
556
|
+
landmark('fg-smelter', 'Smelter', 88, 74, 3, 3, SOLID, 0, { kind: 'capitalbuilding', style: 'smelter-s', phase: 1 }),
|
|
557
|
+
landmark('fg-smith', 'Blacksmith', 54, 97, 4, 5, 'e', 1, { kind: 'forgehall', style: 'forge-e', phase: 1, board: 'Blacksmith', emblem: 'anvil' }),
|
|
558
|
+
landmark('fg-armour', 'Armourer', 94, 97, 4, 5, 'w', 1, { kind: 'forgehall', style: 'forge-w', phase: 1, board: 'Armourer', emblem: 'anvil' }),
|
|
559
|
+
landmark('fg-kiosk', "Smiths' Strongbox", 74, 89, 1, 0, SOLID, 0, { kind: 'marketstall', style: 'bank-s', phase: 2 }),
|
|
560
|
+
landmark('fg-anvilshed', 'Anvil Shed', 64, 104, 3, 2, 'n', 5, { kind: 'shed', style: 'forge-mouth5-n', phase: 2 }),
|
|
561
|
+
landmark('fg-logshed', 'Forge Yard Shed', 51, 112, 3, 2, 'e', 3, { kind: 'shed', style: 'log-mouth3-e', phase: 2 }),
|
|
562
|
+
landmark('fg-artisan', 'Artisan Workshop', 62, 128, 4, 4, 'n', 1, { kind: 'stonehouse', style: 'artisan-n', phase: 2 }),
|
|
563
|
+
landmark('fg-alchemy', 'Alchemy Hut', 86, 128, 4, 4, 'n', 1, { kind: 'stonehouse', style: 'potion-n', phase: 1 }),
|
|
564
|
+
// Military Ward.
|
|
565
|
+
landmark('ml-barracks', 'Barracks', 122, 93, 10, 5, 'n', 1, {
|
|
566
|
+
kind: 'stonehouse',
|
|
567
|
+
style: 'barracks-n',
|
|
568
|
+
phase: 2,
|
|
569
|
+
// hall-n is 11 x 11 centred on the plot, its door on the plot's own door tile.
|
|
570
|
+
standIn: { kind: 'stonehouse', style: 'hall-n' },
|
|
571
|
+
emblem: 'lion',
|
|
572
|
+
}),
|
|
573
|
+
landmark('ml-store1', 'Garrison Store', 112, 71, 3, 2, 's', 5, { kind: 'shed', style: 'store-mouth5-s', phase: 2 }),
|
|
574
|
+
landmark('ml-store2', 'Garrison Store', 132, 71, 3, 2, 's', 5, { kind: 'shed', style: 'store-mouth5-s', phase: 2 }),
|
|
575
|
+
landmark('ml-tower', 'Yard Watchtower', 110, 127, 1, 1, SOLID, 0, { kind: 'citywall', style: 'tower-watch', phase: 1 }),
|
|
576
|
+
// Temple Gardens.
|
|
577
|
+
landmark('tg-temple', 'Temple of Light', 192, 18, 6, 8, 's', 1, { kind: 'temple', style: 'temple-s', phase: 1, heroFront: true, emblem: 'cross' }),
|
|
578
|
+
landmark('tg-cloister', 'Cloister', 250, 15, 10, 4, 's', 1, {
|
|
579
|
+
kind: 'stonehouse',
|
|
580
|
+
style: 'cloister-s',
|
|
581
|
+
phase: 3,
|
|
582
|
+
// The phase-2 Chapter House: hall-s one row north of the plot's centre, on the same door tile.
|
|
583
|
+
standIn: { kind: 'stonehouse', style: 'hall-s', phase: 2, v: 14 },
|
|
584
|
+
}),
|
|
585
|
+
landmark('tg-shrine', 'Healing Shrine', 226, 52, 3, 3, 'w', 5, { kind: 'shed', style: 'chapel-mouth5-w', phase: 2 }),
|
|
586
|
+
landmark('tg-mausoleum', 'Mausoleum', 193, 50, 2, 3, SOLID, 0, { kind: 'mausoleum', style: 'crypt', phase: 3 }),
|
|
587
|
+
// The Keep and the Royal Quarter. The keep is placed by the mound step, not place().
|
|
588
|
+
// Its door is the gate passage's mouth on the west face, three wide (CASTLE_PASSAGE_HALF_W).
|
|
589
|
+
landmark('kp-keep', 'The Keep', KEEP_U, AVENUE_V, CASTLE_KEEP_HALF_X, CASTLE_KEEP_HALF_Z, 'w', 2 * CASTLE_PASSAGE_HALF_W + 1, { kind: 'castle', style: 'keep', phase: 1 }),
|
|
590
|
+
landmark('kp-admin', 'Royal Administration', 262, 70, 6, 6, 's', 1, {
|
|
591
|
+
kind: 'stonehouse',
|
|
592
|
+
style: 'admin-s',
|
|
593
|
+
phase: 2,
|
|
594
|
+
// hall-s centred on the plot: its door is (262, 75), pave (262, 76) to the court.
|
|
595
|
+
standIn: { kind: 'stonehouse', style: 'hall-s' },
|
|
596
|
+
heroFront: true,
|
|
597
|
+
emblem: 'trident',
|
|
598
|
+
}),
|
|
599
|
+
landmark('kp-town1', 'Townhouse', 236, 93, 4, 4, 'n', 1, { kind: 'stonehouse', style: 'townhouse-n', phase: 2 }),
|
|
600
|
+
landmark('kp-town2', 'Townhouse', 248, 93, 4, 4, 'n', 1, { kind: 'stonehouse', style: 'townhouse-n', phase: 2 }),
|
|
601
|
+
landmark('kp-post', 'East Gate Guard Post', 266, 98, 1, 1, SOLID, 0, { kind: 'citywall', style: 'tower-watch', phase: 1 }),
|
|
602
|
+
// Noble Quarter. Green and purple roofs are the frontend's new manor roof tokens (phase 2); until then it draws its fallback roof.
|
|
603
|
+
landmark('nb-blue', 'Blue Manor', 182, 118, 5, 5, 's', 1, { kind: 'manor', style: 'blue-s', phase: 1 }),
|
|
604
|
+
landmark('nb-green', 'Green Manor', 234, 118, 5, 5, 's', 1, { kind: 'manor', style: 'green-s', phase: 1 }),
|
|
605
|
+
landmark('nb-red', 'Red Manor', 182, 152, 5, 5, 'n', 1, { kind: 'manor', style: 'red-n', phase: 1 }),
|
|
606
|
+
landmark('nb-purple', 'Purple Manor', 234, 152, 5, 5, 'n', 1, { kind: 'manor', style: 'purple-n', phase: 1 }),
|
|
607
|
+
landmark('nb-post', 'Garden Guard Post', 272, 110, 1, 1, SOLID, 0, { kind: 'citywall', style: 'tower-watch', phase: 1 }),
|
|
145
608
|
];
|
|
146
|
-
/**
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
609
|
+
/**
|
|
610
|
+
* A monument row. A statue (the row's own kind, or its stand-in's) stands on
|
|
611
|
+
* a stepped plinth filling its square: the `plinth3` / `plinth5` token
|
|
612
|
+
* (constants.ts statueStyleWithPlinth) goes into its style here, so the
|
|
613
|
+
* builder, the smoke and the client all read the one style, and the shared
|
|
614
|
+
* collision blocks the whole square (spec B.3: the Victory Statue's 5x5
|
|
615
|
+
* stepped plinth; a figure life-size on one tile did not read as a monument).
|
|
616
|
+
*/
|
|
617
|
+
function monument(id, name, u, v, size, spec) {
|
|
618
|
+
const h = (size - 1) / 2;
|
|
619
|
+
const district = districtAtLocal(u, v);
|
|
620
|
+
if (!district)
|
|
621
|
+
throw new Error(`capital monument ${id} is outside every district`);
|
|
622
|
+
const style = spec.kind === 'statue' ? statueStyleWithPlinth(spec.style, size) : spec.style;
|
|
623
|
+
const standIn = spec.standIn?.kind === 'statue' ? { ...spec.standIn, style: statueStyleWithPlinth(spec.standIn.style, size) } : spec.standIn;
|
|
624
|
+
return { id, name, district, ...spec, style, standIn, u, v, x: cu(u), z: cv(v), size, footprint: capRect(id, u - h, u + h, v - h, v + h) };
|
|
625
|
+
}
|
|
626
|
+
const KING = { kind: 'statue', style: 'king' };
|
|
627
|
+
const PLAZA_FOUNTAIN = { kind: 'fountain', style: 'plaza' };
|
|
628
|
+
export const CAPITAL_MONUMENTS = [
|
|
629
|
+
monument('tr-fountain', 'Market Fountain', 67, 34, 3, { kind: 'fountain', style: 'plaza', phase: 1 }),
|
|
630
|
+
monument('tr-coinstatue', 'Coin Square Statue', 118, 34, 5, { kind: 'capitalbuilding', style: 'kingstatue-s', phase: 1, standIn: KING }),
|
|
631
|
+
monument('rw-victory', 'Victory Statue', WAY_U, 20, 5, { kind: 'capitalbuilding', style: 'victorystatue-s', phase: 1, standIn: KING }),
|
|
632
|
+
monument('rw-plinth', 'The Queen', WAY_U, 46, 3, { kind: 'hoodedstatue', style: 'pale', phase: 2 }),
|
|
633
|
+
monument('rw-fountain', "King's Fountain", capU(CAPITAL_KINGS_FOUNTAIN.x), capV(CAPITAL_KINGS_FOUNTAIN.z), 5, { kind: 'fountain', style: 'tiered', phase: 2, standIn: PLAZA_FOUNTAIN }),
|
|
634
|
+
monument('rw-noblecross', 'Noble Cross Statue', WAY_U, NOBLE_V, 3, { kind: 'statue', style: 'knight-n', phase: 1 }),
|
|
635
|
+
monument('tg-hubfountain', 'Stone Fountain', 218, 30, 5, { kind: 'fountain', style: 'tiered', phase: 2, standIn: PLAZA_FOUNTAIN }),
|
|
636
|
+
monument('tg-faith', 'Faith Statue', 254, 48, 3, { kind: 'hoodedstatue', style: 'pale', phase: 2 }),
|
|
637
|
+
monument('kp-statue', "King's Statue", capU(CAPITAL_KING_STATUE.x), AVENUE_V, 5, { kind: 'capitalbuilding', style: 'kingstatue-w', phase: 1, standIn: KING }),
|
|
638
|
+
// The keep lions flank the door (the Blender keeplion, placed in phase 1 on
|
|
639
|
+
// James's word, 2026-09-25). The knight on his plinth is the stand-in a
|
|
640
|
+
// rebuild at an earlier phase gets: a brazier there was one more fire in a
|
|
641
|
+
// court that already had nine in view.
|
|
642
|
+
monument('kp-lion-n', 'Keep Lion', 181, AVENUE_V - 5, 3, { kind: 'capitalbuilding', style: 'keeplion-w', phase: 1, standIn: { kind: 'statue', style: 'knight-w' } }),
|
|
643
|
+
monument('kp-lion-s', 'Keep Lion', 181, AVENUE_V + 5, 3, { kind: 'capitalbuilding', style: 'keeplion-w', phase: 1, standIn: { kind: 'statue', style: 'knight-w' } }),
|
|
644
|
+
monument('nb-fountain', 'Noble Fountain', 208, 134, 5, { kind: 'fountain', style: 'tiered', phase: 2, standIn: PLAZA_FOUNTAIN }),
|
|
154
645
|
];
|
|
155
|
-
/**
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
646
|
+
/**
|
|
647
|
+
* What stands for a plan row in `phase` (default CAPITAL_BUILD_PHASE): the
|
|
648
|
+
* row itself once its phase has come, else its stand-in once the stand-in's
|
|
649
|
+
* phase has come, else null (not built yet; the plot stays reserved).
|
|
650
|
+
*/
|
|
651
|
+
export function capitalPlacementNow(row, phase = CAPITAL_BUILD_PHASE) {
|
|
652
|
+
if (row.phase <= phase && row.kind !== 'shed' && row.kind !== 'mausoleum') {
|
|
653
|
+
return { kind: row.kind, style: row.style, u: row.u, v: row.v, x: cu(row.u), z: cv(row.v), standIn: false };
|
|
654
|
+
}
|
|
655
|
+
const s = row.standIn;
|
|
656
|
+
if (s && (s.phase ?? 1) <= phase) {
|
|
657
|
+
const u = s.u ?? row.u;
|
|
658
|
+
const v = s.v ?? row.v;
|
|
659
|
+
return { kind: s.kind, style: s.style, u, v, x: cu(u), z: cv(v), standIn: true };
|
|
660
|
+
}
|
|
661
|
+
return null;
|
|
662
|
+
}
|
|
663
|
+
/** The tiles a door opens on the ring: its middle tile and the rest of its mouth, along the facing side. */
|
|
664
|
+
export function capitalDoorTiles(door) {
|
|
665
|
+
const half = Math.floor((door.mouth - 1) / 2);
|
|
666
|
+
const out = [];
|
|
667
|
+
for (let i = -half; i <= half; i++) {
|
|
668
|
+
out.push(door.facing === 'n' || door.facing === 's' ? capTile(door.u + i, door.v) : capTile(door.u, door.v + i));
|
|
669
|
+
}
|
|
670
|
+
return out;
|
|
671
|
+
}
|
|
672
|
+
/** The approach tiles: one step out from each door tile. Nothing may stand on these (nor on the door). */
|
|
673
|
+
export function capitalDoorApproach(door) {
|
|
674
|
+
const [du, dv] = door.facing === 'n' ? [0, -1] : door.facing === 's' ? [0, 1] : door.facing === 'e' ? [1, 0] : [-1, 0];
|
|
675
|
+
return capitalDoorTiles(door).map((t) => capTile(t.u + du, t.v + dv));
|
|
676
|
+
}
|
|
677
|
+
function station(id, kind, u, v, range, extra = {}) {
|
|
678
|
+
const { phase = 1, ...rest } = extra;
|
|
679
|
+
return { id, kind, ...capTile(u, v), range, phase, ...rest };
|
|
680
|
+
}
|
|
681
|
+
const PICKUP = 1;
|
|
682
|
+
export const CAPITAL_STATIONS = [
|
|
683
|
+
// Bank points: the Outpost Bank, the Capital Bank, the Smiths' Strongbox.
|
|
684
|
+
station('chest-outpost', 'bankchest', 14, 71, BANK_RANGE, { inside: 'wg-bank' }),
|
|
685
|
+
station('chest-capital', 'bankchest', 116, 47, BANK_RANGE, { inside: 'tr-bank' }),
|
|
686
|
+
station('chest-strongbox', 'bankchest', 74, 90, BANK_RANGE, { inside: 'fg-kiosk' }),
|
|
687
|
+
// Cook spots.
|
|
688
|
+
station('stove-cookhouse', 'stove', 10, 53, CAMPFIRE_COOK_RANGE, { inside: 'tr-cookhouse' }),
|
|
689
|
+
station('stove-inn', 'stove', 29, 69, CAMPFIRE_COOK_RANGE, { inside: 'wg-inn' }),
|
|
690
|
+
station('stove-barracks', 'stove', 114, 90, CAMPFIRE_COOK_RANGE, { inside: 'ml-barracks', phase: 2 }),
|
|
691
|
+
station('fire-muster', 'campfire', 122, 76, CAMPFIRE_COOK_RANGE),
|
|
692
|
+
station('stove-cloister', 'stove', 244, 13, CAMPFIRE_COOK_RANGE, { inside: 'tg-cloister', phase: 2 }),
|
|
693
|
+
station('fire-camp', 'campfire', 24, 152, CAMPFIRE_COOK_RANGE, { phase: 3 }),
|
|
694
|
+
// Smithing: the Main Forge pair under its hearth arches, either side of the mouth column u 68-72.
|
|
695
|
+
station('furnace-mf-w', 'furnace', 65, 76, BLACKSMITH_RANGE, { inside: 'fg-mainforge' }),
|
|
696
|
+
station('furnace-mf-e', 'furnace', 75, 76, BLACKSMITH_RANGE, { inside: 'fg-mainforge' }),
|
|
697
|
+
station('anvil-mf-w', 'anvil', 66, 78, BLACKSMITH_RANGE, { inside: 'fg-mainforge' }),
|
|
698
|
+
station('anvil-mf-e', 'anvil', 74, 78, BLACKSMITH_RANGE, { inside: 'fg-mainforge' }),
|
|
699
|
+
station('furnace-smelter', 'furnace', 88, 79, BLACKSMITH_RANGE),
|
|
700
|
+
// The Blacksmith's and the Armourer's flank their doors on Anvil Square, off the door rows.
|
|
701
|
+
station('furnace-smith', 'furnace', 60, 93, BLACKSMITH_RANGE),
|
|
702
|
+
station('anvil-smith', 'anvil', 60, 101, BLACKSMITH_RANGE),
|
|
703
|
+
station('furnace-armour', 'furnace', 88, 93, BLACKSMITH_RANGE),
|
|
704
|
+
station('anvil-armour', 'anvil', 88, 101, BLACKSMITH_RANGE),
|
|
705
|
+
station('anvil-shed', 'anvil', 64, 104, BLACKSMITH_RANGE, { inside: 'fg-anvilshed' }),
|
|
706
|
+
station('mound-1', 'charcoalmound', 68, 111, CHARCOAL_MOUND_RANGE),
|
|
707
|
+
station('mound-2', 'charcoalmound', 74, 111, CHARCOAL_MOUND_RANGE),
|
|
708
|
+
station('mound-3', 'charcoalmound', 80, 111, CHARCOAL_MOUND_RANGE),
|
|
709
|
+
// Twelve training dummies in two rows across the Training Yard.
|
|
710
|
+
...[0, 1, 2, 3, 4, 5].flatMap((i) => [
|
|
711
|
+
station(`dummy-a${i}`, 'yardprop', 112 + i * 5, 129, TRAINING_DUMMY_RANGE, { style: 'dummy' }),
|
|
712
|
+
station(`dummy-b${i}`, 'yardprop', 112 + i * 5, 136, TRAINING_DUMMY_RANGE, { style: 'dummy' }),
|
|
713
|
+
]),
|
|
714
|
+
// Prayer: the Temple Altar at the back of the nave, the Healing Shrine.
|
|
715
|
+
station('shrine-temple', 'shrine', 192, 12, TEMPLE_PRAYER_RANGE, { inside: 'tg-temple' }),
|
|
716
|
+
station('shrine-healing', 'shrine', 227, 52, TEMPLE_PRAYER_RANGE, { inside: 'tg-shrine' }),
|
|
717
|
+
// Notice boards (the bounty board kind; its reach is BANK_RANGE in world.ts).
|
|
718
|
+
station('board-gate', 'bountyboard', 20, 80, BANK_RANGE),
|
|
719
|
+
station('board-coin', 'bountyboard', 128, 34, BANK_RANGE),
|
|
720
|
+
station('board-ranger', 'bountyboard', 35, 104, BANK_RANGE, { inside: 'wg-ranger' }),
|
|
721
|
+
station('board-harbour', 'bountyboard', 268, 79, BANK_RANGE),
|
|
722
|
+
// Work benches: face90 runs the bench along z, against the Ranger Hall's east wall.
|
|
723
|
+
station('bench-ranger', 'workbench', 41, 104, PICKUP, { inside: 'wg-ranger', style: 'face90' }),
|
|
724
|
+
station('bench-artisan', 'workbench', 62, 129, PICKUP, { inside: 'fg-artisan', style: 'face0', phase: 2 }),
|
|
167
725
|
];
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
{
|
|
173
|
-
{
|
|
174
|
-
{
|
|
726
|
+
function stall(id, cluster, goods, u, v, facing) {
|
|
727
|
+
return { id, cluster, goods, facing, style: `${goods}-${facing}`, phase: 1, ...capTile(u, v) };
|
|
728
|
+
}
|
|
729
|
+
const STALL_CLUSTERS = [
|
|
730
|
+
{ cluster: 'produce', goods: 'produce', u: 53, v: 25 },
|
|
731
|
+
{ cluster: 'fish', goods: 'fish', u: 81, v: 25 },
|
|
732
|
+
{ cluster: 'cloth', goods: 'cloth', u: 53, v: 45 },
|
|
733
|
+
{ cluster: 'trader', goods: 'pots', u: 81, v: 45 },
|
|
734
|
+
];
|
|
735
|
+
export const CAPITAL_STALLS = [
|
|
736
|
+
...STALL_CLUSTERS.flatMap((c, i) => [
|
|
737
|
+
stall(`stall${i}a`, c.cluster, c.goods, c.u - 3, c.v, 'w'),
|
|
738
|
+
stall(`stall${i}b`, c.cluster, c.goods, c.u + 3, c.v, 'e'),
|
|
739
|
+
stall(`stall${i}c`, c.cluster, c.goods, c.u, c.v - 3, 'n'),
|
|
740
|
+
]),
|
|
741
|
+
stall('stall-tova', 'food', 'produce', 28, 52, 's'),
|
|
742
|
+
stall('stall-food1', 'food', 'produce', 34, 52, 's'),
|
|
743
|
+
stall('stall-food2', 'food', 'produce', 22, 52, 's'),
|
|
744
|
+
];
|
|
745
|
+
/** A fingerpost's signpost style: `fingerpost-<place>|<place>|...`. The frontend draws one slat per place. */
|
|
746
|
+
export function capitalFingerpostStyle(names) {
|
|
747
|
+
return `fingerpost-${names.join('|')}`;
|
|
748
|
+
}
|
|
749
|
+
/** The places on a `fingerpost-` style, or null for any other style. */
|
|
750
|
+
export function parseFingerpostStyle(style) {
|
|
751
|
+
if (!style || !style.startsWith('fingerpost-'))
|
|
752
|
+
return null;
|
|
753
|
+
return style.slice('fingerpost-'.length).split('|').filter((s) => s.length > 0);
|
|
754
|
+
}
|
|
755
|
+
function fingerpost(id, u, v, names) {
|
|
756
|
+
return { id, kind: 'signpost', style: capitalFingerpostStyle(names), names, ...capTile(u, v) };
|
|
757
|
+
}
|
|
758
|
+
export const CAPITAL_FIXTURES = [
|
|
759
|
+
fingerpost('fingerpost-gate', 40, 80, ['Market Square', 'The Royal Way', 'The Keep', 'Forge Quarter', "King's Harbour", 'The Dreadwoods']),
|
|
760
|
+
fingerpost('fingerpost-cross', 146, 70, ['The Keep', 'Temple of Light', 'Market Square', 'Westgate', "King's Harbour", 'Noble Quarter', 'The Dreadwoods']),
|
|
761
|
+
fingerpost('fingerpost-royal', 230, 72, ['The Royal Way', 'Market Square', 'The Keep', 'Temple of Light']),
|
|
762
|
+
{ id: 'brazier-west-n', kind: 'yardprop', style: 'brazier', ...capTile(7, AVENUE_V - 5) },
|
|
763
|
+
{ id: 'brazier-west-s', kind: 'yardprop', style: 'brazier', ...capTile(7, AVENUE_V + 5) },
|
|
764
|
+
{ id: 'brazier-east-n', kind: 'yardprop', style: 'brazier', ...capTile(IN_U1 - 3, AVENUE_V - 6) },
|
|
765
|
+
{ id: 'brazier-east-s', kind: 'yardprop', style: 'brazier', ...capTile(IN_U1 - 3, AVENUE_V + 6) },
|
|
766
|
+
];
|
|
767
|
+
function npcSpot(id, name, role, u, v) {
|
|
768
|
+
return { id, name, role, ...capTile(u, v) };
|
|
769
|
+
}
|
|
770
|
+
export const CAPITAL_NPC_SPOTS = [
|
|
771
|
+
npcSpot('outpost-teller', 'Outpost Teller', 'banker', 16, 71),
|
|
772
|
+
npcSpot('adventurer-innkeeper', 'Innkeeper', 'merchant:inn', 32, 69),
|
|
773
|
+
npcSpot('gate-captain', 'Gate Captain', 'resident', 10, 104),
|
|
774
|
+
npcSpot('quartermaster', 'Quartermaster', 'merchant:supplies', 24, 104),
|
|
775
|
+
npcSpot('ranger', 'Ranger', 'merchant:ranger', 38, 110),
|
|
776
|
+
npcSpot('clothier', 'Clothier', 'merchant:clothier', 28, 33),
|
|
777
|
+
npcSpot('storekeeper', 'Storekeeper', 'merchant:general', 28, 45),
|
|
778
|
+
npcSpot('cook', 'Cook', 'merchant:cook', 10, 56),
|
|
779
|
+
npcSpot('tova', 'Tova', 'tova', 28, 51),
|
|
780
|
+
npcSpot('produce-keeper', 'Produce seller', 'merchant:produce', 53, 25),
|
|
781
|
+
npcSpot('fish-keeper', 'Fish seller', 'merchant:fish', 81, 25),
|
|
782
|
+
npcSpot('cloth-keeper', 'Cloth seller', 'merchant:cloth', 53, 45),
|
|
783
|
+
npcSpot('pedlar', 'Pedlar', 'merchant:pedlar', 81, 45),
|
|
784
|
+
npcSpot('exchange-clerk', 'Exchange Clerk', 'merchant:exchange', 118, 17),
|
|
785
|
+
npcSpot('capital-banker', 'Capital Banker', 'banker', 120, 49),
|
|
786
|
+
npcSpot('guild-clerk', 'Guild Clerk', 'resident', 172, 44),
|
|
787
|
+
npcSpot('forge-master', 'Forge Master', 'merchant:smith', 52, 97),
|
|
788
|
+
npcSpot('armourer', 'Armourer', 'merchant:armourer', 96, 97),
|
|
789
|
+
npcSpot('alchemist', 'Alchemist', 'merchant:alchemist', 86, 130),
|
|
790
|
+
npcSpot('artisan', 'Artisan', 'resident', 64, 130),
|
|
791
|
+
npcSpot('sergeant', 'Sergeant', 'resident', 126, 94),
|
|
792
|
+
npcSpot('drill-sergeant', 'Drill Sergeant', 'resident', 130, 110),
|
|
793
|
+
npcSpot('priest', 'Priest', 'resident', 194, 14),
|
|
794
|
+
npcSpot('brother-almoner', 'Brother Almoner', 'resident', 252, 14),
|
|
795
|
+
npcSpot('king', 'The King', 'resident:still', 200, AVENUE_V),
|
|
796
|
+
npcSpot('rimdel', 'Rimdel', 'rimdel', 197, AVENUE_V - 3),
|
|
797
|
+
npcSpot('royal-clerk', 'Royal Clerk', 'resident', 262, 67),
|
|
798
|
+
npcSpot('townsman-w', 'Retired captain', 'resident', 236, 95),
|
|
799
|
+
npcSpot('townswoman-e', "Clerk's widow", 'resident', 248, 95),
|
|
800
|
+
npcSpot('noble-blue', 'Blue Manor noble', 'resident', 182, 116),
|
|
801
|
+
npcSpot('noble-green', 'Green Manor noble', 'resident', 234, 116),
|
|
802
|
+
npcSpot('noble-red', 'Red Manor noble', 'resident', 182, 154),
|
|
803
|
+
npcSpot('noble-purple', 'Purple Manor noble', 'resident', 234, 154),
|
|
175
804
|
];
|
|
805
|
+
/**
|
|
806
|
+
* The Capital's guards (2026-09-21, redesigned 2026-09-25): level 40 in
|
|
807
|
+
* steel, in pairs, 14 of them. Inside the Great Gate, on the Audience Court
|
|
808
|
+
* either side of the keep's door, just inside the throne hall's door, inside
|
|
809
|
+
* the East Gate, at the harbour end of its bridge, and in the courts of the
|
|
810
|
+
* two barred gates. `faceX`/`faceZ` is what each pair watches. The hall pair
|
|
811
|
+
* is `still`: the hall's furniture is client-side and a pacing guard would
|
|
812
|
+
* walk through the feast tables. The first post is the admin links' Great
|
|
813
|
+
* Gate guard; keep it first.
|
|
814
|
+
*/
|
|
815
|
+
export const CAPITAL_GUARD_POSTS = [
|
|
816
|
+
{ id: 'west-gate-n', x: CAPITAL_INNER_MIN_X + 3, z: CAPITAL_AVENUE_Z - 4, faceX: CAPITAL_MIN_X, faceZ: CAPITAL_AVENUE_Z },
|
|
817
|
+
{ id: 'west-gate-s', x: CAPITAL_INNER_MIN_X + 3, z: CAPITAL_AVENUE_Z + 4, faceX: CAPITAL_MIN_X, faceZ: CAPITAL_AVENUE_Z },
|
|
818
|
+
{ id: 'keep-court-n', x: CAPITAL_KEEP.x - CASTLE_KEEP_HALF_X - 1, z: CAPITAL_AVENUE_Z - 3, faceX: CAPITAL_KING_STATUE.x, faceZ: CAPITAL_AVENUE_Z },
|
|
819
|
+
{ id: 'keep-court-s', x: CAPITAL_KEEP.x - CASTLE_KEEP_HALF_X - 1, z: CAPITAL_AVENUE_Z + 3, faceX: CAPITAL_KING_STATUE.x, faceZ: CAPITAL_AVENUE_Z },
|
|
820
|
+
// One tile in from the hall's west wall (CASTLE_HALL_MIN_DX in constants.ts is -3).
|
|
821
|
+
{ id: 'throne-hall-n', x: CAPITAL_KEEP.x - 2, z: CAPITAL_AVENUE_Z - 3, faceX: CAPITAL_KEEP.x - CASTLE_KEEP_HALF_X, faceZ: CAPITAL_AVENUE_Z, still: true },
|
|
822
|
+
{ id: 'throne-hall-s', x: CAPITAL_KEEP.x - 2, z: CAPITAL_AVENUE_Z + 3, faceX: CAPITAL_KEEP.x - CASTLE_KEEP_HALF_X, faceZ: CAPITAL_AVENUE_Z, still: true },
|
|
823
|
+
{ id: 'east-gate-n', x: CAPITAL_INNER_MAX_X - 1, z: CAPITAL_AVENUE_Z - 4, faceX: CAPITAL_MAX_X, faceZ: CAPITAL_AVENUE_Z },
|
|
824
|
+
{ id: 'east-gate-s', x: CAPITAL_INNER_MAX_X - 1, z: CAPITAL_AVENUE_Z + 4, faceX: CAPITAL_MAX_X, faceZ: CAPITAL_AVENUE_Z },
|
|
825
|
+
{ id: 'harbour-bridge-n', x: CAPITAL_MOAT_MAX_X + 3, z: CAPITAL_AVENUE_Z - 5, faceX: CAPITAL_MAX_X, faceZ: CAPITAL_AVENUE_Z },
|
|
826
|
+
{ id: 'harbour-bridge-s', x: CAPITAL_MOAT_MAX_X + 3, z: CAPITAL_AVENUE_Z + 5, faceX: CAPITAL_MAX_X, faceZ: CAPITAL_AVENUE_Z },
|
|
827
|
+
{ id: 'north-gate-w', x: CAPITAL_ROYAL_WAY_X - 4, z: CAPITAL_NORTH_Z + 7, faceX: CAPITAL_NORTH_GATE.x, faceZ: CAPITAL_NORTH_GATE.z },
|
|
828
|
+
{ id: 'north-gate-e', x: CAPITAL_ROYAL_WAY_X + 4, z: CAPITAL_NORTH_Z + 7, faceX: CAPITAL_NORTH_GATE.x, faceZ: CAPITAL_NORTH_GATE.z },
|
|
829
|
+
{ id: 'south-gate-w', x: CAPITAL_ROYAL_WAY_X - 4, z: CAPITAL_SOUTH_Z - 8, faceX: CAPITAL_SOUTH_GATE.x, faceZ: CAPITAL_SOUTH_GATE.z },
|
|
830
|
+
{ id: 'south-gate-e', x: CAPITAL_ROYAL_WAY_X + 4, z: CAPITAL_SOUTH_Z - 8, faceX: CAPITAL_SOUTH_GATE.x, faceZ: CAPITAL_SOUTH_GATE.z },
|
|
831
|
+
];
|
|
832
|
+
const STRONGBOX_FRONT = capTile(74, 91);
|
|
833
|
+
const CROWN_CROSS = capTile(WAY_U, 80);
|
|
834
|
+
const WEST_MOUTH = capTile(CAPITAL_WEST_MOUTH.u, CAPITAL_WEST_MOUTH.v);
|
|
835
|
+
const EAST_MOUTH = capTile(CAPITAL_EAST_MOUTH.u, CAPITAL_EAST_MOUTH.v);
|
|
836
|
+
function budget(id, from, to, max, planned) {
|
|
837
|
+
return { id, from, to, max, planned };
|
|
838
|
+
}
|
|
839
|
+
export const CAPITAL_WALK_BUDGETS = [
|
|
840
|
+
// The land arrival: everything a traveller needs within 40 tiles of the gate mouth.
|
|
841
|
+
budget('west-gate-to-outpost-chest', WEST_MOUTH, 'chest-outpost', 20, 16),
|
|
842
|
+
budget('west-gate-to-notice-board', WEST_MOUTH, 'board-gate', 20, 15),
|
|
843
|
+
budget('west-gate-to-supply-depot', WEST_MOUTH, 'wg-depot', 25, 22),
|
|
844
|
+
budget('west-gate-to-inn-stove', WEST_MOUTH, 'stove-inn', 30, 24),
|
|
845
|
+
budget('west-gate-to-inn', WEST_MOUTH, 'wg-inn', 35, 31),
|
|
846
|
+
budget('west-gate-to-ranger-hall', WEST_MOUTH, 'wg-ranger', 40, 37),
|
|
847
|
+
budget('west-gate-to-crown-cross', WEST_MOUTH, CROWN_CROSS, 152, 150),
|
|
848
|
+
// The forge loop: every furnace and anvil within 15 of the Smiths' Strongbox, every mound within 20.
|
|
849
|
+
budget('strongbox-to-furnace-mf-w', STRONGBOX_FRONT, 'furnace-mf-w', 15, 14),
|
|
850
|
+
budget('strongbox-to-furnace-mf-e', STRONGBOX_FRONT, 'furnace-mf-e', 15, 14),
|
|
851
|
+
budget('strongbox-to-anvil-mf-w', STRONGBOX_FRONT, 'anvil-mf-w', 15, 12),
|
|
852
|
+
budget('strongbox-to-anvil-mf-e', STRONGBOX_FRONT, 'anvil-mf-e', 15, 12),
|
|
853
|
+
budget('strongbox-to-furnace-smelter', STRONGBOX_FRONT, 'furnace-smelter', 15, 12),
|
|
854
|
+
budget('strongbox-to-furnace-smith', STRONGBOX_FRONT, 'furnace-smith', 15, 12),
|
|
855
|
+
budget('strongbox-to-anvil-smith', STRONGBOX_FRONT, 'anvil-smith', 15, 12),
|
|
856
|
+
budget('strongbox-to-furnace-armour', STRONGBOX_FRONT, 'furnace-armour', 15, 12),
|
|
857
|
+
budget('strongbox-to-anvil-armour', STRONGBOX_FRONT, 'anvil-armour', 15, 12),
|
|
858
|
+
budget('strongbox-to-anvil-shed', STRONGBOX_FRONT, 'anvil-shed', 15, 11),
|
|
859
|
+
budget('strongbox-to-mound-1', STRONGBOX_FRONT, 'mound-1', 20, 18),
|
|
860
|
+
budget('strongbox-to-mound-2', STRONGBOX_FRONT, 'mound-2', 20, 18),
|
|
861
|
+
budget('strongbox-to-mound-3', STRONGBOX_FRONT, 'mound-3', 20, 18),
|
|
862
|
+
// The sea arrival: straight to the King's Fountain, no detour (Chebyshev 124).
|
|
863
|
+
budget('east-gate-to-harbour-board', EAST_MOUTH, 'board-harbour', 10, 7),
|
|
864
|
+
budget('east-gate-to-royal-administration', EAST_MOUTH, 'kp-admin', 20, 17),
|
|
865
|
+
budget('east-gate-to-kings-fountain', EAST_MOUTH, { ...capTile(capU(CAPITAL_KINGS_FOUNTAIN.x), capV(CAPITAL_KINGS_FOUNTAIN.z)), range: 3 }, 130, 123),
|
|
866
|
+
// From the Crown Cross every district is one screen away.
|
|
867
|
+
budget('crown-cross-to-muster-campfire', CROWN_CROSS, 'fire-muster', 30, 27),
|
|
868
|
+
budget('crown-cross-to-barracks', CROWN_CROSS, 'ml-barracks', 35, 31),
|
|
869
|
+
budget('crown-cross-to-keep', CROWN_CROSS, 'kp-keep', 36, 32),
|
|
870
|
+
budget('crown-cross-to-guild-hall', CROWN_CROSS, 'rw-guild', 40, 37),
|
|
871
|
+
budget('crown-cross-to-capital-bank', CROWN_CROSS, 'tr-bank', 52, 47),
|
|
872
|
+
budget('crown-cross-to-nearest-dummy', CROWN_CROSS, 'dummy-a5', 58, 53),
|
|
873
|
+
budget('crown-cross-to-farthest-dummy', CROWN_CROSS, 'dummy-b0', 58, 57),
|
|
874
|
+
budget('crown-cross-to-grand-exchange', CROWN_CROSS, 'tr-ge', 60, 54),
|
|
875
|
+
budget('crown-cross-to-temple', CROWN_CROSS, 'tg-temple', 62, 57),
|
|
876
|
+
budget('crown-cross-to-healing-shrine', CROWN_CROSS, 'shrine-healing', 78, 72),
|
|
877
|
+
];
|
|
878
|
+
/* ------------------------------------------------------------------ *
|
|
879
|
+
* Predicates.
|
|
880
|
+
* ------------------------------------------------------------------ */
|
|
176
881
|
export function inCapitalWalls(x, z) {
|
|
177
882
|
return x >= CAPITAL_MIN_X && x <= CAPITAL_MAX_X && z >= CAPITAL_NORTH_Z && z <= CAPITAL_SOUTH_Z;
|
|
178
883
|
}
|
|
@@ -185,14 +890,6 @@ export function onCapitalWall(x, z) {
|
|
|
185
890
|
export function inCapitalInterior(x, z) {
|
|
186
891
|
return inCapitalWalls(x, z) && !onCapitalWall(x, z);
|
|
187
892
|
}
|
|
188
|
-
/** The passage tiles through each gate, the wall's own rows only. */
|
|
189
|
-
export function inCapitalGate(x, z) {
|
|
190
|
-
if (!onCapitalWall(x, z))
|
|
191
|
-
return false;
|
|
192
|
-
if (x < CAPITAL_INNER_MIN_X || x > CAPITAL_INNER_MAX_X)
|
|
193
|
-
return Math.abs(z - CAPITAL_AVENUE_Z) <= CAPITAL_GATE_HALF_W;
|
|
194
|
-
return Math.abs(x - CAPITAL_NORTH_GATE.x) <= CAPITAL_GATE_HALF_W;
|
|
195
|
-
}
|
|
196
893
|
export function onCapitalBridge(x, z) {
|
|
197
894
|
return x >= CAPITAL_BRIDGE_MIN_X && x <= CAPITAL_BRIDGE_MAX_X && Math.abs(z - CAPITAL_AVENUE_Z) <= CAPITAL_BRIDGE_HALF_W;
|
|
198
895
|
}
|
|
@@ -209,7 +906,7 @@ function shoreHash(x, z) {
|
|
|
209
906
|
* is a rounded basin (an ellipse from the gate) rather than a box.
|
|
210
907
|
*/
|
|
211
908
|
export function inCapitalMoat(x, z) {
|
|
212
|
-
if (inCapitalWalls(x, z) || onCapitalBridge(x, z))
|
|
909
|
+
if (inCapitalWalls(x, z) || onCapitalBridge(x, z) || onCapitalSouthBridge(x, z))
|
|
213
910
|
return false;
|
|
214
911
|
const dx = Math.max(CAPITAL_MIN_X - x, 0, x - CAPITAL_MAX_X);
|
|
215
912
|
const dz = Math.max(CAPITAL_NORTH_Z - z, 0, z - CAPITAL_SOUTH_Z);
|
|
@@ -222,7 +919,8 @@ export function inCapitalMoat(x, z) {
|
|
|
222
919
|
if (d <= CAPITAL_MOAT_W - 2)
|
|
223
920
|
return true;
|
|
224
921
|
// Wobble: a smooth-ish threshold from two neighbouring hashes.
|
|
225
|
-
const
|
|
922
|
+
const hz = legacyZ(x, z);
|
|
923
|
+
const w = (shoreHash(Math.floor(x / 3), Math.floor(hz / 3)) + shoreHash(Math.floor(x / 5) + 7, Math.floor(hz / 5) + 3)) / 2;
|
|
226
924
|
const reach = CAPITAL_MOAT_W - 2 + Math.round(w * 4);
|
|
227
925
|
if (d <= reach)
|
|
228
926
|
return true;
|
|
@@ -231,7 +929,7 @@ export function inCapitalMoat(x, z) {
|
|
|
231
929
|
const ex = (x - (CAPITAL_MIN_X - 1)) / (CAPITAL_MIN_X - 1 - CAPITAL_POOL.minX + 1);
|
|
232
930
|
const ez = (z - CAPITAL_AVENUE_Z) / ((CAPITAL_POOL.maxZ - CAPITAL_POOL.minZ) / 2 + 2);
|
|
233
931
|
if (x < CAPITAL_MIN_X && ex * ex + ez * ez <= 1) {
|
|
234
|
-
const w = shoreHash(Math.floor(x / 2), Math.floor(z / 2));
|
|
932
|
+
const w = shoreHash(Math.floor(x / 2), Math.floor(legacyZ(x, z) / 2));
|
|
235
933
|
return ex * ex + ez * ez <= 0.82 + w * 0.18;
|
|
236
934
|
}
|
|
237
935
|
return false;
|
|
@@ -246,61 +944,134 @@ export function capitalMoundHeight(x, z) {
|
|
|
246
944
|
const d = Math.max(dx, dz);
|
|
247
945
|
return Math.max(CAPITAL_FLOOR_H, CAPITAL_MOUND_H - d);
|
|
248
946
|
}
|
|
249
|
-
|
|
250
|
-
return
|
|
947
|
+
function inWorldRect(r, x, z) {
|
|
948
|
+
return x >= r.minX && x <= r.maxX && z >= r.minZ && z <= r.maxZ;
|
|
949
|
+
}
|
|
950
|
+
/** The last row containing the tile (later rows win, as they paint). */
|
|
951
|
+
function lastAt(rows, x, z, keep) {
|
|
952
|
+
for (let i = rows.length - 1; i >= 0; i--) {
|
|
953
|
+
const r = rows[i];
|
|
954
|
+
if (inWorldRect(r, x, z) && (!keep || keep(r)))
|
|
955
|
+
return r;
|
|
956
|
+
}
|
|
957
|
+
return undefined;
|
|
958
|
+
}
|
|
959
|
+
/**
|
|
960
|
+
* The route on a tile, in paint order: the Royal Way over the primaries over
|
|
961
|
+
* the lanes and walks. Interior tiles only.
|
|
962
|
+
*/
|
|
963
|
+
export function capitalRouteAt(x, z) {
|
|
964
|
+
if (!inCapitalInterior(x, z))
|
|
965
|
+
return undefined;
|
|
966
|
+
return (lastAt(CAPITAL_ROUTES, x, z, (r) => r.kind === 'way') ??
|
|
967
|
+
lastAt(CAPITAL_ROUTES, x, z, (r) => r.kind === 'primary') ??
|
|
968
|
+
lastAt(CAPITAL_ROUTES, x, z, (r) => r.kind === 'lane' || r.kind === 'walk'));
|
|
969
|
+
}
|
|
970
|
+
export function capitalPlazaAt(x, z) {
|
|
971
|
+
return inCapitalInterior(x, z) ? lastAt(CAPITAL_PLAZAS, x, z) : undefined;
|
|
251
972
|
}
|
|
973
|
+
export function capitalGreenAt(x, z) {
|
|
974
|
+
return inCapitalInterior(x, z) ? lastAt(CAPITAL_GREENS, x, z) : undefined;
|
|
975
|
+
}
|
|
976
|
+
export function capitalYardAt(x, z) {
|
|
977
|
+
return inCapitalInterior(x, z) ? lastAt(CAPITAL_YARDS, x, z) : undefined;
|
|
978
|
+
}
|
|
979
|
+
/** A kerb row: the outer rows of a kerbed route, across its run (the Way's are its outer columns). */
|
|
980
|
+
function onKerb(r, x, z) {
|
|
981
|
+
if (!r.kerb)
|
|
982
|
+
return false;
|
|
983
|
+
const alongX = r.maxX - r.minX >= r.maxZ - r.minZ;
|
|
984
|
+
return alongX ? z === r.minZ || z === r.maxZ : x === r.minX || x === r.maxX;
|
|
985
|
+
}
|
|
986
|
+
/**
|
|
987
|
+
* What the plan lays on an interior tile, in the builder's paint order
|
|
988
|
+
* (spec G.2): the Royal Way, then the primaries, the lanes and walks, the
|
|
989
|
+
* plazas, the mound (plateau Stone, banks Grass), the greens and yards, and
|
|
990
|
+
* lawn under everything. Kerb rows of the kerbed routes are Path. Buildings
|
|
991
|
+
* floor their own footprints on top (buildingFloorSurface); the gate
|
|
992
|
+
* passages and the keep are the builder's. What is not built in this phase
|
|
993
|
+
* is not laid: an apron whose building does not stand yet, a yard of a later
|
|
994
|
+
* phase (the rects stay in the tables, reserved).
|
|
995
|
+
*/
|
|
996
|
+
export function capitalGroundAt(x, z) {
|
|
997
|
+
if (!inCapitalInterior(x, z))
|
|
998
|
+
return undefined;
|
|
999
|
+
const route = capitalRouteAt(x, z);
|
|
1000
|
+
if (route)
|
|
1001
|
+
return { layer: route.kind, id: route.id, surface: onKerb(route, x, z) ? SurfaceType.Path : capitalSurface(route.surface) };
|
|
1002
|
+
// An apron whose building is not standing yet is not laid (the row keeps
|
|
1003
|
+
// its rect reserved, and capitalPlazaAt still answers for it); the next
|
|
1004
|
+
// plaza under it, or the ground, shows.
|
|
1005
|
+
const plaza = lastAt(CAPITAL_PLAZAS, x, z, apronLaidNow);
|
|
1006
|
+
if (plaza) {
|
|
1007
|
+
// A green lying wholly inside a plaza is an island in it (the Royal
|
|
1008
|
+
// Square's hedged lawn, the Drill Square's tree islands), so it stays
|
|
1009
|
+
// grass rather than being paved over.
|
|
1010
|
+
const island = capitalGreenAt(x, z);
|
|
1011
|
+
if (island && island.u0 >= plaza.u0 && island.u1 <= plaza.u1 && island.v0 >= plaza.v0 && island.v1 <= plaza.v1) {
|
|
1012
|
+
return { layer: 'green', id: island.id, surface: SurfaceType.Grass };
|
|
1013
|
+
}
|
|
1014
|
+
return { layer: 'plaza', id: plaza.id, surface: capitalSurface(plaza.surface) };
|
|
1015
|
+
}
|
|
1016
|
+
if (capitalMoundHeight(x, z) > CAPITAL_FLOOR_H) {
|
|
1017
|
+
return inCapitalPlateau(x, z) ? { layer: 'plateau', surface: SurfaceType.Stone } : { layer: 'mound', surface: SurfaceType.Grass };
|
|
1018
|
+
}
|
|
1019
|
+
const green = capitalGreenAt(x, z);
|
|
1020
|
+
if (green)
|
|
1021
|
+
return { layer: 'green', id: green.id, surface: SurfaceType.Grass };
|
|
1022
|
+
// A yard of a later phase is not laid yet: lawn until then.
|
|
1023
|
+
const yard = lastAt(CAPITAL_YARDS, x, z, (y) => (y.phase ?? 1) <= CAPITAL_BUILD_PHASE);
|
|
1024
|
+
if (yard)
|
|
1025
|
+
return { layer: 'yard', id: yard.id, surface: capitalSurface(yard.surface) };
|
|
1026
|
+
return { layer: 'lawn', surface: SurfaceType.Grass };
|
|
1027
|
+
}
|
|
1028
|
+
/** The landmarks standing in this phase (as themselves or a stand-in), for the aprons. */
|
|
1029
|
+
let standingLandmarks = null;
|
|
1030
|
+
/** Whether a plaza row is laid now: every plaza but an apron for a building not standing yet. */
|
|
1031
|
+
function apronLaidNow(p) {
|
|
1032
|
+
if (!p.forLandmark)
|
|
1033
|
+
return true;
|
|
1034
|
+
if (!standingLandmarks)
|
|
1035
|
+
standingLandmarks = new Set(CAPITAL_LANDMARKS.filter((row) => capitalPlacementNow(row) !== null).map((row) => row.id));
|
|
1036
|
+
return standingLandmarks.has(p.forLandmark);
|
|
1037
|
+
}
|
|
1038
|
+
/** The ring lane inside the wall. */
|
|
252
1039
|
export function onCapitalRingRoad(x, z) {
|
|
253
1040
|
if (!inCapitalInterior(x, z))
|
|
254
1041
|
return false;
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
return (
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
1042
|
+
return CAPITAL_ROUTES.some((r) => r.id.startsWith('ring-') && inWorldRect(r, x, z));
|
|
1043
|
+
}
|
|
1044
|
+
/** The Royal Way's paving (u 149-155, the North Gate to the South Gate). */
|
|
1045
|
+
export function onCapitalWay(x, z) {
|
|
1046
|
+
return capitalRouteAt(x, z)?.kind === 'way';
|
|
1047
|
+
}
|
|
1048
|
+
/**
|
|
1049
|
+
* The keep axis: the King's Road's rows from the Great Gate to the Inner
|
|
1050
|
+
* Gate on the plateau's edge (it crosses the Way at the Crown Cross and
|
|
1051
|
+
* climbs the ramp).
|
|
1052
|
+
*/
|
|
263
1053
|
export function onCapitalAvenue(x, z) {
|
|
264
1054
|
if (!inCapitalInterior(x, z))
|
|
265
1055
|
return false;
|
|
266
1056
|
return Math.abs(z - CAPITAL_AVENUE_Z) <= CAPITAL_AVENUE_HALF_W && x <= CAPITAL_INNER_GATE.x;
|
|
267
1057
|
}
|
|
268
|
-
/** A
|
|
269
|
-
/** A primary route tile (not the Way): the wide streets to the market, garrison and temple. */
|
|
1058
|
+
/** A primary route tile (Crown Street, the King's Road and its ramp, the Harbour Road); not the Way. */
|
|
270
1059
|
export function onCapitalPrimary(x, z) {
|
|
271
|
-
if (!inCapitalInterior(x, z)
|
|
1060
|
+
if (!inCapitalInterior(x, z))
|
|
272
1061
|
return false;
|
|
273
|
-
|
|
274
|
-
if (r.x !== undefined && Math.abs(x - r.x) <= CAPITAL_PRIMARY_HALF_W && z >= r.min && z <= r.max)
|
|
275
|
-
return true;
|
|
276
|
-
if (r.z !== undefined && Math.abs(z - r.z) <= CAPITAL_PRIMARY_HALF_W && x >= r.min && x <= r.max)
|
|
277
|
-
return true;
|
|
278
|
-
}
|
|
279
|
-
return false;
|
|
1062
|
+
return CAPITAL_ROUTES.some((r) => r.kind === 'primary' && inWorldRect(r, x, z));
|
|
280
1063
|
}
|
|
1064
|
+
/** A street tile: any route in the table (the Way, the primaries, the lanes and walks, the ring lane). */
|
|
281
1065
|
export function onCapitalStreet(x, z) {
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
return false;
|
|
288
|
-
for (const sx of CAPITAL_CROSS_STREET_XS)
|
|
289
|
-
if (Math.abs(x - sx) <= CAPITAL_STREET_HALF_W)
|
|
290
|
-
return true;
|
|
291
|
-
for (const st of CAPITAL_DISTRICT_STREETS) {
|
|
292
|
-
if (x >= st.minX && x <= st.maxX && Math.abs(z - st.z) <= CAPITAL_STREET_HALF_W)
|
|
293
|
-
return true;
|
|
294
|
-
}
|
|
295
|
-
return false;
|
|
1066
|
+
return capitalRouteAt(x, z) !== undefined;
|
|
1067
|
+
}
|
|
1068
|
+
/** A plaza, square, court or door apron tile. */
|
|
1069
|
+
export function onCapitalPlaza(x, z) {
|
|
1070
|
+
return capitalPlazaAt(x, z) !== undefined;
|
|
296
1071
|
}
|
|
297
1072
|
export function capitalDistrictAt(x, z) {
|
|
298
1073
|
if (!inCapitalInterior(x, z))
|
|
299
1074
|
return undefined;
|
|
300
|
-
|
|
301
|
-
const castle = CAPITAL_DISTRICTS.find((d) => d.id === 'capital-castle');
|
|
302
|
-
if (x >= castle.minX && x <= castle.maxX && z >= castle.minZ && z <= castle.maxZ)
|
|
303
|
-
return castle;
|
|
304
|
-
return CAPITAL_DISTRICTS.find((d) => x >= d.minX && x <= d.maxX && z >= d.minZ && z <= d.maxZ);
|
|
1075
|
+
return CAPITAL_DISTRICTS.find((d) => inWorldRect(d, x, z));
|
|
305
1076
|
}
|
|
306
1077
|
//# sourceMappingURL=capital.js.map
|