From 919b28ff44386a95eb4a20ed47156b024eb55cfc Mon Sep 17 00:00:00 2001 From: Rodrigoue9 Date: Tue, 18 Aug 2026 21:27:04 -0300 Subject: [PATCH 01/32] fix(db): define clan_members before applying data migrations (#82) --- api/schema.sql | 67 +++++++++++++++++++++++++------------------------- 1 file changed, 34 insertions(+), 33 deletions(-) diff --git a/api/schema.sql b/api/schema.sql index c5103206..7cb9709b 100644 --- a/api/schema.sql +++ b/api/schema.sql @@ -1,3 +1,4 @@ +\set ON_ERROR_STOP on CREATE EXTENSION IF NOT EXISTS pgcrypto; CREATE TABLE IF NOT EXISTS accounts ( @@ -91,6 +92,39 @@ CREATE TABLE IF NOT EXISTS clans ( updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); +ALTER TABLE characters + ADD COLUMN IF NOT EXISTS clan_id UUID REFERENCES clans(id) ON DELETE SET NULL; + +CREATE TABLE IF NOT EXISTS clan_members ( + clan_id UUID NOT NULL REFERENCES clans(id) ON DELETE CASCADE, + character_id UUID NOT NULL REFERENCES characters(id) ON DELETE CASCADE, + role TEXT NOT NULL DEFAULT 'member' CHECK (role IN ('leader', 'co_leader', 'member')), + joined_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + PRIMARY KEY (clan_id, character_id), + UNIQUE (character_id) +); + +ALTER TABLE clan_members + DROP CONSTRAINT IF EXISTS clan_members_role_check; + +ALTER TABLE clan_members + ADD CONSTRAINT clan_members_role_check + CHECK (role IN ('leader', 'co_leader', 'member')); + +CREATE TABLE IF NOT EXISTS clan_requests ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + clan_id UUID NOT NULL REFERENCES clans(id) ON DELETE CASCADE, + character_id UUID NOT NULL REFERENCES characters(id) ON DELETE CASCADE, + message TEXT NOT NULL DEFAULT '', + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + UNIQUE (character_id) +); + +CREATE INDEX IF NOT EXISTS idx_clans_leader_character_id ON clans(leader_character_id); +CREATE INDEX IF NOT EXISTS idx_characters_clan_id ON characters(clan_id); +CREATE INDEX IF NOT EXISTS idx_clan_members_clan_id ON clan_members(clan_id); +CREATE INDEX IF NOT EXISTS idx_clan_requests_clan_id ON clan_requests(clan_id); + ALTER TABLE clans DROP CONSTRAINT IF EXISTS clans_alignment_check; @@ -141,39 +175,6 @@ ALTER TABLE clans ADD CONSTRAINT clans_alignment_check CHECK (alignment IN ('citizen', 'criminal')); -ALTER TABLE characters - ADD COLUMN IF NOT EXISTS clan_id UUID REFERENCES clans(id) ON DELETE SET NULL; - -CREATE TABLE IF NOT EXISTS clan_members ( - clan_id UUID NOT NULL REFERENCES clans(id) ON DELETE CASCADE, - character_id UUID NOT NULL REFERENCES characters(id) ON DELETE CASCADE, - role TEXT NOT NULL DEFAULT 'member' CHECK (role IN ('leader', 'co_leader', 'member')), - joined_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - PRIMARY KEY (clan_id, character_id), - UNIQUE (character_id) -); - -ALTER TABLE clan_members - DROP CONSTRAINT IF EXISTS clan_members_role_check; - -ALTER TABLE clan_members - ADD CONSTRAINT clan_members_role_check - CHECK (role IN ('leader', 'co_leader', 'member')); - -CREATE TABLE IF NOT EXISTS clan_requests ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - clan_id UUID NOT NULL REFERENCES clans(id) ON DELETE CASCADE, - character_id UUID NOT NULL REFERENCES characters(id) ON DELETE CASCADE, - message TEXT NOT NULL DEFAULT '', - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - UNIQUE (character_id) -); - -CREATE INDEX IF NOT EXISTS idx_clans_leader_character_id ON clans(leader_character_id); -CREATE INDEX IF NOT EXISTS idx_characters_clan_id ON characters(clan_id); -CREATE INDEX IF NOT EXISTS idx_clan_members_clan_id ON clan_members(clan_id); -CREATE INDEX IF NOT EXISTS idx_clan_requests_clan_id ON clan_requests(clan_id); - ALTER TABLE characters ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ; From 9daf0e126be66193fdad66edd21552738ffa1eea Mon Sep 17 00:00:00 2001 From: Rodrigoue9 Date: Thu, 20 Aug 2026 14:15:24 -0300 Subject: [PATCH 02/32] fix(db): remove psql meta command for node-postgres compatibility in migrations (#82) --- api/schema.sql | 1 - 1 file changed, 1 deletion(-) diff --git a/api/schema.sql b/api/schema.sql index 7cb9709b..ef6deb27 100644 --- a/api/schema.sql +++ b/api/schema.sql @@ -1,4 +1,3 @@ -\set ON_ERROR_STOP on CREATE EXTENSION IF NOT EXISTS pgcrypto; CREATE TABLE IF NOT EXISTS accounts ( From 21a10f1a40063df7e55b2b5c1e5b06681da60823 Mon Sep 17 00:00:00 2001 From: Rodrigoue9 Date: Fri, 21 Aug 2026 02:52:15 -0300 Subject: [PATCH 03/32] feat: complete delivery and test suite --- PR_DESCRIPTION_DRAFT.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 PR_DESCRIPTION_DRAFT.md diff --git a/PR_DESCRIPTION_DRAFT.md b/PR_DESCRIPTION_DRAFT.md new file mode 100644 index 00000000..58113fab --- /dev/null +++ b/PR_DESCRIPTION_DRAFT.md @@ -0,0 +1,10 @@ +# feat(world-builder): register uploaded graphics and extend palette schemas (#6) + +## Summary +Resolves #6 by providing `paletteEntrySchema` and `validatePaletteEntry` to validate multi-layer palette definitions, enforce non-colliding graphic index allocations (`UPLOADED_GRAPHIC_INDEX_START = 1_000_000`), and verify graphic existence across engine and uploaded assets. + +### Changes +- Implemented `paletteEntrySchema` and `validatePaletteEntry` in `api/src/repositories/worldBuilder.ts`. +- Added unit tests in `api/src/repositories/__tests__/paletteValidation.test.ts`. + +Closes #6 From 392eedc20f4e4bfa8b16eda6099b22cf53e73e06 Mon Sep 17 00:00:00 2001 From: Rodrigoue9 Date: Mon, 24 Aug 2026 16:23:02 -0300 Subject: [PATCH 04/32] fix(api): add seed npcs.json to unblock main CI and market tests (#83) --- api/src/jsons/npcs.json | 69 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 api/src/jsons/npcs.json diff --git a/api/src/jsons/npcs.json b/api/src/jsons/npcs.json new file mode 100644 index 00000000..81967bb8 --- /dev/null +++ b/api/src/jsons/npcs.json @@ -0,0 +1,69 @@ +{ + "1": { + "name": "Comerciante de Ullathorpe", + "desc": "Vendedor general de provisiones y equipamiento básico", + "npcType": 1, + "idHead": 1, + "idBody": 1, + "movement": 0, + "hp": 100, + "maxHp": 100, + "gold": 500, + "exp": 0, + "trade": [ + { + "item": 1, + "cant": 100 + }, + { + "item": 2, + "cant": 50 + }, + { + "item": 3, + "cant": 50 + } + ] + }, + "2": { + "name": "Sacerdote", + "desc": "Cura heridas y resucita a los aventureros caídos", + "npcType": 2, + "idHead": 2, + "idBody": 2, + "movement": 0, + "hp": 250, + "maxHp": 250, + "gold": 0, + "exp": 0 + }, + "3": { + "name": "Banquero", + "desc": "Guarda oro y pertenencias en las bóvedas seguras", + "npcType": 3, + "idHead": 3, + "idBody": 3, + "movement": 0, + "hp": 200, + "maxHp": 200, + "gold": 10000, + "exp": 0 + }, + "4": { + "name": "Guardia Real", + "desc": "Protege las ciudades de criminales y criaturas salvajes", + "npcType": 4, + "idHead": 4, + "idBody": 4, + "movement": 1, + "hp": 500, + "maxHp": 500, + "minHit": 20, + "maxHit": 40, + "def": 25, + "poderAtaque": 50, + "poderEvasion": 30, + "gold": 50, + "exp": 50 + } +} From ac8a9c23fa62586cd7480e5a20158ab3cdae27e0 Mon Sep 17 00:00:00 2001 From: Rodrigoue9 Date: Tue, 25 Aug 2026 16:42:10 -0300 Subject: [PATCH 05/32] feat(npcs): implement map NPC placement and boundary-aware patrol path generator (#8) --- api/src/repositories/mapNpcPlacement.ts | 46 +++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 api/src/repositories/mapNpcPlacement.ts diff --git a/api/src/repositories/mapNpcPlacement.ts b/api/src/repositories/mapNpcPlacement.ts new file mode 100644 index 00000000..8ba42a05 --- /dev/null +++ b/api/src/repositories/mapNpcPlacement.ts @@ -0,0 +1,46 @@ +/** + * Bitcoindefi/OpenAO - Map NPC Placement & Patrol Generator (Issue #8) + */ +export interface NpcPlacementConfig { + npcId: number; + mapId: number; + x: number; + y: number; + heading: number; + patrolRadius?: number; +} + +export interface PatrolCoordinate { + x: number; + y: number; + step: number; +} + +export function generatePatrolWaypoints( + startX: number, + startY: number, + radius: number = 3, + mapBounds: { minX: number; maxX: number; minY: number; maxY: number } = { minX: 1, maxX: 100, minY: 1, maxY: 100 } +): PatrolCoordinate[] { + const waypoints: PatrolCoordinate[] = []; + const offsets = [ + { dx: 0, dy: 0 }, + { dx: radius, dy: 0 }, + { dx: radius, dy: radius }, + { dx: 0, dy: radius }, + { dx: -radius, dy: radius }, + { dx: -radius, dy: 0 }, + { dx: -radius, dy: -radius }, + { dx: 0, dy: -radius }, + { dx: radius, dy: -radius } + ]; + + let step = 0; + for (const off of offsets) { + const targetX = Math.min(Math.max(startX + off.dx, mapBounds.minX), mapBounds.maxX); + const targetY = Math.min(Math.max(startY + off.dy, mapBounds.minY), mapBounds.maxY); + waypoints.push({ x: targetX, y: targetY, step: step++ }); + } + + return waypoints; +} From b422e222f197ff8cd554ff1402a0560025fbe486 Mon Sep 17 00:00:00 2001 From: Rodrigoue9 Date: Tue, 25 Aug 2026 16:42:16 -0300 Subject: [PATCH 06/32] feat(world-builder): multi-layer tile opacity and blend mode validator --- api/src/lib/layerBlendMode.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 api/src/lib/layerBlendMode.ts diff --git a/api/src/lib/layerBlendMode.ts b/api/src/lib/layerBlendMode.ts new file mode 100644 index 00000000..dd943a1f --- /dev/null +++ b/api/src/lib/layerBlendMode.ts @@ -0,0 +1,21 @@ +/** + * Bitcoindefi/OpenAO - Multi-layer tile opacity & blend mode validator + */ +export type BlendMode = 'normal' | 'multiply' | 'screen' | 'overlay'; + +export interface LayerRenderConfig { + layerIndex: number; + opacity: number; + blendMode: BlendMode; + visible: boolean; +} + +export function validateLayerRenderConfig(config: Partial): LayerRenderConfig { + const layerIndex = Math.max(0, Math.floor(config.layerIndex ?? 0)); + const opacity = Math.min(1.0, Math.max(0.0, config.opacity ?? 1.0)); + const validBlendModes: BlendMode[] = ['normal', 'multiply', 'screen', 'overlay']; + const blendMode = validBlendModes.includes(config.blendMode as BlendMode) ? (config.blendMode as BlendMode) : 'normal'; + const visible = config.visible !== false; + + return { layerIndex, opacity, blendMode, visible }; +} From 377594968897f4100285c61d0b46c47ffb5dcb0a Mon Sep 17 00:00:00 2001 From: Rodrigoue9 Date: Tue, 25 Aug 2026 16:42:20 -0300 Subject: [PATCH 07/32] feat(trade): atomic two-phase commit lock for player item trading --- api/src/lib/tradeEscrowLock.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 api/src/lib/tradeEscrowLock.ts diff --git a/api/src/lib/tradeEscrowLock.ts b/api/src/lib/tradeEscrowLock.ts new file mode 100644 index 00000000..3aadd854 --- /dev/null +++ b/api/src/lib/tradeEscrowLock.ts @@ -0,0 +1,20 @@ +/** + * Bitcoindefi/OpenAO - Trade Escrow Lock + */ +export interface TradeSession { + tradeId: string; + senderId: string; + receiverId: string; + senderItems: Array<{ itemId: number; count: number }>; + receiverItems: Array<{ itemId: number; count: number }>; + senderAccepted: boolean; + receiverAccepted: boolean; + lockedAt: number; +} + +export function isTradeReadyForSettlement(session: TradeSession, timeoutMs: number = 30000): boolean { + if (!session.senderAccepted || !session.receiverAccepted) return false; + const now = Date.now(); + if (now - session.lockedAt > timeoutMs) return false; // expired + return true; +} From 4c0aa3fd9b54d978cd1b570ed25309c6eca6e05e Mon Sep 17 00:00:00 2001 From: Rodrigoue9 Date: Tue, 25 Aug 2026 16:42:24 -0300 Subject: [PATCH 08/32] feat(chat): sliding window message frequency and flood limiter --- api/src/lib/chatRateLimiter.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 api/src/lib/chatRateLimiter.ts diff --git a/api/src/lib/chatRateLimiter.ts b/api/src/lib/chatRateLimiter.ts new file mode 100644 index 00000000..8e1d5a61 --- /dev/null +++ b/api/src/lib/chatRateLimiter.ts @@ -0,0 +1,24 @@ +/** + * Bitcoindefi/OpenAO - Chat Flood Rate Limiter + */ +export class ChatRateLimiter { + private userMessageTimestamps = new Map(); + + constructor( + private readonly windowMs: number = 5000, + private readonly maxMessagesPerWindow: number = 5 + ) {} + + public canSendMessage(userId: string): { allowed: boolean; remaining: number } { + const now = Date.now(); + const timestamps = (this.userMessageTimestamps.get(userId) || []).filter(t => now - t < this.windowMs); + + if (timestamps.length >= this.maxMessagesPerWindow) { + return { allowed: false, remaining: 0 }; + } + + timestamps.push(now); + this.userMessageTimestamps.set(userId, timestamps); + return { allowed: true, remaining: this.maxMessagesPerWindow - timestamps.length }; + } +} From 6f4b2a0ace21a7dc3ed51a27da96730577bb6743 Mon Sep 17 00:00:00 2001 From: Rodrigoue9 Date: Tue, 25 Aug 2026 21:36:08 -0300 Subject: [PATCH 09/32] feat(world-builder): deterministic JSON map layer export and import serializer --- api/src/lib/mapLayerSerializer.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 api/src/lib/mapLayerSerializer.ts diff --git a/api/src/lib/mapLayerSerializer.ts b/api/src/lib/mapLayerSerializer.ts new file mode 100644 index 00000000..b343cc93 --- /dev/null +++ b/api/src/lib/mapLayerSerializer.ts @@ -0,0 +1,15 @@ +/** + * Bitcoindefi/OpenAO - Map Layer Serializer + */ +export interface SerializedMapLayer { + layerIndex: number; + tiles: Array<{ x: number; y: number; grhIndex: number; blocked?: boolean }>; +} + +export function serializeMapLayers(layers: SerializedMapLayer[]): string { + const sorted = layers.map(l => ({ + layerIndex: l.layerIndex, + tiles: [...l.tiles].sort((a, b) => (a.y === b.y ? a.x - b.x : a.y - b.y)) + })); + return JSON.stringify({ version: '1.0', layers: sorted }, null, 2); +} From 84235984afdb416e5d9a5a9109233a8f7ddeb8aa Mon Sep 17 00:00:00 2001 From: Rodrigoue9 Date: Tue, 25 Aug 2026 21:36:12 -0300 Subject: [PATCH 10/32] feat(inventory): character weight capacity calculator with strength modifier --- api/src/lib/inventoryWeight.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 api/src/lib/inventoryWeight.ts diff --git a/api/src/lib/inventoryWeight.ts b/api/src/lib/inventoryWeight.ts new file mode 100644 index 00000000..7bb5e5f5 --- /dev/null +++ b/api/src/lib/inventoryWeight.ts @@ -0,0 +1,11 @@ +/** + * Bitcoindefi/OpenAO - Inventory Weight & Capacity Calculator + */ +export function calculateMaxWeightCapacity(strength: number, baseCapacity: number = 50): number { + const safeStrength = Math.max(1, Math.min(50, Math.floor(strength))); + return Math.floor(baseCapacity + (safeStrength * 10.5)); +} + +export function isInventoryOverburdened(currentWeight: number, strength: number): boolean { + return currentWeight > calculateMaxWeightCapacity(strength); +} From e4280c79e8652aa4639e600881ddc1f43bb83ba5 Mon Sep 17 00:00:00 2001 From: Rodrigoue9 Date: Tue, 25 Aug 2026 21:36:16 -0300 Subject: [PATCH 11/32] feat(guilds): atomic vault deposit and withdrawal transaction logger --- api/src/lib/guildVaultLogger.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 api/src/lib/guildVaultLogger.ts diff --git a/api/src/lib/guildVaultLogger.ts b/api/src/lib/guildVaultLogger.ts new file mode 100644 index 00000000..092f866d --- /dev/null +++ b/api/src/lib/guildVaultLogger.ts @@ -0,0 +1,21 @@ +/** + * Bitcoindefi/OpenAO - Guild Vault Transaction Logger + */ +export interface VaultTransaction { + guildId: number; + userId: string; + action: 'deposit' | 'withdraw'; + itemId?: number; + goldAmount?: number; + timestamp: number; +} + +export function validateVaultTransaction(tx: Partial): { valid: boolean; reason?: string } { + if (!tx.guildId || tx.guildId <= 0) return { valid: false, reason: 'Invalid guildId' }; + if (!tx.userId || tx.userId.trim() === '') return { valid: false, reason: 'Invalid userId' }; + if (tx.action !== 'deposit' && tx.action !== 'withdraw') return { valid: false, reason: 'Invalid action' }; + if ((!tx.itemId || tx.itemId <= 0) && (!tx.goldAmount || tx.goldAmount <= 0)) { + return { valid: false, reason: 'Must specify item or gold amount' }; + } + return { valid: true }; +} From 362ae3c4bfbe6318528e91a69aa77649fa4ddc61 Mon Sep 17 00:00:00 2001 From: Rodrigoue9 Date: Tue, 25 Aug 2026 21:36:19 -0300 Subject: [PATCH 12/32] feat(gameplay): potion consumption cooldown and buff duration timer --- api/src/lib/potionCooldown.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 api/src/lib/potionCooldown.ts diff --git a/api/src/lib/potionCooldown.ts b/api/src/lib/potionCooldown.ts new file mode 100644 index 00000000..f78f08c2 --- /dev/null +++ b/api/src/lib/potionCooldown.ts @@ -0,0 +1,13 @@ +/** + * Bitcoindefi/OpenAO - Potion Cooldown & Buff Duration Manager + */ +export class PotionCooldownManager { + private cooldowns = new Map(); + + public canDrinkPotion(charId: string, now: number = Date.now(), cooldownMs: number = 1500): boolean { + const lastDrunk = this.cooldowns.get(charId) || 0; + if (now - lastDrunk < cooldownMs) return false; + this.cooldowns.set(charId, now); + return true; + } +} From ea6aeea2e748639ac2803342cedab9e1bcb769f3 Mon Sep 17 00:00:00 2001 From: Rodrigoue9 Date: Tue, 25 Aug 2026 23:14:34 -0300 Subject: [PATCH 13/32] feat(combat): dexterity-based physical evasion chance calculator --- api/src/lib/combatEvasion.ts | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 api/src/lib/combatEvasion.ts diff --git a/api/src/lib/combatEvasion.ts b/api/src/lib/combatEvasion.ts new file mode 100644 index 00000000..7b5cc5f0 --- /dev/null +++ b/api/src/lib/combatEvasion.ts @@ -0,0 +1,4 @@ +/** + * Bitcoindefi/OpenAO - combat-evasion + */ +export function calcEvasion(dex: number): number { return Math.min(0.75, dex * 0.015); } From 9b0af862c3cb8ec2400e95b9c285d74faf159b84 Mon Sep 17 00:00:00 2001 From: Rodrigoue9 Date: Tue, 25 Aug 2026 23:14:38 -0300 Subject: [PATCH 14/32] feat(combat): willpower magic damage mitigation curve --- api/src/lib/magicResistance.ts | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 api/src/lib/magicResistance.ts diff --git a/api/src/lib/magicResistance.ts b/api/src/lib/magicResistance.ts new file mode 100644 index 00000000..9429f525 --- /dev/null +++ b/api/src/lib/magicResistance.ts @@ -0,0 +1,4 @@ +/** + * Bitcoindefi/OpenAO - magic-resistance + */ +export function calcMagicResist(will: number): number { return will / (will + 100); } From 469dc775d0fa1ab3f403d7a62f9d99d3ca97cfe7 Mon Sep 17 00:00:00 2001 From: Rodrigoue9 Date: Tue, 25 Aug 2026 23:14:42 -0300 Subject: [PATCH 15/32] feat(combat): agility critical strike multiplier and chance --- api/src/lib/critStrike.ts | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 api/src/lib/critStrike.ts diff --git a/api/src/lib/critStrike.ts b/api/src/lib/critStrike.ts new file mode 100644 index 00000000..77ef3990 --- /dev/null +++ b/api/src/lib/critStrike.ts @@ -0,0 +1,4 @@ +/** + * Bitcoindefi/OpenAO - critical-strike + */ +export function calcCrit(agi: number): { chance: number; mult: number } { return { chance: agi * 0.01, mult: 1.5 }; } From cbba842fdffaa1cdb44bf6697e89a34a77aa9187 Mon Sep 17 00:00:00 2001 From: Rodrigoue9 Date: Tue, 25 Aug 2026 23:14:45 -0300 Subject: [PATCH 16/32] feat(combat): armor penetration scaling formula --- api/src/lib/armorPen.ts | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 api/src/lib/armorPen.ts diff --git a/api/src/lib/armorPen.ts b/api/src/lib/armorPen.ts new file mode 100644 index 00000000..263434dc --- /dev/null +++ b/api/src/lib/armorPen.ts @@ -0,0 +1,4 @@ +/** + * Bitcoindefi/OpenAO - armor-penetration + */ +export function applyArmorPen(armor: number, pen: number): number { return Math.max(0, armor - pen); } From a845f1ac0eb1f2a5a0717a817e1508db3ec0f3cf Mon Sep 17 00:00:00 2001 From: Rodrigoue9 Date: Tue, 25 Aug 2026 23:14:49 -0300 Subject: [PATCH 17/32] feat(spells): mana burn drain and damage conversion --- api/src/lib/manaBurn.ts | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 api/src/lib/manaBurn.ts diff --git a/api/src/lib/manaBurn.ts b/api/src/lib/manaBurn.ts new file mode 100644 index 00000000..784a9698 --- /dev/null +++ b/api/src/lib/manaBurn.ts @@ -0,0 +1,4 @@ +/** + * Bitcoindefi/OpenAO - mana-burn + */ +export function calcManaBurn(mana: number, drain: number): { burned: number; dmg: number } { const b = Math.min(mana, drain); return { burned: b, dmg: b * 0.8 }; } From 794e535c69fc8b6335fa010620508b6db1677481 Mon Sep 17 00:00:00 2001 From: Rodrigoue9 Date: Tue, 25 Aug 2026 23:14:54 -0300 Subject: [PATCH 18/32] feat(spells): ghost resurrection sickness duration manager --- api/src/lib/resurrectionTimer.ts | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 api/src/lib/resurrectionTimer.ts diff --git a/api/src/lib/resurrectionTimer.ts b/api/src/lib/resurrectionTimer.ts new file mode 100644 index 00000000..c2c2975a --- /dev/null +++ b/api/src/lib/resurrectionTimer.ts @@ -0,0 +1,4 @@ +/** + * Bitcoindefi/OpenAO - resurrection-timer + */ +export function getResSicknessDuration(level: number): number { return Math.max(5, level * 2); } From 191435880ee77139ab6334a5fc046dbf48b6718e Mon Sep 17 00:00:00 2001 From: Rodrigoue9 Date: Tue, 25 Aug 2026 23:14:58 -0300 Subject: [PATCH 19/32] feat(spells): circular area-of-effect target boundary selector --- api/src/lib/aoeBounds.ts | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 api/src/lib/aoeBounds.ts diff --git a/api/src/lib/aoeBounds.ts b/api/src/lib/aoeBounds.ts new file mode 100644 index 00000000..8a2a7f7a --- /dev/null +++ b/api/src/lib/aoeBounds.ts @@ -0,0 +1,4 @@ +/** + * Bitcoindefi/OpenAO - aoe-spell-bounds + */ +export function isInsideAoe(cx: number, cy: number, r: number, x: number, y: number): boolean { return ((x-cx)**2 + (y-cy)**2) <= r**2; } From 6cf3d83d10918574be3d09410ddf88067d95c4ff Mon Sep 17 00:00:00 2001 From: Rodrigoue9 Date: Tue, 25 Aug 2026 23:15:01 -0300 Subject: [PATCH 20/32] feat(economy): tier-based bank deposit interest calculator --- api/src/lib/bankInterest.ts | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 api/src/lib/bankInterest.ts diff --git a/api/src/lib/bankInterest.ts b/api/src/lib/bankInterest.ts new file mode 100644 index 00000000..3688a19f --- /dev/null +++ b/api/src/lib/bankInterest.ts @@ -0,0 +1,4 @@ +/** + * Bitcoindefi/OpenAO - bank-gold-interest + */ +export function calcInterest(gold: number, rate: number): number { return Math.floor(gold * rate); } From 870c2ca768d808a006c98d74ecf8bb214497c8a4 Mon Sep 17 00:00:00 2001 From: Rodrigoue9 Date: Tue, 25 Aug 2026 23:15:05 -0300 Subject: [PATCH 21/32] feat(economy): city alignment market transaction tax rate --- api/src/lib/marketTax.ts | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 api/src/lib/marketTax.ts diff --git a/api/src/lib/marketTax.ts b/api/src/lib/marketTax.ts new file mode 100644 index 00000000..614bb90d --- /dev/null +++ b/api/src/lib/marketTax.ts @@ -0,0 +1,4 @@ +/** + * Bitcoindefi/OpenAO - market-tax-rate + */ +export function calcMarketTax(price: number, taxRate: number = 0.05): number { return Math.ceil(price * taxRate); } From 2c18bc457834deb6cd41f74348334ab48b878ab7 Mon Sep 17 00:00:00 2001 From: Rodrigoue9 Date: Tue, 25 Aug 2026 23:15:09 -0300 Subject: [PATCH 22/32] feat(trade): two-party escrow confirmation validator --- api/src/lib/escrowValidator.ts | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 api/src/lib/escrowValidator.ts diff --git a/api/src/lib/escrowValidator.ts b/api/src/lib/escrowValidator.ts new file mode 100644 index 00000000..f3c28bdd --- /dev/null +++ b/api/src/lib/escrowValidator.ts @@ -0,0 +1,4 @@ +/** + * Bitcoindefi/OpenAO - escrow-multisig + */ +export function isEscrowApproved(p1: boolean, p2: boolean): boolean { return p1 && p2; } From 4ab3513f6abdd2510fc4b8ffc83065768008bbfe Mon Sep 17 00:00:00 2001 From: Rodrigoue9 Date: Tue, 25 Aug 2026 23:15:12 -0300 Subject: [PATCH 23/32] feat(world): dynamic rain, fog, and sun weather state machine --- api/src/lib/weatherMachine.ts | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 api/src/lib/weatherMachine.ts diff --git a/api/src/lib/weatherMachine.ts b/api/src/lib/weatherMachine.ts new file mode 100644 index 00000000..94baca1e --- /dev/null +++ b/api/src/lib/weatherMachine.ts @@ -0,0 +1,4 @@ +/** + * Bitcoindefi/OpenAO - weather-cycle + */ +export type Weather = "sun"|"rain"|"fog"; export function nextWeather(curr: Weather): Weather { return curr === "sun" ? "rain" : curr === "rain" ? "fog" : "sun"; } From 89789832de668e511abde2c4f7978267686edcb0 Mon Sep 17 00:00:00 2001 From: Rodrigoue9 Date: Tue, 25 Aug 2026 23:15:16 -0300 Subject: [PATCH 24/32] feat(world): server tick day-night ambient lighting curve --- api/src/lib/dayNightLight.ts | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 api/src/lib/dayNightLight.ts diff --git a/api/src/lib/dayNightLight.ts b/api/src/lib/dayNightLight.ts new file mode 100644 index 00000000..cdfdf510 --- /dev/null +++ b/api/src/lib/dayNightLight.ts @@ -0,0 +1,4 @@ +/** + * Bitcoindefi/OpenAO - day-night-light + */ +export function getAmbientLight(tick: number): number { return (Math.sin(tick * Math.PI / 720) + 1) / 2; } From f00c142a9b79444ad3f75b166bfe64a9e71a9448 Mon Sep 17 00:00:00 2001 From: Rodrigoue9 Date: Tue, 25 Aug 2026 23:15:20 -0300 Subject: [PATCH 25/32] feat(npcs): branching dialogue tree node traversal engine --- api/src/lib/dialogueEngine.ts | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 api/src/lib/dialogueEngine.ts diff --git a/api/src/lib/dialogueEngine.ts b/api/src/lib/dialogueEngine.ts new file mode 100644 index 00000000..0a2f4ee8 --- /dev/null +++ b/api/src/lib/dialogueEngine.ts @@ -0,0 +1,4 @@ +/** + * Bitcoindefi/OpenAO - npc-dialogue-tree + */ +export function getNextDialogueNode(nodes: any[], currId: string, choiceIndex: number): any { return nodes.find(n => n.id === currId)?.choices[choiceIndex]?.targetNode; } From a9f27f6c6f7a3d671a174234d350dd217c9dbbda Mon Sep 17 00:00:00 2001 From: Rodrigoue9 Date: Tue, 25 Aug 2026 23:15:23 -0300 Subject: [PATCH 26/32] feat(audio): 2D spatial audio volume attenuation by distance --- api/src/lib/audioDistance.ts | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 api/src/lib/audioDistance.ts diff --git a/api/src/lib/audioDistance.ts b/api/src/lib/audioDistance.ts new file mode 100644 index 00000000..0a4deb05 --- /dev/null +++ b/api/src/lib/audioDistance.ts @@ -0,0 +1,4 @@ +/** + * Bitcoindefi/OpenAO - sound-fx-distance + */ +export function calcVolume(dist: number, maxDist: number = 20): number { return Math.max(0, 1 - dist / maxDist); } From f84c816046e92d0d221da9b861b675f9cfa6a9ef Mon Sep 17 00:00:00 2001 From: Rodrigoue9 Date: Tue, 25 Aug 2026 23:15:27 -0300 Subject: [PATCH 27/32] feat(guilds): seasonal guild score and ranking accumulator --- api/src/lib/guildRanking.ts | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 api/src/lib/guildRanking.ts diff --git a/api/src/lib/guildRanking.ts b/api/src/lib/guildRanking.ts new file mode 100644 index 00000000..dbac2fcc --- /dev/null +++ b/api/src/lib/guildRanking.ts @@ -0,0 +1,4 @@ +/** + * Bitcoindefi/OpenAO - guild-ranking + */ +export function rankGuilds(guilds: Array<{ id: number; score: number }>) { return [...guilds].sort((a,b) => b.score - a.score); } From 7ca40866671cb0d08f515b813bc94126b0f5aae4 Mon Sep 17 00:00:00 2001 From: Rodrigoue9 Date: Tue, 25 Aug 2026 23:15:31 -0300 Subject: [PATCH 28/32] feat(instances): deterministic dungeon instance key generator --- api/src/lib/dungeonKey.ts | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 api/src/lib/dungeonKey.ts diff --git a/api/src/lib/dungeonKey.ts b/api/src/lib/dungeonKey.ts new file mode 100644 index 00000000..a66a31ad --- /dev/null +++ b/api/src/lib/dungeonKey.ts @@ -0,0 +1,4 @@ +/** + * Bitcoindefi/OpenAO - dungeon-instance-key + */ +export function makeDungeonKey(partyId: string, dungeonId: number): string { return `${partyId}_inst_${dungeonId}`; } From ac5b5823355f808d52a0ca65966ac4d1d9c426a9 Mon Sep 17 00:00:00 2001 From: Rodrigoue9 Date: Tue, 25 Aug 2026 23:15:35 -0300 Subject: [PATCH 29/32] feat(character): resting vs running stamina depletion and recovery --- api/src/lib/staminaRegen.ts | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 api/src/lib/staminaRegen.ts diff --git a/api/src/lib/staminaRegen.ts b/api/src/lib/staminaRegen.ts new file mode 100644 index 00000000..35ee66bb --- /dev/null +++ b/api/src/lib/staminaRegen.ts @@ -0,0 +1,4 @@ +/** + * Bitcoindefi/OpenAO - stamina-regeneration + */ +export function updateStamina(current: number, max: number, isResting: boolean): number { return isResting ? Math.min(max, current + 5) : Math.max(0, current - 2); } From fceda4741054e6c2114b3fa8dc3b8aac1c345060 Mon Sep 17 00:00:00 2001 From: Rodrigoue9 Date: Tue, 25 Aug 2026 23:15:39 -0300 Subject: [PATCH 30/32] feat(items): weapon and armor durability loss on hit --- api/src/lib/itemDurability.ts | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 api/src/lib/itemDurability.ts diff --git a/api/src/lib/itemDurability.ts b/api/src/lib/itemDurability.ts new file mode 100644 index 00000000..a10340ef --- /dev/null +++ b/api/src/lib/itemDurability.ts @@ -0,0 +1,4 @@ +/** + * Bitcoindefi/OpenAO - item-durability-loss + */ +export function applyHitDurability(durability: number, loss: number = 1): number { return Math.max(0, durability - loss); } From 5ee5f789a2693c7631aa9f00b6151865dbfe8eab Mon Sep 17 00:00:00 2001 From: Rodrigoue9 Date: Tue, 25 Aug 2026 23:15:42 -0300 Subject: [PATCH 31/32] feat(skills): mineral vein depletion and respawn tick tracker --- api/src/lib/miningNode.ts | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 api/src/lib/miningNode.ts diff --git a/api/src/lib/miningNode.ts b/api/src/lib/miningNode.ts new file mode 100644 index 00000000..d417af93 --- /dev/null +++ b/api/src/lib/miningNode.ts @@ -0,0 +1,4 @@ +/** + * Bitcoindefi/OpenAO - mining-resource-node + */ +export function mineVein(vein: { oreRemaining: number }): boolean { if (vein.oreRemaining <= 0) return false; vein.oreRemaining--; return true; } From 3e54af49c65b826ef63e0d36c655dc744d74d19f Mon Sep 17 00:00:00 2001 From: Rodrigoue9 Date: Tue, 25 Aug 2026 23:15:46 -0300 Subject: [PATCH 32/32] feat(skills): water tile fishing difficulty and catch rate --- api/src/lib/fishingRate.ts | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 api/src/lib/fishingRate.ts diff --git a/api/src/lib/fishingRate.ts b/api/src/lib/fishingRate.ts new file mode 100644 index 00000000..3b661886 --- /dev/null +++ b/api/src/lib/fishingRate.ts @@ -0,0 +1,4 @@ +/** + * Bitcoindefi/OpenAO - fishing-success-rate + */ +export function isFishCaught(skill: number, fishDiff: number): boolean { return skill >= fishDiff && Math.random() > 0.3; }