Refactor org web platform and remove legacy code
All checks were successful
Build Org Website / build (push) Successful in 39s

This commit is contained in:
gitea-actions
2026-07-30 11:29:20 +01:00
parent dc1ca5c00e
commit fe8acac707
74 changed files with 2776 additions and 3386 deletions

View File

@@ -0,0 +1,212 @@
(function (root, factory) {
"use strict";
const data = root.PrincessLimaData || (typeof require === "function" ? require("./princess-lima-data.js") : null);
const stateApi = root.PrincessLimaState || (typeof require === "function" ? require("./princess-lima-state.js") : null);
const api = factory(data, stateApi);
if (typeof module === "object" && module.exports) module.exports = api;
root.PrincessLimaSystems = api;
}(typeof globalThis !== "undefined" ? globalThis : this, function (Data, State) {
"use strict";
function copy(state) {
return State.normalize(JSON.parse(JSON.stringify(state)));
}
function normalizedVector(x, y, speed) {
const length = Math.hypot(Number(x) || 0, Number(y) || 0);
return length ? { x: x / length * speed, y: y / length * speed } : { x: 0, y: 0 };
}
function approachVelocity(currentX, currentY, inputX, inputY, deltaMs, boots) {
const target = normalizedVector(inputX, inputY, 176);
const moving = Boolean(inputX || inputY);
const rate = moving ? (boots ? 1900 : 1500) : 2300;
const change = Math.min(50, Math.max(0, Number(deltaMs) || 0)) / 1000 * rate;
function approach(value, goal) {
return Math.abs(goal - value) <= change ? goal : value + Math.sign(goal - value) * change;
}
const velocity = { x: approach(currentX || 0, target.x), y: approach(currentY || 0, target.y) };
const length = Math.hypot(velocity.x, velocity.y);
return length > 176 ? normalizedVector(velocity.x, velocity.y, 176) : velocity;
}
function pointInShape(x, y, shape, padding) {
const pad = Number(padding) || 0;
if (shape.shape === "circle") return Math.hypot(x - shape.x, y - shape.y) <= shape.radius + pad;
return x >= shape.x - pad && x <= shape.x + shape.width + pad
&& y >= shape.y - pad && y <= shape.y + shape.height + pad;
}
function activeObstacles(state, regionId) {
const map = Data.MAPS[regionId];
if (!map) return [];
return map.obstacles.concat((map.dynamicObstacles || []).filter((item) => !state.unlockedRoutes.includes(item.opensWith)));
}
function isSafePosition(state, regionId, x, y) {
if (!Data.MAPS[regionId] || x < 30 || y < 30 || x > Data.WIDTH - 30 || y > Data.HEIGHT - 30) return false;
return !activeObstacles(state, regionId).some((shape) => pointInShape(x, y, shape, 14));
}
function nearestSafeSpawn(state, regionId, x, y) {
const map = Data.MAPS[regionId] || Data.MAPS.village;
if (isSafePosition(state, regionId, x, y)) return { region: regionId, spawn: "saved", x, y };
const candidates = Object.entries(map.spawns).filter(([, point]) => isSafePosition(state, regionId, point.x, point.y));
const best = candidates.sort((a, b) =>
Math.hypot(a[1].x - x, a[1].y - y) - Math.hypot(b[1].x - x, b[1].y - y))[0];
return best ? { region: regionId, spawn: best[0], x: best[1].x, y: best[1].y }
: { region: "village", spawn: "start", x: State.START.x, y: State.START.y };
}
function quantity(state, id) {
const found = state.inventory.find((entry) => entry.id === id);
return found ? found.quantity : 0;
}
function addItem(state, id, amount) {
const next = copy(state);
const item = Data.ITEMS[id];
if (!next || !item) return next;
const cap = item.stack || 1;
const existing = next.inventory.find((entry) => entry.id === id);
if (existing) existing.quantity = Math.min(cap, existing.quantity + Math.max(1, Math.floor(amount || 1)));
else next.inventory.push({ id, quantity: Math.min(cap, Math.max(1, Math.floor(amount || 1))) });
return State.normalize(next);
}
function removeItem(state, id, amount) {
const next = copy(state);
const item = Data.ITEMS[id];
if (!next || !item || item.protected) return { state: next, removed: false };
const existing = next.inventory.find((entry) => entry.id === id);
if (!existing || existing.quantity < amount) return { state: next, removed: false };
existing.quantity -= amount;
next.inventory = next.inventory.filter((entry) => entry.quantity > 0);
return { state: State.normalize(next), removed: true };
}
function useItem(state, id) {
const item = Data.ITEMS[id];
const next = copy(state);
if (!next || !item || item.type !== "consumable" || quantity(next, id) < 1 || next.health >= next.maxHealth) {
return { state: next, used: false, amount: 0 };
}
const amount = Math.min(next.maxHealth - next.health, item.heal);
next.health += amount;
const removed = removeItem(next, id, 1);
return { state: removed.state, used: true, amount };
}
function startQuest(state, id) {
const next = copy(state);
if (!next || !Data.QUESTS[id]) return next;
if (next.quests[id].status === "locked") next.quests[id] = { status: "active", count: 0, rewarded: false };
return State.normalize(next);
}
function progressQuest(state, id, amount) {
let next = startQuest(state, id);
if (!next || next.quests[id].status === "complete") return next;
next.quests[id].count = Math.min(Data.QUESTS[id].target, next.quests[id].count + Math.max(1, amount || 1));
return next.quests[id].count >= Data.QUESTS[id].target ? completeQuest(next, id) : State.normalize(next);
}
function applyQuestConsequences(state, id) {
const routes = {
village_defence: "village_defended", find_guide: "guide_found", ruins_light: "ruins_complete",
wolf_miniboss: "briar_defeated", repair_bridge: "bridge_repaired",
stone_guardian: "guardian_defeated", free_scout: "emblem_found",
break_wards: "wards_broken", defeat_malrec: "malrec_defeated"
};
if (routes[id] && !state.unlockedRoutes.includes(routes[id])) state.unlockedRoutes.push(routes[id]);
if (id === "aftermath") state.flags.aftermath_complete = true;
if (id === "find_guide") state.chapter = Math.max(state.chapter, 2);
if (id === "repair_bridge") state.chapter = Math.max(state.chapter, 3);
if (id === "free_scout") state.chapter = Math.max(state.chapter, 4);
if (id === "defeat_malrec") state.story = "rescue";
return state;
}
function completeQuest(state, id) {
let next = startQuest(state, id);
if (!next || next.quests[id].status === "complete") return next;
next.quests[id].status = "complete";
if (!next.quests[id].rewarded) {
(Data.QUESTS[id].reward || []).forEach(([itemId, amount]) => { next = addItem(next, itemId, amount); });
next.quests[id].rewarded = true;
}
next = applyQuestConsequences(next, id);
return State.normalize(next);
}
function solvePuzzle(state, id) {
const next = copy(state);
if (!next || next.solvedPuzzles.includes(id)) return next;
next.solvedPuzzles.push(id);
if (id === "forest_stones") return completeQuest(next, "find_guide");
if (id === "ruin_braziers") return completeQuest(next, "ruins_light");
if (id === "bridge_winches") return completeQuest(next, "repair_bridge");
if (id === "shadow_wards") return completeQuest(next, "break_wards");
return State.normalize(next);
}
function damage(state, amount, now, lastHitAt) {
const next = copy(state);
if (!next || Number(now) - Number(lastHitAt || 0) < 850) return { state: next, hit: false, defeated: false };
const dealt = Math.max(1, Math.round(amount - next.defence));
next.health = Math.max(0, next.health - dealt);
return { state: State.normalize(next), hit: true, defeated: next.health <= 0, amount: dealt };
}
function bossPhase(health, maximum, phases) {
const count = Math.max(1, Math.floor(Number(phases) || 1));
const ratio = Math.max(0, Number(health) || 0) / Math.max(1, Number(maximum) || 1);
if (count === 1) return 1;
if (count === 2) return ratio > 0.5 ? 1 : 2;
return ratio > 0.66 ? 1 : ratio > 0.33 ? 2 : 3;
}
function respawn(state) {
let next = copy(state);
if (!next) return next;
const safe = nearestSafeSpawn(next, next.checkpoint.region, next.checkpoint.x, next.checkpoint.y);
next.health = Math.max(50, Math.ceil(next.maxHealth * 0.65));
next.region = safe.region;
next.position = { spawn: safe.spawn, x: safe.x, y: safe.y, facing: "south" };
return State.normalize(next);
}
function recordBoss(state, id) {
let next = copy(state);
if (!next || !["briar_wolf", "stone_guardian", "captain", "malrec"].includes(id)) return next;
if (!next.defeatedBosses.includes(id)) next.defeatedBosses.push(id);
if (id === "briar_wolf") next = completeQuest(next, "wolf_miniboss");
if (id === "stone_guardian") next = completeQuest(next, "stone_guardian");
if (id === "captain") next = progressQuest(next, "free_scout", 1);
if (id === "malrec") next = completeQuest(next, "defeat_malrec");
return State.normalize(next);
}
function currentObjective(state) {
if (state.rescued) return "Princess Lima is safe. The road home is open.";
const activeMain = Data.QUEST_IDS.find((id) => Data.QUESTS[id].main && state.quests[id].status === "active");
if (activeMain) return Data.QUESTS[activeMain].description;
if (!state.flags.aftermath_complete) return "Speak with Elder Corin in the village square.";
if (!state.unlockedRoutes.includes("village_defended")) return "Defend the village from the second raid.";
if (!state.unlockedRoutes.includes("guide_found")) return "Find Guide Tovin in the Whispering Woods.";
if (!state.unlockedRoutes.includes("ruins_complete")) return "Explore the Sunken Ruins.";
if (!state.unlockedRoutes.includes("briar_defeated")) return "Defeat the Briar Wolf and open the mountain trail.";
if (!state.unlockedRoutes.includes("bridge_repaired")) return "Repair the bridge across the Mountain Pass.";
if (!state.unlockedRoutes.includes("guardian_defeated")) return "Defeat the Stone Guardian.";
if (!state.unlockedRoutes.includes("emblem_found")) return "Free Scout Elowen at Blackridge Camp.";
if (!state.unlockedRoutes.includes("wards_broken")) return "Break the three wards inside the fortress.";
if (!state.unlockedRoutes.includes("malrec_defeated")) return "Confront Lord Malrec in the throne room.";
return "Find Princess Lima beyond the throne room.";
}
return Object.freeze({
normalizedVector, approachVelocity, pointInShape, activeObstacles, isSafePosition, nearestSafeSpawn,
quantity, addItem, removeItem, useItem, startQuest, progressQuest, completeQuest, solvePuzzle,
damage, bossPhase, respawn, recordBoss, currentObjective
});
}));