@dreadwood-types/types 0.1.0
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/README.md +43 -0
- package/dist/appearance.d.ts +80 -0
- package/dist/appearance.d.ts.map +1 -0
- package/dist/appearance.js +319 -0
- package/dist/appearance.js.map +1 -0
- package/dist/bank.d.ts +15 -0
- package/dist/bank.d.ts.map +1 -0
- package/dist/bank.js +45 -0
- package/dist/bank.js.map +1 -0
- package/dist/clientConfig.d.ts +103 -0
- package/dist/clientConfig.d.ts.map +1 -0
- package/dist/clientConfig.js +209 -0
- package/dist/clientConfig.js.map +1 -0
- package/dist/constants.d.ts +205 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +713 -0
- package/dist/constants.js.map +1 -0
- package/dist/cooking.d.ts +26 -0
- package/dist/cooking.d.ts.map +1 -0
- package/dist/cooking.js +41 -0
- package/dist/cooking.js.map +1 -0
- package/dist/equipment.d.ts +47 -0
- package/dist/equipment.d.ts.map +1 -0
- package/dist/equipment.js +186 -0
- package/dist/equipment.js.map +1 -0
- package/dist/goblinCave.d.ts +112 -0
- package/dist/goblinCave.d.ts.map +1 -0
- package/dist/goblinCave.js +69 -0
- package/dist/goblinCave.js.map +1 -0
- package/dist/groundItems.d.ts +17 -0
- package/dist/groundItems.d.ts.map +1 -0
- package/dist/groundItems.js +104 -0
- package/dist/groundItems.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/landmarks.d.ts +366 -0
- package/dist/landmarks.d.ts.map +1 -0
- package/dist/landmarks.js +584 -0
- package/dist/landmarks.js.map +1 -0
- package/dist/messages.d.ts +723 -0
- package/dist/messages.d.ts.map +1 -0
- package/dist/messages.js +38 -0
- package/dist/messages.js.map +1 -0
- package/dist/nameFilter.d.ts +8 -0
- package/dist/nameFilter.d.ts.map +1 -0
- package/dist/nameFilter.js +94 -0
- package/dist/nameFilter.js.map +1 -0
- package/dist/quests.d.ts +63 -0
- package/dist/quests.d.ts.map +1 -0
- package/dist/quests.js +233 -0
- package/dist/quests.js.map +1 -0
- package/dist/torches.d.ts +7 -0
- package/dist/torches.d.ts.map +1 -0
- package/dist/torches.js +11 -0
- package/dist/torches.js.map +1 -0
- package/dist/woodwork.d.ts +206 -0
- package/dist/woodwork.d.ts.map +1 -0
- package/dist/woodwork.js +365 -0
- package/dist/woodwork.js.map +1 -0
- package/dist/xp.d.ts +74 -0
- package/dist/xp.d.ts.map +1 -0
- package/dist/xp.js +126 -0
- package/dist/xp.js.map +1 -0
- package/dist/zones.d.ts +30 -0
- package/dist/zones.d.ts.map +1 -0
- package/dist/zones.js +143 -0
- package/dist/zones.js.map +1 -0
- package/package.json +36 -0
|
@@ -0,0 +1,713 @@
|
|
|
1
|
+
export const TILE_SIZE = 1;
|
|
2
|
+
export const CHAT_MAX_CHARS = 120;
|
|
3
|
+
export const CHAT_MAX_WORDS = 24;
|
|
4
|
+
export function clampChatText(text) {
|
|
5
|
+
const sliced = text.trim().slice(0, CHAT_MAX_CHARS);
|
|
6
|
+
if (!sliced)
|
|
7
|
+
return '';
|
|
8
|
+
const words = sliced.split(/\s+/).filter(Boolean);
|
|
9
|
+
if (words.length <= CHAT_MAX_WORDS)
|
|
10
|
+
return words.join(' ');
|
|
11
|
+
return words.slice(0, CHAT_MAX_WORDS).join(' ');
|
|
12
|
+
}
|
|
13
|
+
export const PLAYER_NAME_MIN = 2;
|
|
14
|
+
export const PLAYER_NAME_MAX = 24;
|
|
15
|
+
const PLAYER_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9 _'-]*$/;
|
|
16
|
+
export function sanitizePlayerName(raw) {
|
|
17
|
+
return raw.trim().replace(/\s+/g, ' ').slice(0, PLAYER_NAME_MAX);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Pet names go through the same rules as player names — the claim dialog
|
|
21
|
+
* already validates with `playerNameError`, so the server must apply the same
|
|
22
|
+
* pair or a hand-crafted packet could park arbitrary text over someone's head.
|
|
23
|
+
* Returns null for anything unusable rather than throwing, since this also runs
|
|
24
|
+
* over whatever the database happens to hold.
|
|
25
|
+
*/
|
|
26
|
+
export function normalizeBeagleName(raw) {
|
|
27
|
+
if (typeof raw !== 'string')
|
|
28
|
+
return null;
|
|
29
|
+
const name = sanitizePlayerName(raw);
|
|
30
|
+
return playerNameError(name) === null ? name : null;
|
|
31
|
+
}
|
|
32
|
+
export function playerNameError(name) {
|
|
33
|
+
if (name.length < PLAYER_NAME_MIN) {
|
|
34
|
+
return `Names must be at least ${PLAYER_NAME_MIN} characters.`;
|
|
35
|
+
}
|
|
36
|
+
if (name.length > PLAYER_NAME_MAX) {
|
|
37
|
+
return `Names must be at most ${PLAYER_NAME_MAX} characters.`;
|
|
38
|
+
}
|
|
39
|
+
if (!PLAYER_NAME_PATTERN.test(name)) {
|
|
40
|
+
return 'Names can only use letters, numbers, spaces, hyphens, underscores and apostrophes.';
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
export const CHUNK_SIZE = 32;
|
|
45
|
+
export const FARMLAND_CHUNKS_X = 8;
|
|
46
|
+
export const VILLAGE_CHUNKS_X = 2;
|
|
47
|
+
export const EASTLAND_CHUNKS_X = 14;
|
|
48
|
+
export const PLAYABLE_CHUNKS_X = FARMLAND_CHUNKS_X + VILLAGE_CHUNKS_X + EASTLAND_CHUNKS_X;
|
|
49
|
+
export const PLAYABLE_CHUNKS_Z = 3;
|
|
50
|
+
export const FARMLAND_WIDTH = CHUNK_SIZE * FARMLAND_CHUNKS_X;
|
|
51
|
+
export const WORLD_EAST_CHUNKS = 2;
|
|
52
|
+
export const WORLD_NORTH_CHUNKS = 23;
|
|
53
|
+
export const DEMO_CHUNKS_X = PLAYABLE_CHUNKS_X + WORLD_EAST_CHUNKS;
|
|
54
|
+
export const DEMO_CHUNKS_Z = PLAYABLE_CHUNKS_Z + WORLD_NORTH_CHUNKS;
|
|
55
|
+
export const PLAYABLE_WIDTH = CHUNK_SIZE * PLAYABLE_CHUNKS_X;
|
|
56
|
+
export const PLAYABLE_HEIGHT = CHUNK_SIZE * PLAYABLE_CHUNKS_Z;
|
|
57
|
+
export const WORLD_NORTH_PAD = CHUNK_SIZE * WORLD_NORTH_CHUNKS;
|
|
58
|
+
export const PLAYABLE_ORIGIN_X = 0;
|
|
59
|
+
export const PLAYABLE_ORIGIN_Z = WORLD_NORTH_PAD;
|
|
60
|
+
export const WORLD_WIDTH = CHUNK_SIZE * DEMO_CHUNKS_X;
|
|
61
|
+
export const WORLD_HEIGHT = CHUNK_SIZE * DEMO_CHUNKS_Z;
|
|
62
|
+
export const MAP_WIDTH = WORLD_WIDTH;
|
|
63
|
+
export const MAP_HEIGHT = WORLD_HEIGHT;
|
|
64
|
+
/**
|
|
65
|
+
* Extra columns east of the overworld for underground interiors (Rat Tunnel
|
|
66
|
+
* rooms, forest-den caverns). Generated and playable, but not on the paper
|
|
67
|
+
* map — the chart is WORLD_WIDTH × WORLD_HEIGHT.
|
|
68
|
+
*/
|
|
69
|
+
export const UNDERGROUND_CHUNKS_X = 8;
|
|
70
|
+
export const UNDERGROUND_ORIGIN_X = WORLD_WIDTH;
|
|
71
|
+
export const GRID_CHUNKS_X = DEMO_CHUNKS_X + UNDERGROUND_CHUNKS_X;
|
|
72
|
+
export const GRID_CHUNKS_Z = DEMO_CHUNKS_Z;
|
|
73
|
+
export const GRID_WIDTH = CHUNK_SIZE * GRID_CHUNKS_X;
|
|
74
|
+
export const GRID_HEIGHT = CHUNK_SIZE * GRID_CHUNKS_Z;
|
|
75
|
+
export function isPaperMapChunk(cx, cz) {
|
|
76
|
+
return cx >= 0 && cz >= 0 && cx < DEMO_CHUNKS_X && cz < DEMO_CHUNKS_Z;
|
|
77
|
+
}
|
|
78
|
+
export const HEIGHT_STEP = 0.45;
|
|
79
|
+
export const MAX_STEP_HEIGHT = 2;
|
|
80
|
+
export const PLAYABLE_BEACH_START_Z = 56;
|
|
81
|
+
export const PLAYABLE_BEACH_SAND_START_Z = 60;
|
|
82
|
+
export const PLAYABLE_OCEAN_START_Z = 82;
|
|
83
|
+
export const BEACH_START_Z = PLAYABLE_BEACH_START_Z + WORLD_NORTH_PAD;
|
|
84
|
+
export const BEACH_SAND_START_Z = PLAYABLE_BEACH_SAND_START_Z + WORLD_NORTH_PAD;
|
|
85
|
+
export const OCEAN_START_Z = PLAYABLE_OCEAN_START_Z + WORLD_NORTH_PAD;
|
|
86
|
+
export const BEACH_GATE_X = FARMLAND_WIDTH + 16;
|
|
87
|
+
export const BEACH_GATE_HALF = 4;
|
|
88
|
+
export const CHUNK_VIEW_RADIUS = 2;
|
|
89
|
+
export const CART_DISTURB_COOLDOWN_MS = 45000;
|
|
90
|
+
export var SurfaceType;
|
|
91
|
+
(function (SurfaceType) {
|
|
92
|
+
SurfaceType[SurfaceType["Grass"] = 0] = "Grass";
|
|
93
|
+
SurfaceType[SurfaceType["Path"] = 1] = "Path";
|
|
94
|
+
SurfaceType[SurfaceType["Water"] = 2] = "Water";
|
|
95
|
+
SurfaceType[SurfaceType["Sand"] = 3] = "Sand";
|
|
96
|
+
SurfaceType[SurfaceType["Ash"] = 4] = "Ash";
|
|
97
|
+
SurfaceType[SurfaceType["Dirt"] = 5] = "Dirt";
|
|
98
|
+
SurfaceType[SurfaceType["Soil"] = 6] = "Soil";
|
|
99
|
+
SurfaceType[SurfaceType["Stone"] = 7] = "Stone";
|
|
100
|
+
SurfaceType[SurfaceType["Mud"] = 8] = "Mud";
|
|
101
|
+
})(SurfaceType || (SurfaceType = {}));
|
|
102
|
+
export var TileType;
|
|
103
|
+
(function (TileType) {
|
|
104
|
+
TileType[TileType["Grass"] = 0] = "Grass";
|
|
105
|
+
TileType[TileType["Path"] = 1] = "Path";
|
|
106
|
+
TileType[TileType["Water"] = 2] = "Water";
|
|
107
|
+
TileType[TileType["Tree"] = 3] = "Tree";
|
|
108
|
+
TileType[TileType["Sand"] = 4] = "Sand";
|
|
109
|
+
TileType[TileType["Ash"] = 5] = "Ash";
|
|
110
|
+
})(TileType || (TileType = {}));
|
|
111
|
+
export const SURFACE_COLORS = {
|
|
112
|
+
[SurfaceType.Grass]: '#4a7c3f',
|
|
113
|
+
[SurfaceType.Path]: '#c2a878',
|
|
114
|
+
[SurfaceType.Water]: '#3a6ea5',
|
|
115
|
+
[SurfaceType.Sand]: '#e2c98a',
|
|
116
|
+
[SurfaceType.Ash]: '#4a3f36',
|
|
117
|
+
[SurfaceType.Dirt]: '#9a7b4f',
|
|
118
|
+
[SurfaceType.Soil]: '#6b4a2c',
|
|
119
|
+
[SurfaceType.Stone]: '#8a887f',
|
|
120
|
+
[SurfaceType.Mud]: '#4a3824',
|
|
121
|
+
};
|
|
122
|
+
export const TILE_COLORS = {
|
|
123
|
+
[TileType.Grass]: '#4a7c3f',
|
|
124
|
+
[TileType.Path]: '#c2a878',
|
|
125
|
+
[TileType.Water]: '#3a6ea5',
|
|
126
|
+
[TileType.Tree]: '#2d5a27',
|
|
127
|
+
[TileType.Sand]: '#e2c98a',
|
|
128
|
+
[TileType.Ash]: '#4a3f36',
|
|
129
|
+
};
|
|
130
|
+
export function chunkKey(cx, cz) {
|
|
131
|
+
return `${cx},${cz}`;
|
|
132
|
+
}
|
|
133
|
+
export function worldToChunk(x, z) {
|
|
134
|
+
const cx = Math.floor(x / CHUNK_SIZE);
|
|
135
|
+
const cz = Math.floor(z / CHUNK_SIZE);
|
|
136
|
+
return {
|
|
137
|
+
cx,
|
|
138
|
+
cz,
|
|
139
|
+
lx: ((x % CHUNK_SIZE) + CHUNK_SIZE) % CHUNK_SIZE,
|
|
140
|
+
lz: ((z % CHUNK_SIZE) + CHUNK_SIZE) % CHUNK_SIZE,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
export function toWorldZ(localZ) {
|
|
144
|
+
return localZ + WORLD_NORTH_PAD;
|
|
145
|
+
}
|
|
146
|
+
export function chunksAround(cx, cz, radius) {
|
|
147
|
+
const out = [];
|
|
148
|
+
for (let dz = -radius; dz <= radius; dz++) {
|
|
149
|
+
for (let dx = -radius; dx <= radius; dx++) {
|
|
150
|
+
out.push({ cx: cx + dx, cz: cz + dz });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return out;
|
|
154
|
+
}
|
|
155
|
+
export function localIndex(lx, lz) {
|
|
156
|
+
return lz * CHUNK_SIZE + lx;
|
|
157
|
+
}
|
|
158
|
+
export function isWalkableSurface(surface) {
|
|
159
|
+
if (surface === SurfaceType.Water)
|
|
160
|
+
return false;
|
|
161
|
+
return (surface === SurfaceType.Grass ||
|
|
162
|
+
surface === SurfaceType.Path ||
|
|
163
|
+
surface === SurfaceType.Sand ||
|
|
164
|
+
surface === SurfaceType.Ash ||
|
|
165
|
+
surface === SurfaceType.Dirt ||
|
|
166
|
+
surface === SurfaceType.Soil ||
|
|
167
|
+
surface === SurfaceType.Stone ||
|
|
168
|
+
surface === SurfaceType.Mud);
|
|
169
|
+
}
|
|
170
|
+
export function entityBlocksWalk(kind) {
|
|
171
|
+
return (kind === 'tree' ||
|
|
172
|
+
kind === 'rock' ||
|
|
173
|
+
kind === 'beachhut' ||
|
|
174
|
+
kind === 'farmhut' ||
|
|
175
|
+
kind === 'storagehut' ||
|
|
176
|
+
kind === 'bankchest' ||
|
|
177
|
+
kind === 'workshop' ||
|
|
178
|
+
kind === 'table' ||
|
|
179
|
+
kind === 'fence' ||
|
|
180
|
+
kind === 'tradingcart' ||
|
|
181
|
+
kind === 'signpost' ||
|
|
182
|
+
kind === 'brokenpier' ||
|
|
183
|
+
kind === 'abandonedboat' ||
|
|
184
|
+
kind === 'ship' ||
|
|
185
|
+
kind === 'campfire' ||
|
|
186
|
+
kind === 'fortwall' ||
|
|
187
|
+
kind === 'archertower' ||
|
|
188
|
+
kind === 'barbedwire' ||
|
|
189
|
+
kind === 'barricade' ||
|
|
190
|
+
kind === 'haybale' ||
|
|
191
|
+
kind === 'horse' ||
|
|
192
|
+
kind === 'trough' ||
|
|
193
|
+
kind === 'well' ||
|
|
194
|
+
kind === 'farmcart' ||
|
|
195
|
+
kind === 'logpile' ||
|
|
196
|
+
kind === 'lantern' ||
|
|
197
|
+
kind === 'bench' ||
|
|
198
|
+
kind === 'milestone' ||
|
|
199
|
+
kind === 'tent' ||
|
|
200
|
+
kind === 'mudhut' ||
|
|
201
|
+
kind === 'cargo' ||
|
|
202
|
+
kind === 'scarecrow' ||
|
|
203
|
+
kind === 'barrel' ||
|
|
204
|
+
kind === 'compost' ||
|
|
205
|
+
kind === 'farmtool' ||
|
|
206
|
+
kind === 'caveentrance' ||
|
|
207
|
+
kind === 'ranchgate' ||
|
|
208
|
+
kind === 'dungeondoor' ||
|
|
209
|
+
kind === 'silo' ||
|
|
210
|
+
kind === 'grainmill' ||
|
|
211
|
+
kind === 'pumpwindmill' ||
|
|
212
|
+
kind === 'watertower' ||
|
|
213
|
+
kind === 'packbarn' ||
|
|
214
|
+
kind === 'packcoop' ||
|
|
215
|
+
kind === 'packstable' ||
|
|
216
|
+
kind === 'rabbithutch');
|
|
217
|
+
}
|
|
218
|
+
/** Dungeon room-gate visual states — mirrors ranch gate open/closed styling. */
|
|
219
|
+
export function isDungeonDoorOpen(style) {
|
|
220
|
+
// Dungeon gates retain their room id in the style (`gate1-open` etc.) so
|
|
221
|
+
// the renderer can select the right model. Treat all of those as open, as
|
|
222
|
+
// well as the original generic style kept for older worlds.
|
|
223
|
+
return style === 'open' || style?.endsWith('-open') === true;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* A dungeon gate the viewing player has earned the right to open, but which is
|
|
227
|
+
* still physically DOWN.
|
|
228
|
+
*
|
|
229
|
+
* Unlocking used to swap the style straight to `-open`, so a gate you had
|
|
230
|
+
* cleared was simply drawn permanently winched up — there was never a moment of
|
|
231
|
+
* opening it, and nobody else saw anything happen. `-ready` keeps the
|
|
232
|
+
* portcullis closed and lets the client mark it as openable (a glow), so the
|
|
233
|
+
* open is an action with an animation and a sound rather than a state you
|
|
234
|
+
* arrive to.
|
|
235
|
+
*/
|
|
236
|
+
export function isDungeonGateReady(style) {
|
|
237
|
+
return typeof style === 'string' && style.endsWith('-ready');
|
|
238
|
+
}
|
|
239
|
+
/** Goblin cell gates share personal dungeon crossing, not shared fence toggles. */
|
|
240
|
+
export function isGoblinPrisonDoorStyle(style) {
|
|
241
|
+
return typeof style === 'string' && /^goblin-prison-(north|south)(?:-ready)?$/.test(style);
|
|
242
|
+
}
|
|
243
|
+
/** Dungeon wall torch — lantern entity with a `torch-*` facing style. */
|
|
244
|
+
export function isWallTorchStyle(style) {
|
|
245
|
+
return typeof style === 'string' && style.startsWith('torch');
|
|
246
|
+
}
|
|
247
|
+
/** Parse farm fence gate styles such as `gate-wide-closed-ew`. */
|
|
248
|
+
export function parseFenceGateStyle(style) {
|
|
249
|
+
if (typeof style !== 'string' || !style.startsWith('gate'))
|
|
250
|
+
return null;
|
|
251
|
+
let rest = style;
|
|
252
|
+
let ew = false;
|
|
253
|
+
let closed = false;
|
|
254
|
+
if (rest.endsWith('-ew')) {
|
|
255
|
+
ew = true;
|
|
256
|
+
rest = rest.slice(0, -3);
|
|
257
|
+
}
|
|
258
|
+
if (rest.endsWith('-closed')) {
|
|
259
|
+
closed = true;
|
|
260
|
+
rest = rest.slice(0, -7);
|
|
261
|
+
}
|
|
262
|
+
if (rest === 'gate' || rest === 'gate-wide' || rest === 'gate-prison') {
|
|
263
|
+
return { kind: rest, ew, closed };
|
|
264
|
+
}
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
export function formatFenceGateStyle(info) {
|
|
268
|
+
let style = info.kind;
|
|
269
|
+
if (info.closed)
|
|
270
|
+
style += '-closed';
|
|
271
|
+
if (info.ew)
|
|
272
|
+
style += '-ew';
|
|
273
|
+
return style;
|
|
274
|
+
}
|
|
275
|
+
/** Open fence gate styles that leave a walkable gap in a fence ring. */
|
|
276
|
+
export function isOpenGateStyle(style) {
|
|
277
|
+
const gate = parseFenceGateStyle(style);
|
|
278
|
+
return gate !== null && !gate.closed;
|
|
279
|
+
}
|
|
280
|
+
export function isFenceGateStyle(style) {
|
|
281
|
+
return parseFenceGateStyle(style) !== null;
|
|
282
|
+
}
|
|
283
|
+
export function isRanchGateOpen(style) {
|
|
284
|
+
if (typeof style !== 'string' || style.length === 0)
|
|
285
|
+
return true;
|
|
286
|
+
return !style.includes('closed');
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Storage-hut (Base Camp bank) door.
|
|
290
|
+
*
|
|
291
|
+
* Plain `'repaired'` means open, and only the explicit closed style shuts it.
|
|
292
|
+
* After repair the door starts closed; clicking the personal enter icon opens
|
|
293
|
+
* it, teleports the player through, then closes it again.
|
|
294
|
+
*
|
|
295
|
+
* The door is deliberately **not** a passage blocker. Hut repair is per-player
|
|
296
|
+
* (see World.entityForPlayer, which rewrites the style per viewer off
|
|
297
|
+
* `player.storageHutRepaired`), so the door state is per-player too — and the
|
|
298
|
+
* walkability grid is global, so a per-player door cannot gate it without one
|
|
299
|
+
* player's door blocking another player's path. Unrepaired players still see
|
|
300
|
+
* the abandoned wreck (no enter icon); repaired players use the door portal.
|
|
301
|
+
*/
|
|
302
|
+
export const STORAGE_HUT_DOOR_CLOSED_STYLE = 'repaired-closed';
|
|
303
|
+
export function isStorageHutRepairedStyle(style) {
|
|
304
|
+
return style === 'repaired' || style === STORAGE_HUT_DOOR_CLOSED_STYLE;
|
|
305
|
+
}
|
|
306
|
+
export function isStorageHutDoorOpen(style) {
|
|
307
|
+
return style !== STORAGE_HUT_DOOR_CLOSED_STYLE;
|
|
308
|
+
}
|
|
309
|
+
export function isGateInteractable(kind, style) {
|
|
310
|
+
if (kind === 'ranchgate')
|
|
311
|
+
return true;
|
|
312
|
+
if (kind === 'fence')
|
|
313
|
+
return isFenceGateStyle(style);
|
|
314
|
+
// Dungeon doors are objectives rather than toggles, but still need an
|
|
315
|
+
// interaction so players can read why the route is blocked.
|
|
316
|
+
if (kind === 'dungeondoor')
|
|
317
|
+
return true;
|
|
318
|
+
// Only once it has been repaired — a wrecked hut has no door to swing.
|
|
319
|
+
if (kind === 'storagehut')
|
|
320
|
+
return isStorageHutRepairedStyle(style);
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
export function isGateOpen(kind, style) {
|
|
324
|
+
if (kind === 'ranchgate')
|
|
325
|
+
return isRanchGateOpen(style);
|
|
326
|
+
if (kind === 'fence')
|
|
327
|
+
return isOpenGateStyle(style);
|
|
328
|
+
if (kind === 'dungeondoor')
|
|
329
|
+
return isDungeonDoorOpen(style);
|
|
330
|
+
if (kind === 'storagehut')
|
|
331
|
+
return isStorageHutDoorOpen(style);
|
|
332
|
+
return true;
|
|
333
|
+
}
|
|
334
|
+
export function toggleGateStyle(kind, style) {
|
|
335
|
+
if (kind === 'ranchgate') {
|
|
336
|
+
const ns = typeof style === 'string' && style.includes('ns');
|
|
337
|
+
const closed = !isRanchGateOpen(style);
|
|
338
|
+
if (closed)
|
|
339
|
+
return ns ? 'ns' : undefined;
|
|
340
|
+
return ns ? 'ns-closed' : 'closed';
|
|
341
|
+
}
|
|
342
|
+
if (kind === 'fence') {
|
|
343
|
+
const gate = parseFenceGateStyle(style);
|
|
344
|
+
if (!gate)
|
|
345
|
+
return style;
|
|
346
|
+
return formatFenceGateStyle({ ...gate, closed: !gate.closed });
|
|
347
|
+
}
|
|
348
|
+
if (kind === 'storagehut') {
|
|
349
|
+
if (!isStorageHutRepairedStyle(style))
|
|
350
|
+
return style;
|
|
351
|
+
return isStorageHutDoorOpen(style) ? STORAGE_HUT_DOOR_CLOSED_STYLE : 'repaired';
|
|
352
|
+
}
|
|
353
|
+
return style;
|
|
354
|
+
}
|
|
355
|
+
/** Tile offsets a closed gate blocks relative to its entity tile. */
|
|
356
|
+
export function visitGatePassageOffsets(kind, style, visit) {
|
|
357
|
+
if (kind === 'ranchgate') {
|
|
358
|
+
visit(0, 0);
|
|
359
|
+
// Default ranch gate faces E/W traffic (posts along Z). `ns` flips that.
|
|
360
|
+
if (typeof style === 'string' && style.includes('ns')) {
|
|
361
|
+
visit(-1, 0);
|
|
362
|
+
visit(1, 0);
|
|
363
|
+
}
|
|
364
|
+
else {
|
|
365
|
+
visit(0, -1);
|
|
366
|
+
visit(0, 1);
|
|
367
|
+
}
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
if (kind === 'fence') {
|
|
371
|
+
const gate = parseFenceGateStyle(style);
|
|
372
|
+
if (!gate)
|
|
373
|
+
return;
|
|
374
|
+
visit(0, 0);
|
|
375
|
+
if (gate.kind === 'gate-wide') {
|
|
376
|
+
if (gate.ew)
|
|
377
|
+
visit(1, 0);
|
|
378
|
+
else
|
|
379
|
+
visit(0, 1);
|
|
380
|
+
}
|
|
381
|
+
if (gate.kind === 'gate-prison') {
|
|
382
|
+
if (gate.ew) {
|
|
383
|
+
visit(-1, 0);
|
|
384
|
+
visit(1, 0);
|
|
385
|
+
}
|
|
386
|
+
else {
|
|
387
|
+
visit(0, -1);
|
|
388
|
+
visit(0, 1);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
/** Must match the farmhut visual multiplier in frontend assets/index.ts. */
|
|
394
|
+
const FARMHUT_RENDER_SCALE = 1.15;
|
|
395
|
+
/** Stable shell width from farmHut.ts configs.stable (before render scale). */
|
|
396
|
+
const STABLE_MESH_WIDTH = 2.28;
|
|
397
|
+
const STABLE_LEAN_WIDTH = 0.58;
|
|
398
|
+
function normalizeBuildingFootprint(footprint) {
|
|
399
|
+
if (typeof footprint === 'number') {
|
|
400
|
+
const r = footprint > 0 ? footprint : 0;
|
|
401
|
+
return { radiusX: r, radiusZ: r };
|
|
402
|
+
}
|
|
403
|
+
return {
|
|
404
|
+
radiusX: footprint.radiusX > 0 ? footprint.radiusX : 0,
|
|
405
|
+
radiusZ: footprint.radiusZ > 0 ? footprint.radiusZ : 0,
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
function buildingScaleBracketRadius(scale) {
|
|
409
|
+
if (scale >= 4.5)
|
|
410
|
+
return 4;
|
|
411
|
+
if (scale >= 3.25)
|
|
412
|
+
return 3;
|
|
413
|
+
if (scale >= 2.5)
|
|
414
|
+
return 2;
|
|
415
|
+
return 1;
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Tile wall-ring size for beach/farm/storage huts.
|
|
419
|
+
* Most styles use a square from scale brackets. The farm `stable` is a long
|
|
420
|
+
* barn whose mesh (×1.15 render scale) is wider than the scale bracket, so
|
|
421
|
+
* radiusX follows the mesh half-width (walls align with the visual ends).
|
|
422
|
+
* Z stays on the bracket so the south wall stays north of horse-pen gates.
|
|
423
|
+
*/
|
|
424
|
+
export function buildingFootprintExtents(kind, scale, style) {
|
|
425
|
+
if (kind !== 'beachhut' && kind !== 'farmhut' && kind !== 'storagehut' && kind !== 'workshop') {
|
|
426
|
+
return { radiusX: 0, radiusZ: 0 };
|
|
427
|
+
}
|
|
428
|
+
if (typeof scale !== 'number' || !Number.isFinite(scale) || scale < 2) {
|
|
429
|
+
return { radiusX: 0, radiusZ: 0 };
|
|
430
|
+
}
|
|
431
|
+
const bracket = buildingScaleBracketRadius(scale);
|
|
432
|
+
if (kind === 'farmhut' && style === 'stable') {
|
|
433
|
+
const size = scale * FARMHUT_RENDER_SCALE;
|
|
434
|
+
const halfW = (STABLE_MESH_WIDTH * size) / 2;
|
|
435
|
+
// Hollow wall ring must sit on the visual end walls (not past them), or the
|
|
436
|
+
// mesh ends become walkable "interior" tiles.
|
|
437
|
+
const radiusX = Math.max(bracket, Math.ceil(halfW - 0.35));
|
|
438
|
+
return { radiusX, radiusZ: bracket };
|
|
439
|
+
}
|
|
440
|
+
return { radiusX: bracket, radiusZ: bracket };
|
|
441
|
+
}
|
|
442
|
+
/** West tack-room lean-to tiles beyond the stable's main wall ring. */
|
|
443
|
+
export function visitStableLeanToBlockedOffsets(scale, visit) {
|
|
444
|
+
if (typeof scale !== 'number' || !Number.isFinite(scale) || scale < 2)
|
|
445
|
+
return;
|
|
446
|
+
const fp = buildingFootprintExtents('farmhut', scale, 'stable');
|
|
447
|
+
if (fp.radiusX <= 0 || fp.radiusZ <= 0)
|
|
448
|
+
return;
|
|
449
|
+
const size = scale * FARMHUT_RENDER_SCALE;
|
|
450
|
+
const leanReach = STABLE_LEAN_WIDTH * size * 0.75;
|
|
451
|
+
const leanTiles = Math.max(1, Math.ceil(leanReach - 0.2));
|
|
452
|
+
const halfDepth = Math.max(1, fp.radiusZ - 1);
|
|
453
|
+
for (let i = 1; i <= leanTiles; i++) {
|
|
454
|
+
for (let dz = -halfDepth; dz <= halfDepth; dz++) {
|
|
455
|
+
visit(-fp.radiusX - i, dz);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
/** Max axis of {@link buildingFootprintExtents} (square buildings: the ring radius). */
|
|
460
|
+
export function buildingFootprintRadius(kind, scale, style) {
|
|
461
|
+
const { radiusX, radiusZ } = buildingFootprintExtents(kind, scale, style);
|
|
462
|
+
return Math.max(radiusX, radiusZ);
|
|
463
|
+
}
|
|
464
|
+
export function isBuildingDoorOffset(dx, dz, footprint, doorFacing = 'posZ') {
|
|
465
|
+
const { radiusX, radiusZ } = normalizeBuildingFootprint(footprint);
|
|
466
|
+
if (radiusX <= 0 || radiusZ <= 0)
|
|
467
|
+
return false;
|
|
468
|
+
if (doorFacing === 'posZ')
|
|
469
|
+
return dx === 0 && dz === radiusZ;
|
|
470
|
+
if (doorFacing === 'negZ')
|
|
471
|
+
return dx === 0 && dz === -radiusZ;
|
|
472
|
+
if (doorFacing === 'posX')
|
|
473
|
+
return dx === radiusX && dz === 0;
|
|
474
|
+
return dx === -radiusX && dz === 0;
|
|
475
|
+
}
|
|
476
|
+
export function buildingWallBlocksOffset(dx, dz, footprint, doorFacing = 'posZ') {
|
|
477
|
+
const { radiusX, radiusZ } = normalizeBuildingFootprint(footprint);
|
|
478
|
+
if (radiusX <= 0 || radiusZ <= 0)
|
|
479
|
+
return false;
|
|
480
|
+
if (Math.abs(dx) > radiusX || Math.abs(dz) > radiusZ)
|
|
481
|
+
return false;
|
|
482
|
+
const onWallRing = Math.abs(dx) === radiusX || Math.abs(dz) === radiusZ;
|
|
483
|
+
if (!onWallRing)
|
|
484
|
+
return false;
|
|
485
|
+
return !isBuildingDoorOffset(dx, dz, { radiusX, radiusZ }, doorFacing);
|
|
486
|
+
}
|
|
487
|
+
export function isBuildingInteriorOffset(dx, dz, footprint, doorFacing = 'posZ') {
|
|
488
|
+
const { radiusX, radiusZ } = normalizeBuildingFootprint(footprint);
|
|
489
|
+
if (radiusX <= 0 || radiusZ <= 0)
|
|
490
|
+
return false;
|
|
491
|
+
if (Math.abs(dx) > radiusX || Math.abs(dz) > radiusZ)
|
|
492
|
+
return false;
|
|
493
|
+
return !buildingWallBlocksOffset(dx, dz, { radiusX, radiusZ }, doorFacing);
|
|
494
|
+
}
|
|
495
|
+
export function boulderFootprintRadius(style, scale) {
|
|
496
|
+
if (style !== 'boulder')
|
|
497
|
+
return 0;
|
|
498
|
+
if (typeof scale !== 'number' || !Number.isFinite(scale) || scale < 1.6)
|
|
499
|
+
return 0;
|
|
500
|
+
// Boulder meshes span ~1.05*scale tiles; walkable centers sit at radius+1.
|
|
501
|
+
return Math.min(3, Math.max(1, Math.ceil(scale * 1.05 - 0.55)));
|
|
502
|
+
}
|
|
503
|
+
export function blockingFootprintRadius(kind, style, scale) {
|
|
504
|
+
if (kind === 'beachhut' || kind === 'farmhut' || kind === 'storagehut' || kind === 'workshop')
|
|
505
|
+
return 0;
|
|
506
|
+
if (kind === 'rock') {
|
|
507
|
+
if (style === 'boulder')
|
|
508
|
+
return boulderFootprintRadius(style, scale);
|
|
509
|
+
const s = typeof scale === 'number' && Number.isFinite(scale) ? scale : 1;
|
|
510
|
+
if (style === 'pebble')
|
|
511
|
+
return 0;
|
|
512
|
+
// Crags sit on blocked cliff tiles; one extra ring covers remaining overhang.
|
|
513
|
+
if (style === 'crag')
|
|
514
|
+
return s >= 1.6 ? 1 : 0;
|
|
515
|
+
if (s >= 2.2)
|
|
516
|
+
return 1;
|
|
517
|
+
return 0;
|
|
518
|
+
}
|
|
519
|
+
if (kind === 'tree') {
|
|
520
|
+
const s = typeof scale === 'number' && Number.isFinite(scale) ? scale : 1;
|
|
521
|
+
return s >= 1.35 ? 1 : 0;
|
|
522
|
+
}
|
|
523
|
+
if (kind === 'brokenpier' || kind === 'abandonedboat' || kind === 'ship')
|
|
524
|
+
return 1;
|
|
525
|
+
if (kind === 'fortwall' || kind === 'archertower')
|
|
526
|
+
return 0;
|
|
527
|
+
if (kind === 'fence' || kind === 'table' || kind === 'barbedwire' || kind === 'barricade') {
|
|
528
|
+
return 0;
|
|
529
|
+
}
|
|
530
|
+
// Imported decorative structures (silo, grain mill, pump windmill, water
|
|
531
|
+
// tower): sized well beyond one tile, so a bare centre block would leave
|
|
532
|
+
// players walking straight through the visible mesh. The stone grain mill
|
|
533
|
+
// (the tall "old windmill") is the largest (~9.5 tiles); the steel pump /
|
|
534
|
+
// silo / tower still need more than a 3×3 or you clip through the sails and tanks.
|
|
535
|
+
if (kind === 'grainmill')
|
|
536
|
+
return 4;
|
|
537
|
+
if (kind === 'silo' || kind === 'pumpwindmill' || kind === 'watertower')
|
|
538
|
+
return 3;
|
|
539
|
+
// Imported buildings (barn, chicken coop, stable): treated as solid
|
|
540
|
+
// exteriors, not the hollow-wall-ring-with-door system the procedural
|
|
541
|
+
// farmhut/beachhut/storagehut buildings use — nothing in the game requires
|
|
542
|
+
// walking inside these specifically (the stablemaster NPC already stands
|
|
543
|
+
// just outside the old stable's door, not in its interior).
|
|
544
|
+
if (kind === 'packbarn' || kind === 'packstable')
|
|
545
|
+
return 4;
|
|
546
|
+
if (kind === 'packcoop')
|
|
547
|
+
return 2;
|
|
548
|
+
// Sleeping / battlefield tents and goblin mudhuts are solid 5×5 (radius 2),
|
|
549
|
+
// matching map.ts claimTentFootprint. Hollow "great" craft tents keep the
|
|
550
|
+
// wall-ring carve-out from map generation instead of filling the hall.
|
|
551
|
+
if (kind === 'tent') {
|
|
552
|
+
if (typeof style === 'string' && style.includes('great'))
|
|
553
|
+
return 0;
|
|
554
|
+
return 2;
|
|
555
|
+
}
|
|
556
|
+
if (kind === 'mudhut')
|
|
557
|
+
return 2;
|
|
558
|
+
return 0;
|
|
559
|
+
}
|
|
560
|
+
const CAVE_ENTRANCE_FORWARD = 2;
|
|
561
|
+
// The mesh's cliff/boulder backdrop (see caveEntrance.ts) renders several tiles
|
|
562
|
+
// deep behind the doorway and out to the sides. Base Camp's original entrance
|
|
563
|
+
// is dug into the mountain rim, so that backdrop happens to sit over terrain
|
|
564
|
+
// that's already unwalkable — but forest animal dens and the dungeon exit use a
|
|
565
|
+
// compact footprint so the tiny hole doesn't block a huge patch of woods.
|
|
566
|
+
const CAVE_ENTRANCE_BACK = 4;
|
|
567
|
+
const CAVE_ENTRANCE_HALF_WIDTH = 4;
|
|
568
|
+
function caveEntranceFacing(style) {
|
|
569
|
+
if (style === 'goblin-return-north')
|
|
570
|
+
return { fx: 0, fz: -1, sx: 1, sz: 0 };
|
|
571
|
+
if (isGoblinCaveStyle(style))
|
|
572
|
+
return { fx: 1, fz: 0, sx: 0, sz: 1 };
|
|
573
|
+
const dir = style?.includes('west')
|
|
574
|
+
? 'west'
|
|
575
|
+
: style?.includes('east')
|
|
576
|
+
? 'east'
|
|
577
|
+
: style?.includes('north')
|
|
578
|
+
? 'north'
|
|
579
|
+
: 'south';
|
|
580
|
+
if (dir === 'west')
|
|
581
|
+
return { fx: -1, fz: 0, sx: 0, sz: 1 };
|
|
582
|
+
if (dir === 'east')
|
|
583
|
+
return { fx: 1, fz: 0, sx: 0, sz: 1 };
|
|
584
|
+
if (dir === 'north')
|
|
585
|
+
return { fx: 0, fz: -1, sx: 1, sz: 0 };
|
|
586
|
+
return { fx: 0, fz: 1, sx: 1, sz: 0 };
|
|
587
|
+
}
|
|
588
|
+
export function isCaveTunnelExitStyle(style) {
|
|
589
|
+
return typeof style === 'string' && style.startsWith('exit');
|
|
590
|
+
}
|
|
591
|
+
export function isGoblinCaveStyle(style) {
|
|
592
|
+
return style === 'goblin-burrow' || style?.startsWith('goblin-return') === true;
|
|
593
|
+
}
|
|
594
|
+
/** Tiny animal-dug hole in the woods — not the Base Camp Suspicious hole. */
|
|
595
|
+
export function isAnimalDenStyle(style) {
|
|
596
|
+
return typeof style === 'string' && style.startsWith('den-');
|
|
597
|
+
}
|
|
598
|
+
/** Crawl-out hole inside a forest den cavern. */
|
|
599
|
+
export function isDenExitStyle(style) {
|
|
600
|
+
return typeof style === 'string' && style.startsWith('denexit');
|
|
601
|
+
}
|
|
602
|
+
export function caveEntranceApproachOffset(style) {
|
|
603
|
+
const { fx, fz } = caveEntranceFacing(style);
|
|
604
|
+
const compact = isCaveTunnelExitStyle(style) || isAnimalDenStyle(style) || isDenExitStyle(style) || isGoblinCaveStyle(style);
|
|
605
|
+
const dist = compact ? 2 : CAVE_ENTRANCE_FORWARD + 1;
|
|
606
|
+
return { dx: dist * fx, dz: dist * fz };
|
|
607
|
+
}
|
|
608
|
+
export function visitCaveEntranceBlockedOffsets(style, visit) {
|
|
609
|
+
if (style === 'goblin-return-north') {
|
|
610
|
+
for (let dx = -1; dx <= 1; dx++)
|
|
611
|
+
visit(dx, 0);
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
if (style === 'goblin-return') {
|
|
615
|
+
visit(0, 0);
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
if (style === 'goblin-burrow') {
|
|
619
|
+
// East-facing mountain shoulders, with a clear throat in front of the door.
|
|
620
|
+
for (let dx = -4; dx <= 2; dx++) {
|
|
621
|
+
for (let dz = -7; dz <= 7; dz++) {
|
|
622
|
+
if (dx <= 0 || Math.abs(dz) >= 2)
|
|
623
|
+
visit(dx, dz);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
const { fx, fz, sx, sz } = caveEntranceFacing(style);
|
|
629
|
+
const den = isAnimalDenStyle(style);
|
|
630
|
+
const compact = isCaveTunnelExitStyle(style) || den || isDenExitStyle(style);
|
|
631
|
+
const forwardMax = compact ? 1 : CAVE_ENTRANCE_FORWARD;
|
|
632
|
+
const backMax = compact ? 0 : CAVE_ENTRANCE_BACK;
|
|
633
|
+
const halfW = den ? 2 : compact ? 1 : CAVE_ENTRANCE_HALF_WIDTH;
|
|
634
|
+
for (let forward = -backMax; forward <= forwardMax; forward++) {
|
|
635
|
+
for (let side = -halfW; side <= halfW; side++) {
|
|
636
|
+
visit(forward * fx + side * sx, forward * fz + side * sz);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
export function visitBlockedWalkOffsets(kind, style, scale, visit, doorFacing = kind === 'storagehut' ? 'negZ' : 'posZ') {
|
|
641
|
+
if (!entityBlocksWalk(kind))
|
|
642
|
+
return;
|
|
643
|
+
if (kind === 'dungeondoor' && isGoblinPrisonDoorStyle(style)) {
|
|
644
|
+
// Unlocking is per character. Never remove shared collision for -ready.
|
|
645
|
+
for (let dz = -1; dz <= 1; dz++)
|
|
646
|
+
visit(0, dz);
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
if (kind === 'dungeondoor' && style?.startsWith('goblin-gate')) {
|
|
650
|
+
// Personal passage: remains solid even when the viewer has earned -ready.
|
|
651
|
+
for (let dx = -3; dx <= 3; dx++)
|
|
652
|
+
visit(dx, 0);
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
if (kind === 'caveentrance') {
|
|
656
|
+
visitCaveEntranceBlockedOffsets(style, visit);
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
if (kind === 'fence' && isFenceGateStyle(style)) {
|
|
660
|
+
if (isOpenGateStyle(style))
|
|
661
|
+
return;
|
|
662
|
+
visitGatePassageOffsets(kind, style, visit);
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
if (kind === 'ranchgate') {
|
|
666
|
+
if (isRanchGateOpen(style))
|
|
667
|
+
return;
|
|
668
|
+
visitGatePassageOffsets(kind, style, visit);
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
const buildingFp = buildingFootprintExtents(kind, scale, style);
|
|
672
|
+
if (buildingFp.radiusX > 0 && buildingFp.radiusZ > 0) {
|
|
673
|
+
// Hollow buildings: only the wall ring is solid. The origin is interior
|
|
674
|
+
// floor — blocking it sealed the storage hut so you could never stand inside.
|
|
675
|
+
for (let dz = -buildingFp.radiusZ; dz <= buildingFp.radiusZ; dz++) {
|
|
676
|
+
for (let dx = -buildingFp.radiusX; dx <= buildingFp.radiusX; dx++) {
|
|
677
|
+
if (!buildingWallBlocksOffset(dx, dz, buildingFp, doorFacing))
|
|
678
|
+
continue;
|
|
679
|
+
// Cottage is a walk-through: south porch door and north garden door.
|
|
680
|
+
if (kind === 'farmhut' &&
|
|
681
|
+
style === 'cottage' &&
|
|
682
|
+
isBuildingDoorOffset(dx, dz, buildingFp, 'negZ')) {
|
|
683
|
+
continue;
|
|
684
|
+
}
|
|
685
|
+
visit(dx, dz);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
if (kind === 'farmhut' && style === 'stable') {
|
|
689
|
+
visitStableLeanToBlockedOffsets(scale, visit);
|
|
690
|
+
}
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
visit(0, 0);
|
|
694
|
+
const radius = blockingFootprintRadius(kind, style, scale);
|
|
695
|
+
if (radius <= 0)
|
|
696
|
+
return;
|
|
697
|
+
for (let dz = -radius; dz <= radius; dz++) {
|
|
698
|
+
for (let dx = -radius; dx <= radius; dx++) {
|
|
699
|
+
if (dx === 0 && dz === 0)
|
|
700
|
+
continue;
|
|
701
|
+
visit(dx, dz);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
export function isWalkable(tile) {
|
|
706
|
+
if (tile === TileType.Water)
|
|
707
|
+
return false;
|
|
708
|
+
return (tile === TileType.Grass ||
|
|
709
|
+
tile === TileType.Path ||
|
|
710
|
+
tile === TileType.Sand ||
|
|
711
|
+
tile === TileType.Ash);
|
|
712
|
+
}
|
|
713
|
+
//# sourceMappingURL=constants.js.map
|