-- Migration 000006: Quest system — towns, NPCs, quests, hero quest tracking. -- ============================================================ -- Towns: fixed settlements along the hero's travel road. -- ============================================================ CREATE TABLE IF NOT EXISTS towns ( id BIGSERIAL PRIMARY KEY, name TEXT NOT NULL UNIQUE, biome TEXT NOT NULL, world_x DOUBLE PRECISION NOT NULL, world_y DOUBLE PRECISION NOT NULL, radius DOUBLE PRECISION NOT NULL DEFAULT 8.0, level_min INT NOT NULL DEFAULT 1, level_max INT NOT NULL DEFAULT 100, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); -- ============================================================ -- NPCs: non-hostile characters in towns. -- ============================================================ CREATE TABLE IF NOT EXISTS npcs ( id BIGSERIAL PRIMARY KEY, town_id BIGINT NOT NULL REFERENCES towns(id) ON DELETE CASCADE, name TEXT NOT NULL, type TEXT NOT NULL CHECK (type IN ('quest_giver', 'merchant', 'healer')), offset_x DOUBLE PRECISION NOT NULL DEFAULT 0, offset_y DOUBLE PRECISION NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX IF NOT EXISTS idx_npcs_town ON npcs(town_id); -- ============================================================ -- Quests: template definitions offered by quest-giver NPCs. -- ============================================================ CREATE TABLE IF NOT EXISTS quests ( id BIGSERIAL PRIMARY KEY, npc_id BIGINT NOT NULL REFERENCES npcs(id) ON DELETE CASCADE, title TEXT NOT NULL, description TEXT NOT NULL DEFAULT '', type TEXT NOT NULL CHECK (type IN ('kill_count', 'visit_town', 'collect_item')), target_count INT NOT NULL DEFAULT 1, target_enemy_type TEXT, -- NULL = any enemy (for kill_count) target_town_id BIGINT REFERENCES towns(id), -- for visit_town quests drop_chance DOUBLE PRECISION NOT NULL DEFAULT 0.3, -- for collect_item min_level INT NOT NULL DEFAULT 1, max_level INT NOT NULL DEFAULT 100, reward_xp BIGINT NOT NULL DEFAULT 0, reward_gold BIGINT NOT NULL DEFAULT 0, reward_potions INT NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX IF NOT EXISTS idx_quests_npc ON quests(npc_id); -- ============================================================ -- Hero quests: per-hero progress tracking. -- ============================================================ CREATE TABLE IF NOT EXISTS hero_quests ( id BIGSERIAL PRIMARY KEY, hero_id BIGINT NOT NULL REFERENCES heroes(id) ON DELETE CASCADE, quest_id BIGINT NOT NULL REFERENCES quests(id) ON DELETE CASCADE, status TEXT NOT NULL DEFAULT 'accepted' CHECK (status IN ('accepted', 'completed', 'claimed')), progress INT NOT NULL DEFAULT 0, accepted_at TIMESTAMPTZ NOT NULL DEFAULT now(), completed_at TIMESTAMPTZ, claimed_at TIMESTAMPTZ, UNIQUE (hero_id, quest_id) ); CREATE INDEX IF NOT EXISTS idx_hero_quests_hero ON hero_quests(hero_id); CREATE INDEX IF NOT EXISTS idx_hero_quests_status ON hero_quests(hero_id, status); -- ============================================================ -- Seed data: towns -- ============================================================ INSERT INTO towns (name, biome, world_x, world_y, radius, level_min, level_max) VALUES ('Willowdale', 'meadow', 50, 15, 8.0, 1, 5), ('Thornwatch', 'forest', 200, 60, 8.0, 5, 10), ('Ashengard', 'ruins', 400, 120, 8.0, 10, 16), ('Redcliff', 'canyon', 650, 195, 8.0, 16, 22), ('Boghollow', 'swamp', 900, 270, 8.0, 22, 28), ('Cinderkeep', 'volcanic', 1200, 360, 8.0, 28, 34), ('Starfall', 'astral', 1550, 465, 8.0, 34, 40); -- ============================================================ -- Seed data: NPCs (2-3 per town) -- ============================================================ INSERT INTO npcs (town_id, name, type, offset_x, offset_y) VALUES -- Willowdale (meadow) (1, 'Elder Maren', 'quest_giver', -2.0, 1.0), (1, 'Peddler Finn', 'merchant', 3.0, 0.0), (1, 'Sister Asha', 'healer', 0.0, -2.5), -- Thornwatch (forest) (2, 'Guard Halric', 'quest_giver', -3.0, 0.5), (2, 'Trader Wynn', 'merchant', 2.0, 2.0), -- Ashengard (ruins) (3, 'Scholar Orin', 'quest_giver', 1.0, -2.0), (3, 'Bone Merchant', 'merchant', -2.0, 3.0), (3, 'Priestess Liora', 'healer', 3.0, 1.0), -- Redcliff (canyon) (4, 'Foreman Brak', 'quest_giver', -1.0, 2.0), (4, 'Miner Supplies', 'merchant', 2.5, -1.0), -- Boghollow (swamp) (5, 'Witch Nessa', 'quest_giver', 0.0, 3.0), (5, 'Swamp Trader', 'merchant', -3.0, -1.0), (5, 'Marsh Healer Ren', 'healer', 2.0, 0.0), -- Cinderkeep (volcanic) (6, 'Forge-master Kael','quest_giver', -2.5, 0.0), (6, 'Ember Merchant', 'merchant', 1.0, 2.5), -- Starfall (astral) (7, 'Seer Aelith', 'quest_giver', 0.0, -3.0), (7, 'Void Trader', 'merchant', 3.0, 1.0), (7, 'Astral Mender', 'healer', -2.0, 2.0); -- ============================================================ -- Seed data: quests -- ============================================================ -- Willowdale quests (Elder Maren, npc_id = 1) INSERT INTO quests (npc_id, title, description, type, target_count, target_enemy_type, target_town_id, drop_chance, min_level, max_level, reward_xp, reward_gold, reward_potions) VALUES (1, 'Wolf Cull', 'The wolves near Willowdale are getting bolder. Thin their numbers.', 'kill_count', 5, 'wolf', NULL, 0.0, 1, 5, 30, 15, 0), (1, 'Boar Hunt', 'Wild boars are trampling the crops. Take care of them.', 'kill_count', 8, 'boar', NULL, 0.0, 2, 6, 50, 25, 1), (1, 'Deliver to Thornwatch', 'Carry this supply manifest to Guard Halric in Thornwatch.', 'visit_town', 1, NULL, 2, 0.0, 1, 10, 40, 20, 0); -- Thornwatch quests (Guard Halric, npc_id = 4) INSERT INTO quests (npc_id, title, description, type, target_count, target_enemy_type, target_town_id, drop_chance, min_level, max_level, reward_xp, reward_gold, reward_potions) VALUES (4, 'Spider Infestation', 'Cave spiders have overrun the logging trails. Clear them out.', 'kill_count', 12, 'spider', NULL, 0.0, 5, 10, 80, 40, 1), (4, 'Spider Fang Collection', 'We need spider fangs for antivenom. Collect them from slain spiders.', 'collect_item', 5, 'spider', NULL, 0.3, 5, 10, 100, 60, 1), (4, 'Forest Patrol', 'Slay any 15 creatures along the forest road to keep it safe.', 'kill_count', 15, NULL, NULL, 0.0, 5, 12, 120, 70, 1); -- Ashengard quests (Scholar Orin, npc_id = 6) INSERT INTO quests (npc_id, title, description, type, target_count, target_enemy_type, target_town_id, drop_chance, min_level, max_level, reward_xp, reward_gold, reward_potions) VALUES (6, 'Undead Purge', 'The ruins are crawling with undead. Destroy the zombies.', 'kill_count', 15, 'zombie', NULL, 0.0, 10, 16, 150, 80, 1), (6, 'Ancient Relics', 'Search fallen enemies for fragments of the old kingdom.', 'collect_item', 8, NULL, NULL, 0.25, 10, 16, 200, 120, 2), (6, 'Report to Redcliff', 'Warn Foreman Brak about the growing undead threat.', 'visit_town', 1, NULL, 4, 0.0, 10, 20, 120, 60, 0); -- Redcliff quests (Foreman Brak, npc_id = 9) INSERT INTO quests (npc_id, title, description, type, target_count, target_enemy_type, target_town_id, drop_chance, min_level, max_level, reward_xp, reward_gold, reward_potions) VALUES (9, 'Orc Raider Cleanup', 'Orc warriors are raiding the mine carts. Stop them.', 'kill_count', 20, 'orc', NULL, 0.0, 16, 22, 250, 150, 2), (9, 'Ore Samples', 'Collect glowing ore fragments from defeated enemies near the canyon.', 'collect_item', 6, NULL, NULL, 0.3, 16, 22, 200, 120, 1); -- Boghollow quests (Witch Nessa, npc_id = 11) INSERT INTO quests (npc_id, title, description, type, target_count, target_enemy_type, target_town_id, drop_chance, min_level, max_level, reward_xp, reward_gold, reward_potions) VALUES (11, 'Swamp Creatures', 'The swamp beasts grow more aggressive by the day. Cull 25.', 'kill_count', 25, NULL, NULL, 0.0, 22, 28, 350, 200, 2), (11, 'Venomous Harvest', 'Collect venom sacs from swamp creatures for my brews.', 'collect_item', 10, NULL, NULL, 0.25, 22, 28, 400, 250, 2), (11, 'Message to Cinderkeep', 'The forgemaster needs to know about the corruption spreading here.', 'visit_town', 1, NULL, 6, 0.0, 22, 34, 200, 100, 1); -- Cinderkeep quests (Forge-master Kael, npc_id = 14) INSERT INTO quests (npc_id, title, description, type, target_count, target_enemy_type, target_town_id, drop_chance, min_level, max_level, reward_xp, reward_gold, reward_potions) VALUES (14, 'Demon Slayer', 'Fire demons are emerging from the vents. Destroy them.', 'kill_count', 10, 'fire_demon', NULL, 0.0, 28, 34, 500, 300, 2), (14, 'Infernal Cores', 'Retrieve smoldering cores from defeated fire demons.', 'collect_item', 5, 'fire_demon', NULL, 0.3, 28, 34, 600, 350, 3); -- Starfall quests (Seer Aelith, npc_id = 16) INSERT INTO quests (npc_id, title, description, type, target_count, target_enemy_type, target_town_id, drop_chance, min_level, max_level, reward_xp, reward_gold, reward_potions) VALUES (16, 'Titan''s Challenge', 'The Lightning Titans must be stopped before they breach the gate.', 'kill_count', 8, 'lightning_titan', NULL, 0.0, 34, 40, 800, 500, 3), (16, 'Void Fragments', 'Gather crystallized void energy from the astral enemies.', 'collect_item', 8, NULL, NULL, 0.2, 34, 40, 1000, 600, 3), (16, 'Full Circle', 'Return to Willowdale and tell Elder Maren of your journey.', 'visit_town', 1, NULL, 1, 0.0, 34, 40, 500, 300, 2);