Refine archive world spawn and interaction handling
All checks were successful
Build Org Website / build (push) Successful in 38s

This commit is contained in:
gitea-actions
2026-07-30 10:22:23 +01:00
parent 2f125f32ae
commit dc1ca5c00e
11 changed files with 112 additions and 33 deletions

View File

@@ -120,12 +120,12 @@
const AREA_FRAMES = Object.freeze({ vault: 0, workshop: 1, playroom: 2, boss: 3 });
const AREAS = Object.freeze({
town: Object.freeze({
width: 1448, height: 1086, background: "town", spawn: { x: 724, y: 965 },
width: 1448, height: 1086, background: "town", spawn: { x: 724, y: 1015 },
movement: Object.freeze({ speed: 162, acceleration: 1280, deceleration: 1850, bootAcceleration: 1720 }),
safeSpawns: Object.freeze({
gate: { x: 724, y: 965 }, square: { x: 724, y: 555 }, library: { x: 270, y: 350 },
study: { x: 720, y: 330 }, inn: { x: 1110, y: 350 }, workshop: { x: 1060, y: 615 },
playroom: { x: 1060, y: 840 }, museum: { x: 330, y: 835 }, garden: { x: 330, y: 545 }
gate: { x: 724, y: 1015 }, square: { x: 724, y: 555 }, library: { x: 270, y: 370 },
study: { x: 720, y: 350 }, inn: { x: 1110, y: 370 }, workshop: { x: 1060, y: 625 },
playroom: { x: 1120, y: 835 }, museum: { x: 270, y: 830 }, garden: { x: 370, y: 545 }
}),
safeZones: Object.freeze([
rect(650, 845, 150, 220), rect(585, 455, 280, 190)

View File

@@ -84,7 +84,7 @@
});
this.createAnimations();
this.npcSprites = [];
this.npcCollisions = this.physics.add.staticGroup();
const state = controller.getState();
const safe = Systems.nearestSafeSpawn(this.areaId,
state.position.area === this.areaId ? state.position.x : area.spawn.x,
@@ -100,6 +100,7 @@
this.interactables = [];
this.markers = [];
this.createWorldObjects();
this.physics.add.collider(this.player, this.npcCollisions);
this.createEnemies();
this.createInput();
@@ -164,11 +165,12 @@
if (!controller.reducedMotion()) this.tweens.add({ targets: marker, alpha: 0.45, scale: 1.14, yoyo: true, repeat: -1, duration: 1100 });
});
this.area.npcs.forEach(([id, x, y]) => {
const npc = this.add.sprite(x, y, "cast", Data.NPCS[id].frame).setScale(0.23).setDepth(y + 60);
const npc = this.npcCollisions.create(x, y, "cast", Data.NPCS[id].frame)
.setScale(0.23).setDepth(y + 60);
npc.refreshBody();
npc.body.setSize(82, 44).setOffset(115, 214);
npc.setData("interaction", { type: "npc", id, x, y });
this.npcSprites.push(npc);
this.interactables.push(npc.getData("interaction"));
if (!controller.reducedMotion()) this.tweens.add({ targets: npc, y: y - 2, duration: 1200 + Data.NPCS[id].frame * 30, yoyo: true, repeat: -1 });
});
this.addChest("town-garden", 205, 565, "memory_fragment", 2);
this.addChest("town-wall", 1235, 840, "ink_vial", 2);
@@ -215,6 +217,7 @@
createEnemies() {
this.enemies = this.physics.add.group();
this.physics.add.collider(this.enemies, this.collisions);
this.physics.add.collider(this.enemies, this.npcCollisions);
this.physics.add.collider(this.enemies, this.enemies);
this.area.enemies.forEach((entry) => {
const spawn = Array.isArray(entry)
@@ -289,7 +292,6 @@
if (!moving) this.stepDistance = 0;
}
this.player.setDepth(this.player.y + 60);
this.npcSprites.forEach((npc) => npc.setFlipX(this.player.x < npc.x));
this.updateEnemies(time, delta);
this.updateNearest();
this.handleKeys(time);
@@ -311,13 +313,7 @@
}
updateNearest() {
let nearest = null;
let best = Infinity;
this.interactables.forEach((item) => {
const distance = Phaser.Math.Distance.Between(this.player.x, this.player.y, item.x, item.y);
if (distance < best) { best = distance; nearest = item; }
});
this.nearest = best < 92 ? nearest : null;
this.nearest = Systems.nearestInteraction(this.interactables, this.player.x, this.player.y);
controller.setPrompt(this.nearest ? promptFor(this.nearest) : "");
}

View File

@@ -12,7 +12,7 @@
const LEGACY_KEY = "zxh_archive_world_v1";
const PALETTES = Object.freeze(["brass", "moss", "berry"]);
const FACES = Object.freeze(["north", "south", "east", "west"]);
const SPAWN = Object.freeze({ area: "town", spawn: "gate", x: 724, y: 965 });
const SPAWN = Object.freeze({ area: "town", spawn: "gate", x: 724, y: 1015 });
const LANDMARK_IDS = Data ? Data.LANDMARK_IDS : Object.freeze(["gate", "library", "study", "inn", "workshop", "playroom", "museum", "garden"]);
const QUEST_IDS = Data ? Data.QUEST_IDS : Object.freeze(LANDMARK_IDS.concat("lost_letter"));
const ITEM_IDS = Data ? Data.ITEM_IDS : Object.freeze(["living_bookmark", "lantern", "archive_key", "swiftstep_boots", "ink_vial", "memory_fragment", "garden_seed", "workshop_cog", "museum_lens", "recall_stew", "lore_page", "archive_sigil"]);

View File

@@ -193,9 +193,47 @@
return Boolean(area && (area.safeZones || []).some((item) => pointInRect(x, y, item, 0)));
}
function interactionRadius(item) {
return {
npc: 46,
landmark: 64,
portal: 58,
chest: 48,
challenge: 52,
"boss-memory": 50
}[item && item.type] || 44;
}
function nearestInteraction(items, x, y) {
let nearest = null;
let best = Infinity;
(Array.isArray(items) ? items : []).forEach((item) => {
if (!item || !Number.isFinite(item.x) || !Number.isFinite(item.y)) return;
const distance = Math.hypot(item.x - x, item.y - y);
if (distance <= interactionRadius(item) && distance < best) {
nearest = item;
best = distance;
}
});
return nearest;
}
function safeSpawnForState(state) {
const position = state && state.position;
const gate = Data.AREAS.town.gates && Data.AREAS.town.gates[0];
const gateLocked = state && Array.isArray(state.sigils) && !state.sigils.includes("gate");
if (position && position.area === "town" && gate && gateLocked && position.y < gate.y + gate.height) {
return nearestNamedSafeSpawn("town", Data.AREAS.town.safeSpawns.gate.x, Data.AREAS.town.safeSpawns.gate.y);
}
return nearestSafeSpawn(
position && position.area || "town",
position && position.x,
position && position.y
);
}
function nearestSafeSpawn(areaId, x, y) {
const area = Data.AREAS[areaId] || Data.AREAS.town;
const points = Object.entries(area.safeSpawns || { entrance: area.spawn });
const safeCurrent = isSafe(areaId, x, y);
if (safeCurrent) return { area: areaId, spawn: "saved", x, y };
return nearestNamedSafeSpawn(areaId, x, y);
@@ -223,6 +261,7 @@
return Object.freeze({
normalizedVector, approachVelocity, discover, startQuest, progressQuest, completeQuest, updateStory, addItem, itemQuantity,
consumeItem, grantXp, takeDamage, respawn, recordBoss, isSafe, isCombatSafe,
interactionRadius, nearestInteraction, safeSpawnForState,
nearestSafeSpawn, nearestNamedSafeSpawn, currentObjective
});
}));

View File

@@ -95,7 +95,7 @@
function ensureSafePosition(state) {
if (state.health <= 0) state = Systems.respawn(state);
const safe = Systems.nearestSafeSpawn(state.position.area, state.position.x, state.position.y);
const safe = Systems.safeSpawnForState(state);
return State.withPosition(state, safe.area, safe.x, safe.y, state.position.facing, safe.spawn);
}