npcs
parent
9d182cd39b
commit
d34d428d8a
@ -0,0 +1,75 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
// Gear vendor NPC types (town shop stock is restricted by slot set).
|
||||||
|
const (
|
||||||
|
NPCTypeMerchant = "merchant" // feet, hands, cloak
|
||||||
|
NPCTypeArmorer = "armorer" // legs, wrist, chest, head
|
||||||
|
NPCTypeWeapon = "weapon" // main_hand
|
||||||
|
NPCTypeJeweler = "jeweler" // finger, neck
|
||||||
|
NPCTypeHealer = "healer"
|
||||||
|
NPCTypeBounty = "bounty_hunter"
|
||||||
|
NPCTypeElder = "elder"
|
||||||
|
NPCTypeQuestGiver = "quest_giver" // legacy; not used after DB migration
|
||||||
|
)
|
||||||
|
|
||||||
|
var gearVendorTypes = map[string]struct{}{
|
||||||
|
NPCTypeMerchant: {},
|
||||||
|
NPCTypeArmorer: {},
|
||||||
|
NPCTypeWeapon: {},
|
||||||
|
NPCTypeJeweler: {},
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsGearVendorType is true for town NPCs that sell rolled gear rows.
|
||||||
|
func IsGearVendorType(t string) bool {
|
||||||
|
_, ok := gearVendorTypes[t]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// GearVendorSlots returns allowed equipment slots for this vendor type, or nil if not a gear vendor.
|
||||||
|
func GearVendorSlots(npcType string) []EquipmentSlot {
|
||||||
|
switch npcType {
|
||||||
|
case NPCTypeMerchant:
|
||||||
|
return []EquipmentSlot{SlotFeet, SlotHands, SlotCloak}
|
||||||
|
case NPCTypeArmorer:
|
||||||
|
return []EquipmentSlot{SlotLegs, SlotWrist, SlotChest, SlotHead}
|
||||||
|
case NPCTypeWeapon:
|
||||||
|
return []EquipmentSlot{SlotMainHand}
|
||||||
|
case NPCTypeJeweler:
|
||||||
|
return []EquipmentSlot{SlotFinger, SlotNeck}
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsQuestOfferNPCType is true for NPCs that can offer quest templates from the catalog.
|
||||||
|
func IsQuestOfferNPCType(t string) bool {
|
||||||
|
return t == NPCTypeBounty || t == NPCTypeElder || t == NPCTypeQuestGiver
|
||||||
|
}
|
||||||
|
|
||||||
|
// QuestTemplateAllowedForNPCType returns whether a quest template type may be offered by this NPC role.
|
||||||
|
func QuestTemplateAllowedForNPCType(npcType, questType string) bool {
|
||||||
|
switch npcType {
|
||||||
|
case NPCTypeBounty:
|
||||||
|
return questType == "kill_count" || questType == "collect_item"
|
||||||
|
case NPCTypeElder:
|
||||||
|
return questType == "visit_town" || questType == "collect_item"
|
||||||
|
case NPCTypeQuestGiver:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FilterQuestTemplatesByNPCType keeps only quests valid for the NPC role (no-op if not a quest NPC type).
|
||||||
|
func FilterQuestTemplatesByNPCType(quests []Quest, npcType string) []Quest {
|
||||||
|
if !IsQuestOfferNPCType(npcType) || npcType == NPCTypeQuestGiver {
|
||||||
|
return quests
|
||||||
|
}
|
||||||
|
out := make([]Quest, 0, len(quests))
|
||||||
|
for _, q := range quests {
|
||||||
|
if QuestTemplateAllowedForNPCType(npcType, q.Type) {
|
||||||
|
out = append(out, q)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestFilterQuestTemplatesByNPCType(t *testing.T) {
|
||||||
|
qs := []Quest{
|
||||||
|
{ID: 1, Type: "kill_count"},
|
||||||
|
{ID: 2, Type: "visit_town"},
|
||||||
|
{ID: 3, Type: "collect_item"},
|
||||||
|
}
|
||||||
|
b := FilterQuestTemplatesByNPCType(qs, NPCTypeBounty)
|
||||||
|
if len(b) != 2 || b[0].ID != 1 || b[1].ID != 3 {
|
||||||
|
t.Fatalf("bounty filter: got %+v", b)
|
||||||
|
}
|
||||||
|
e := FilterQuestTemplatesByNPCType(qs, NPCTypeElder)
|
||||||
|
if len(e) != 2 || e[0].ID != 2 || e[1].ID != 3 {
|
||||||
|
t.Fatalf("elder filter: got %+v", e)
|
||||||
|
}
|
||||||
|
if len(FilterQuestTemplatesByNPCType(qs, NPCTypeQuestGiver)) != 3 {
|
||||||
|
t.Fatal("legacy quest_giver should not filter")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGearVendorSlots(t *testing.T) {
|
||||||
|
if len(GearVendorSlots(NPCTypeWeapon)) != 1 || GearVendorSlots(NPCTypeWeapon)[0] != SlotMainHand {
|
||||||
|
t.Fatal("weapon vendor slots")
|
||||||
|
}
|
||||||
|
if !IsGearVendorType(NPCTypeJeweler) || IsGearVendorType(NPCTypeHealer) {
|
||||||
|
t.Fatal("IsGearVendorType")
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,83 @@
|
|||||||
|
-- Expand npcs.type and town_buildings.building_type; split mixed quest_givers; rotate merchants into armorer/weapon/jeweler.
|
||||||
|
|
||||||
|
ALTER TABLE public.npcs DROP CONSTRAINT IF EXISTS npcs_type_check;
|
||||||
|
ALTER TABLE public.town_buildings DROP CONSTRAINT IF EXISTS town_buildings_building_type_check;
|
||||||
|
|
||||||
|
-- visit_town + kill_count on same NPC → new elder NPC owns visit_town quests only
|
||||||
|
DO $$
|
||||||
|
DECLARE
|
||||||
|
r RECORD;
|
||||||
|
new_bid bigint;
|
||||||
|
new_nid bigint;
|
||||||
|
BEGIN
|
||||||
|
FOR r IN
|
||||||
|
SELECT n.id AS nid, n.town_id, n.offset_x AS ox, n.offset_y AS oy
|
||||||
|
FROM public.npcs n
|
||||||
|
WHERE n.type = 'quest_giver'
|
||||||
|
AND EXISTS (SELECT 1 FROM public.quests q WHERE q.npc_id = n.id AND q.type = 'visit_town')
|
||||||
|
AND EXISTS (SELECT 1 FROM public.quests q WHERE q.npc_id = n.id AND q.type = 'kill_count')
|
||||||
|
LOOP
|
||||||
|
INSERT INTO public.town_buildings (town_id, building_type, offset_x, offset_y, facing, footprint_w, footprint_h, created_at)
|
||||||
|
VALUES (r.town_id, 'house.elder', r.ox + 11, r.oy + 0.5, 'south', 2.5, 2, now())
|
||||||
|
RETURNING id INTO new_bid;
|
||||||
|
|
||||||
|
INSERT INTO public.npcs (town_id, name, name_key, type, offset_x, offset_y, created_at, building_id)
|
||||||
|
VALUES (r.town_id, 'Town Speaker', 'npc.town_speaker_generic.v1', 'elder', r.ox + 11, r.oy + 1.6, now(), new_bid)
|
||||||
|
RETURNING id INTO new_nid;
|
||||||
|
|
||||||
|
UPDATE public.quests SET npc_id = new_nid WHERE npc_id = r.nid AND type = 'visit_town';
|
||||||
|
END LOOP;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
UPDATE public.npcs SET type = 'elder'
|
||||||
|
WHERE type = 'quest_giver'
|
||||||
|
AND EXISTS (SELECT 1 FROM public.quests q WHERE q.npc_id = npcs.id AND q.type = 'visit_town')
|
||||||
|
AND NOT EXISTS (SELECT 1 FROM public.quests q WHERE q.npc_id = npcs.id AND q.type = 'kill_count');
|
||||||
|
|
||||||
|
UPDATE public.npcs SET type = 'bounty_hunter' WHERE type = 'quest_giver';
|
||||||
|
|
||||||
|
WITH ranked AS (
|
||||||
|
SELECT id,
|
||||||
|
ROW_NUMBER() OVER (PARTITION BY town_id ORDER BY id) AS rn
|
||||||
|
FROM public.npcs
|
||||||
|
WHERE type = 'merchant'
|
||||||
|
)
|
||||||
|
UPDATE public.npcs n
|
||||||
|
SET type = CASE ((r.rn - 1) % 4)
|
||||||
|
WHEN 0 THEN 'merchant'
|
||||||
|
WHEN 1 THEN 'armorer'
|
||||||
|
WHEN 2 THEN 'weapon'
|
||||||
|
ELSE 'jeweler'
|
||||||
|
END
|
||||||
|
FROM ranked r
|
||||||
|
WHERE n.id = r.id;
|
||||||
|
|
||||||
|
UPDATE public.town_buildings b
|
||||||
|
SET building_type = CASE n.type
|
||||||
|
WHEN 'merchant' THEN 'house.merchant'
|
||||||
|
WHEN 'armorer' THEN 'house.armorer'
|
||||||
|
WHEN 'weapon' THEN 'house.weapon_smith'
|
||||||
|
WHEN 'jeweler' THEN 'house.jeweler'
|
||||||
|
WHEN 'bounty_hunter' THEN 'house.bounty_hunter'
|
||||||
|
WHEN 'elder' THEN 'house.elder'
|
||||||
|
ELSE b.building_type
|
||||||
|
END
|
||||||
|
FROM public.npcs n
|
||||||
|
WHERE n.building_id = b.id
|
||||||
|
AND n.type IN ('merchant', 'armorer', 'weapon', 'jeweler', 'bounty_hunter', 'elder');
|
||||||
|
|
||||||
|
ALTER TABLE public.npcs ADD CONSTRAINT npcs_type_check CHECK (type = ANY (ARRAY[
|
||||||
|
'merchant'::text, 'armorer'::text, 'weapon'::text, 'jeweler'::text,
|
||||||
|
'bounty_hunter'::text, 'elder'::text, 'healer'::text
|
||||||
|
]));
|
||||||
|
|
||||||
|
ALTER TABLE public.town_buildings ADD CONSTRAINT town_buildings_building_type_check CHECK (building_type = ANY (ARRAY[
|
||||||
|
'house.quest_giver'::text,
|
||||||
|
'house.merchant'::text, 'house.armorer'::text, 'house.weapon_smith'::text, 'house.jeweler'::text,
|
||||||
|
'house.bounty_hunter'::text, 'house.elder'::text,
|
||||||
|
'house.healer'::text,
|
||||||
|
'decoration.well'::text, 'decoration.stall'::text, 'decoration.signpost'::text
|
||||||
|
]));
|
||||||
|
|
||||||
|
SELECT pg_catalog.setval('public.npcs_id_seq', (SELECT COALESCE(MAX(id), 1) FROM public.npcs), true);
|
||||||
|
SELECT pg_catalog.setval('public.town_buildings_id_seq', (SELECT COALESCE(MAX(id), 1) FROM public.town_buildings), true);
|
||||||
@ -0,0 +1,135 @@
|
|||||||
|
-- Per-town NPC count target: clamp(3, 6, 3 + floor((radius-7)/4)); add healers first, then rotating gear stalls.
|
||||||
|
-- New hub town 32 (Capital) with 10 NPCs; bidirectional roads to every town 1..31.
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
DECLARE
|
||||||
|
t RECORD;
|
||||||
|
need int;
|
||||||
|
have int;
|
||||||
|
new_bid bigint;
|
||||||
|
rot int := 0;
|
||||||
|
vendor_types text[] := ARRAY['merchant', 'armorer', 'weapon', 'jeweler'];
|
||||||
|
tx double precision;
|
||||||
|
ty double precision;
|
||||||
|
slot int;
|
||||||
|
BEGIN
|
||||||
|
FOR t IN SELECT id, radius FROM public.towns WHERE id BETWEEN 1 AND 31 ORDER BY id
|
||||||
|
LOOP
|
||||||
|
have := (SELECT COUNT(*)::int FROM public.npcs WHERE town_id = t.id);
|
||||||
|
need := LEAST(6, GREATEST(3, 3 + (GREATEST(0, FLOOR(t.radius)::int - 7) / 4)));
|
||||||
|
|
||||||
|
WHILE have < need LOOP
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM public.npcs WHERE town_id = t.id AND type = 'healer') THEN
|
||||||
|
tx := -16 + (have * 2.5);
|
||||||
|
ty := 14;
|
||||||
|
INSERT INTO public.town_buildings (town_id, building_type, offset_x, offset_y, facing, footprint_w, footprint_h, created_at)
|
||||||
|
VALUES (t.id, 'house.healer', tx, ty, 'south', 2.5, 2, now())
|
||||||
|
RETURNING id INTO new_bid;
|
||||||
|
|
||||||
|
INSERT INTO public.npcs (town_id, name, name_key, type, offset_x, offset_y, created_at, building_id)
|
||||||
|
VALUES (t.id, 'Roadside Medic', 'npc.roadside_medic_generic.v1', 'healer', tx, ty + 1.1, now(), new_bid);
|
||||||
|
ELSE
|
||||||
|
slot := (rot % 4) + 1;
|
||||||
|
tx := -16 + (have * 2.8);
|
||||||
|
ty := 16;
|
||||||
|
INSERT INTO public.town_buildings (town_id, building_type, offset_x, offset_y, facing, footprint_w, footprint_h, created_at)
|
||||||
|
VALUES (t.id,
|
||||||
|
CASE slot
|
||||||
|
WHEN 1 THEN 'house.merchant'
|
||||||
|
WHEN 2 THEN 'house.armorer'
|
||||||
|
WHEN 3 THEN 'house.weapon_smith'
|
||||||
|
ELSE 'house.jeweler'
|
||||||
|
END,
|
||||||
|
tx, ty, 'south', 2.5, 2, now())
|
||||||
|
RETURNING id INTO new_bid;
|
||||||
|
|
||||||
|
INSERT INTO public.npcs (town_id, name, name_key, type, offset_x, offset_y, created_at, building_id)
|
||||||
|
VALUES (t.id,
|
||||||
|
'Stall Hand ' || have::text,
|
||||||
|
'npc.stall_vendor_generic.v1',
|
||||||
|
vendor_types[slot],
|
||||||
|
tx, ty + 1.2, now(), new_bid);
|
||||||
|
rot := rot + 1;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
have := have + 1;
|
||||||
|
END LOOP;
|
||||||
|
END LOOP;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
INSERT INTO public.towns (id, name, name_key, biome, world_x, world_y, radius, level_min, level_max, created_at)
|
||||||
|
SELECT
|
||||||
|
32,
|
||||||
|
'Capital',
|
||||||
|
'town.capital.v1',
|
||||||
|
'meadow',
|
||||||
|
s.mx + 22000,
|
||||||
|
s.my,
|
||||||
|
26,
|
||||||
|
1,
|
||||||
|
60,
|
||||||
|
now()
|
||||||
|
FROM (SELECT MAX(world_x) AS mx, AVG(world_y) AS my FROM public.towns WHERE id BETWEEN 1 AND 31) AS s;
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
DECLARE
|
||||||
|
i int;
|
||||||
|
bid bigint;
|
||||||
|
v_types text[] := ARRAY['merchant','armorer','weapon','jeweler','bounty_hunter','bounty_hunter','elder','healer','armorer','jeweler'];
|
||||||
|
v_bld text[] := ARRAY['house.merchant','house.armorer','house.weapon_smith','house.jeweler','house.bounty_hunter','house.bounty_hunter','house.elder','house.healer','house.armorer','house.jeweler'];
|
||||||
|
v_ox double precision[] := ARRAY[-12::double precision,-4,4,12,-12,-4,4,12,-8,8];
|
||||||
|
v_oy double precision[] := ARRAY[-8::double precision,-8,-8,-8,0,0,0,0,8,8];
|
||||||
|
v_name text;
|
||||||
|
v_key text;
|
||||||
|
BEGIN
|
||||||
|
FOR i IN 1..10 LOOP
|
||||||
|
INSERT INTO public.town_buildings (town_id, building_type, offset_x, offset_y, facing, footprint_w, footprint_h, created_at)
|
||||||
|
VALUES (32, v_bld[i], v_ox[i], v_oy[i], 'south', 2.5, 2, now())
|
||||||
|
RETURNING id INTO bid;
|
||||||
|
|
||||||
|
v_name := CASE v_types[i]
|
||||||
|
WHEN 'merchant' THEN 'Emporium Clerk'
|
||||||
|
WHEN 'armorer' THEN 'Royal Armorer'
|
||||||
|
WHEN 'weapon' THEN 'Arena Smith'
|
||||||
|
WHEN 'jeweler' THEN 'Crown Jeweler'
|
||||||
|
WHEN 'bounty_hunter' THEN 'Contract Agent'
|
||||||
|
WHEN 'elder' THEN 'Seneschal'
|
||||||
|
WHEN 'healer' THEN 'Cathedral Medic'
|
||||||
|
ELSE 'Hall Attendant'
|
||||||
|
END;
|
||||||
|
v_key := CASE i
|
||||||
|
WHEN 1 THEN 'npc.capital.merchant_clerk.v1'
|
||||||
|
WHEN 2 THEN 'npc.capital.armorer.v1'
|
||||||
|
WHEN 3 THEN 'npc.capital.smith.v1'
|
||||||
|
WHEN 4 THEN 'npc.capital.jeweler.v1'
|
||||||
|
WHEN 5 THEN 'npc.capital.bounty_agent_a.v1'
|
||||||
|
WHEN 6 THEN 'npc.capital.bounty_agent_b.v1'
|
||||||
|
WHEN 7 THEN 'npc.capital.elder.v1'
|
||||||
|
WHEN 8 THEN 'npc.capital.healer.v1'
|
||||||
|
WHEN 9 THEN 'npc.capital.second_armorer.v1'
|
||||||
|
ELSE 'npc.capital.second_jeweler.v1'
|
||||||
|
END;
|
||||||
|
|
||||||
|
INSERT INTO public.npcs (town_id, name, name_key, type, offset_x, offset_y, created_at, building_id)
|
||||||
|
VALUES (32, v_name, v_key, v_types[i], v_ox[i], v_oy[i] + 1.2, now(), bid);
|
||||||
|
END LOOP;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
DECLARE
|
||||||
|
i int;
|
||||||
|
rid bigint;
|
||||||
|
BEGIN
|
||||||
|
SELECT COALESCE(MAX(id), 0) INTO rid FROM public.roads;
|
||||||
|
FOR i IN 1..31 LOOP
|
||||||
|
rid := rid + 1;
|
||||||
|
INSERT INTO public.roads (id, from_town_id, to_town_id, distance) VALUES (rid, 32, i, 1000);
|
||||||
|
rid := rid + 1;
|
||||||
|
INSERT INTO public.roads (id, from_town_id, to_town_id, distance) VALUES (rid, i, 32, 1000);
|
||||||
|
END LOOP;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
SELECT pg_catalog.setval('public.towns_id_seq', (SELECT COALESCE(MAX(id), 1) FROM public.towns), true);
|
||||||
|
SELECT pg_catalog.setval('public.town_buildings_id_seq', (SELECT COALESCE(MAX(id), 1) FROM public.town_buildings), true);
|
||||||
|
SELECT pg_catalog.setval('public.npcs_id_seq', (SELECT COALESCE(MAX(id), 1) FROM public.npcs), true);
|
||||||
|
SELECT pg_catalog.setval('public.roads_id_seq', (SELECT COALESCE(MAX(id), 1) FROM public.roads), true);
|
||||||
@ -0,0 +1,180 @@
|
|||||||
|
-- Medieval-style personal names for NPCs; generic elders/medics/stalls use per-id keys and pools (frontend/npcGeneratedNames.ts).
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
DECLARE
|
||||||
|
elder_names text[] := ARRAY['Edmund Weaver','Roger Thane','Aldous Pryor','Wilfred Cantor','Benedict Marsh','Godwin Alder','Piers Roper','Simon Hext','Thaddeus Wexford','Lawrence Fitzhugh','Martin Crier','Humphrey Stowe','Geoffrey Merton','Richard Plowman','Walter Burgh','Thomas Reeve','Henry Wainwright','Stephen Tiler','Nicholas Cooper','James Fletcher']::text[];
|
||||||
|
medic_names text[] := ARRAY['Brother Anselm','Sister Gode','Brother Piers','Sister Edith','Brother Osmund','Sister Maud','Brother Cuthbert','Sister Agnes','Brother Wulfstan','Sister Hilde','Brother Leofric','Sister Elfrida','Brother Dunstan','Sister Godiva','Brother Aldwin','Sister Isolde','Brother Bertram','Sister Yvette','Brother Everard','Sister Matilda','Brother Hugh','Sister Beatrice','Brother Ralph','Sister Joan','Brother Miles','Sister Margery','Brother Guy','Sister Cecily','Brother Odo','Sister Ethelreda','Brother Fulke','Sister Rosamund','Brother Ivo','Sister Aveline','Brother Lambert','Sister Sybil','Brother Gerard','Sister Petronilla','Brother Thurstan','Sister Hawise']::text[];
|
||||||
|
stall_names text[] := ARRAY['Henric Cotlar','Giles Turner','Ralf Cordwainer','Drogo Mercer','Ivo Chapman','Baldwin Fuller','Reynard Webber','Sigeric Dyer','Ailwin Skinner','Leofwine Bowyer','Ordgar Fletcher','Wulfhere Smith','Eadric Mason','Cynric Thatcher','Beorn Carver','Grimwald Cooper','Sæward Potter','Tovi Weaver','Ketil Wright','Orm Gardiner','Hakon Fisher','Snorri Cook','Ulf Baker','Eirik Brewer','Halfdan Butcher','Ragnar Chandler','Sweyn Saddler','Toki Horner','Grim Kelner','Arnulf Spicer','Berenger Glover','Fulk Haberdasher','Payn Cutler','Jocelin Nailor','Eluard Whittler','Gervase Joiner','Hamo Sawyer','Isembard Planer','Lancelin Turner','Mainard Wheeler','Odo Carter','Pagan Porter','Quentin Badger','Roric Packer','Savin Binder','Turold Tenter','Ulric Shearer','Warin Fuller','Yvain Mercer','Zacharias Draper','Alured Hosier','Brien Leatherseller','Conan Fellmonger','Denzil Woolman','Elwin Silkman','Faramund Linendraper','Garin Mercer','Helias Chapman','Isembart Cordwainer','Jordan Webber','Kenelm Dyer','Laurin Skinner','Milo Bowyer','Nigel Fletcher','Osmund Smith','Percy Mason','Quince Thatcher','Roland Carver','Sayer Cooper','Turgis Potter','Urian Weaver','Virgil Wright','Wymar Gardiner','York Fisher','Zeno Cook','Alaric Baker','Brice Brewer','Crispin Butcher','Drust Chandler','Emeric Saddler','Faramir Horner','Gawain Kelner','Hadwin Spicer','Idris Glover','Jasper Haberdasher','Kenrick Cutler','Lionel Nailor','Merrick Whittler','Nestor Joiner','Owyn Sawyer','Piers Planer','Quinlan Turner','Roric Wheeler','Seward Carter','Tancred Porter','Ulfric Badger','Valens Packer','Wulfhere Binder','Yngvar Tenter','Zebulon Shearer','Athelstan Fuller','Baldric Mercer','Cerdic Chapman','Dunstan Cordwainer','Eadwine Webber','Frith Dyer','Godric Skinner','Hereward Bowyer','Ingulf Fletcher','Kenelm Smith','Leofric Mason','Mærwynn Thatcher']::text[];
|
||||||
|
BEGIN
|
||||||
|
UPDATE public.npcs SET
|
||||||
|
name_key = 'npc.elder.byid.' || id::text || '.v1',
|
||||||
|
name = elder_names[1 + ((id * 3) % 20)]
|
||||||
|
WHERE name_key = 'npc.town_speaker_generic.v1';
|
||||||
|
|
||||||
|
UPDATE public.npcs SET
|
||||||
|
name_key = 'npc.medic.byid.' || id::text || '.v1',
|
||||||
|
name = medic_names[1 + ((id * 7) % 40)]
|
||||||
|
WHERE name_key = 'npc.roadside_medic_generic.v1';
|
||||||
|
|
||||||
|
UPDATE public.npcs SET
|
||||||
|
name_key = 'npc.stall.byid.' || id::text || '.v1',
|
||||||
|
name = stall_names[1 + ((id * 13) % 112)]
|
||||||
|
WHERE name_key = 'npc.stall_vendor_generic.v1';
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
UPDATE public.npcs SET name = v.n
|
||||||
|
FROM (VALUES
|
||||||
|
('npc.capital.merchant_clerk.v1', 'Hugh Pennington'),
|
||||||
|
('npc.capital.armorer.v1', 'Raoul d''Aubigny'),
|
||||||
|
('npc.capital.smith.v1', 'Gilles Ferron'),
|
||||||
|
('npc.capital.jeweler.v1', 'Ysabel Tremaine'),
|
||||||
|
('npc.capital.bounty_agent_a.v1', 'Roderick Vaughn'),
|
||||||
|
('npc.capital.bounty_agent_b.v1', 'Matteo Fabbri'),
|
||||||
|
('npc.capital.elder.v1', 'Anselm Corwyn'),
|
||||||
|
('npc.capital.healer.v1', 'Clothilde Mercier'),
|
||||||
|
('npc.capital.second_armorer.v1', 'Bertrand Hale'),
|
||||||
|
('npc.capital.second_jeweler.v1', 'Eleonore Rivard')
|
||||||
|
) AS v(k, n)
|
||||||
|
WHERE public.npcs.name_key = v.k;
|
||||||
|
|
||||||
|
UPDATE public.npcs SET name = CASE id
|
||||||
|
WHEN 1 THEN 'Maren Thistlewood'
|
||||||
|
WHEN 2 THEN 'Finn Marlow'
|
||||||
|
WHEN 3 THEN 'Asha Kentwell'
|
||||||
|
WHEN 4 THEN 'Halric Morrow'
|
||||||
|
WHEN 5 THEN 'Wynn Cartwright'
|
||||||
|
WHEN 6 THEN 'Orin Aldgate'
|
||||||
|
WHEN 7 THEN 'Osbert Waynwood'
|
||||||
|
WHEN 8 THEN 'Liora Selwyn'
|
||||||
|
END
|
||||||
|
WHERE id BETWEEN 1 AND 8;
|
||||||
|
|
||||||
|
UPDATE public.npcs SET
|
||||||
|
name_key = CASE id
|
||||||
|
WHEN 9 THEN 'npc.brandric_thacker.v1'
|
||||||
|
WHEN 10 THEN 'npc.conrad_pitwright.v1'
|
||||||
|
WHEN 11 THEN 'npc.nessa_bramble.v1'
|
||||||
|
WHEN 12 THEN 'npc.torin_marshwick.v1'
|
||||||
|
WHEN 13 THEN 'npc.renulf_broadmere.v1'
|
||||||
|
WHEN 14 THEN 'npc.kael_ironwright.v1'
|
||||||
|
WHEN 15 THEN 'npc.edmund_cinderwell.v1'
|
||||||
|
WHEN 16 THEN 'npc.aelith_northgate.v1'
|
||||||
|
WHEN 17 THEN 'npc.dorian_hawke.v1'
|
||||||
|
WHEN 18 THEN 'npc.mariel_starling.v1'
|
||||||
|
WHEN 19 THEN 'npc.milo_ropewalk.v1'
|
||||||
|
WHEN 20 THEN 'npc.lissa_harcourt.v1'
|
||||||
|
WHEN 21 THEN 'npc.jasper_kindling.v1'
|
||||||
|
WHEN 22 THEN 'npc.kess_wiley.v1'
|
||||||
|
WHEN 23 THEN 'npc.aldwin_relicton.v1'
|
||||||
|
WHEN 24 THEN 'npc.torvik_grimstad.v1'
|
||||||
|
WHEN 25 THEN 'npc.morna_fenwick.v1'
|
||||||
|
WHEN 26 THEN 'npc.morah_ellis.v1'
|
||||||
|
END,
|
||||||
|
name = CASE id
|
||||||
|
WHEN 9 THEN 'Brandric Thacker'
|
||||||
|
WHEN 10 THEN 'Conrad Pitwright'
|
||||||
|
WHEN 11 THEN 'Nessa Bramble'
|
||||||
|
WHEN 12 THEN 'Torin Marshwick'
|
||||||
|
WHEN 13 THEN 'Renulf Broadmere'
|
||||||
|
WHEN 14 THEN 'Kael Ironwright'
|
||||||
|
WHEN 15 THEN 'Edmund Cinderwell'
|
||||||
|
WHEN 16 THEN 'Aelith Northgate'
|
||||||
|
WHEN 17 THEN 'Dorian Hawke'
|
||||||
|
WHEN 18 THEN 'Mariel Starling'
|
||||||
|
WHEN 19 THEN 'Milo Ropewalk'
|
||||||
|
WHEN 20 THEN 'Lissa Harcourt'
|
||||||
|
WHEN 21 THEN 'Jasper Kindling'
|
||||||
|
WHEN 22 THEN 'Kess Wiley'
|
||||||
|
WHEN 23 THEN 'Aldwin Relicton'
|
||||||
|
WHEN 24 THEN 'Torvik Grimstad'
|
||||||
|
WHEN 25 THEN 'Morna Fenwick'
|
||||||
|
WHEN 26 THEN 'Morah Ellis'
|
||||||
|
END
|
||||||
|
WHERE id BETWEEN 9 AND 26;
|
||||||
|
|
||||||
|
|
||||||
|
UPDATE public.npcs SET name = CASE id
|
||||||
|
WHEN 27 THEN 'Sera Whitcomb'
|
||||||
|
WHEN 28 THEN 'Bram Ashcombe'
|
||||||
|
WHEN 29 THEN 'Nils Copperton'
|
||||||
|
WHEN 30 THEN 'Mara Tinwell'
|
||||||
|
WHEN 31 THEN 'Agnes Stillwater'
|
||||||
|
WHEN 32 THEN 'Rodrick Cantrell'
|
||||||
|
WHEN 33 THEN 'Wulfric Strand'
|
||||||
|
WHEN 34 THEN 'Jada Boltwright'
|
||||||
|
WHEN 35 THEN 'Alaric Motlow'
|
||||||
|
WHEN 36 THEN 'Percival Pike'
|
||||||
|
WHEN 37 THEN 'Eadric Ashenford'
|
||||||
|
WHEN 38 THEN 'Yoric Scarn'
|
||||||
|
WHEN 39 THEN 'Rillian Hereward'
|
||||||
|
WHEN 40 THEN 'Tove Millerson'
|
||||||
|
WHEN 41 THEN 'Gareth Grantham'
|
||||||
|
WHEN 42 THEN 'Renulf Sackville'
|
||||||
|
WHEN 43 THEN 'Bernard Lukin'
|
||||||
|
WHEN 44 THEN 'Aldwin Grimston'
|
||||||
|
WHEN 45 THEN 'Edmund Edgerton'
|
||||||
|
WHEN 46 THEN 'Crispin Aylesford'
|
||||||
|
WHEN 47 THEN 'Brunhild Flint'
|
||||||
|
WHEN 48 THEN 'Oren Starward'
|
||||||
|
WHEN 49 THEN 'Simon Spirewell'
|
||||||
|
WHEN 50 THEN 'Hugh Comstock'
|
||||||
|
WHEN 51 THEN 'Yves Portier'
|
||||||
|
WHEN 52 THEN 'Cedric Brinewell'
|
||||||
|
WHEN 53 THEN 'Osmund Salter'
|
||||||
|
WHEN 54 THEN 'Rhys Reedman'
|
||||||
|
WHEN 55 THEN 'Godfrey Middleton'
|
||||||
|
WHEN 56 THEN 'Wystan Postlethwaite'
|
||||||
|
WHEN 57 THEN 'Ivo Ironside'
|
||||||
|
WHEN 58 THEN 'Roland Rivett'
|
||||||
|
WHEN 59 THEN 'Lucan Forrest'
|
||||||
|
WHEN 60 THEN 'Alaric Boghurst'
|
||||||
|
WHEN 61 THEN 'Norbert Fenwick'
|
||||||
|
WHEN 62 THEN 'Miles Myreham'
|
||||||
|
WHEN 63 THEN 'Cuthbert Reed'
|
||||||
|
WHEN 64 THEN 'Wendel Marsham'
|
||||||
|
WHEN 65 THEN 'Sigurd Dunstan'
|
||||||
|
WHEN 66 THEN 'Silas Siltwell'
|
||||||
|
WHEN 67 THEN 'Peter Sanderson'
|
||||||
|
WHEN 68 THEN 'Griselda Holt'
|
||||||
|
WHEN 69 THEN 'Bartholomew Howe'
|
||||||
|
WHEN 70 THEN 'Baldwin Bonewright'
|
||||||
|
WHEN 71 THEN 'Cole Aldridge'
|
||||||
|
WHEN 72 THEN 'Shadrach Morrow'
|
||||||
|
WHEN 73 THEN 'Rowan Mistwell'
|
||||||
|
WHEN 74 THEN 'Fergus Fogarty'
|
||||||
|
WHEN 75 THEN 'Dewi Tarrant'
|
||||||
|
WHEN 76 THEN 'Vespasian Vale'
|
||||||
|
WHEN 77 THEN 'Hugo Holloway'
|
||||||
|
WHEN 78 THEN 'Meredith Stowe'
|
||||||
|
WHEN 79 THEN 'Roderick Rotherham'
|
||||||
|
WHEN 80 THEN 'Beatrice Boghurst'
|
||||||
|
WHEN 81 THEN 'Ashford Hale'
|
||||||
|
WHEN 82 THEN 'Cyril Cinders'
|
||||||
|
WHEN 83 THEN 'Emrys Emberly'
|
||||||
|
WHEN 84 THEN 'Alicia Ashford'
|
||||||
|
WHEN 85 THEN 'Thorne Hawthorn'
|
||||||
|
WHEN 86 THEN 'Brian Briarton'
|
||||||
|
WHEN 87 THEN 'Rowan Rootwell'
|
||||||
|
WHEN 88 THEN 'Leofric Leaford'
|
||||||
|
WHEN 89 THEN 'Galfrid Gales'
|
||||||
|
WHEN 90 THEN 'Wynstan Windham'
|
||||||
|
WHEN 91 THEN 'Gustav Merseburg'
|
||||||
|
WHEN 92 THEN 'Blaise Brissot'
|
||||||
|
WHEN 93 THEN 'Archibald Frostwick'
|
||||||
|
WHEN 94 THEN 'Rhys Rimer'
|
||||||
|
WHEN 95 THEN 'Horace Hoarwell'
|
||||||
|
WHEN 96 THEN 'Isolde Ismay'
|
||||||
|
WHEN 97 THEN 'Solomon Sunderland'
|
||||||
|
WHEN 98 THEN 'Clifford Cliffeton'
|
||||||
|
WHEN 99 THEN 'Craig Cragwell'
|
||||||
|
WHEN 100 THEN 'Dustin Harwell'
|
||||||
|
WHEN 101 THEN 'Marshall Fordham'
|
||||||
|
WHEN 102 THEN 'Rivers Trent'
|
||||||
|
WHEN 103 THEN 'Bridges Ballard'
|
||||||
|
WHEN 104 THEN 'Sterling Brook'
|
||||||
|
WHEN 105 THEN 'Sevrin Veilcourt'
|
||||||
|
WHEN 106 THEN 'Sterling Starwell'
|
||||||
|
WHEN 107 THEN 'Neville Nevett'
|
||||||
|
WHEN 108 THEN 'Vera Veilhart'
|
||||||
|
END
|
||||||
|
WHERE id BETWEEN 27 AND 108;
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 2.8 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.3 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.5 MiB |
@ -0,0 +1,5 @@
|
|||||||
|
-- Generated by scripts/gen-npc-sql-arrays.mjs from npcGeneratedNames.ts
|
||||||
|
-- lengths: elder=20 medic=40 stall=112
|
||||||
|
elder_names text[] := ARRAY['Edmund Weaver','Roger Thane','Aldous Pryor','Wilfred Cantor','Benedict Marsh','Godwin Alder','Piers Roper','Simon Hext','Thaddeus Wexford','Lawrence Fitzhugh','Martin Crier','Humphrey Stowe','Geoffrey Merton','Richard Plowman','Walter Burgh','Thomas Reeve','Henry Wainwright','Stephen Tiler','Nicholas Cooper','James Fletcher']::text[];
|
||||||
|
medic_names text[] := ARRAY['Brother Anselm','Sister Gode','Brother Piers','Sister Edith','Brother Osmund','Sister Maud','Brother Cuthbert','Sister Agnes','Brother Wulfstan','Sister Hilde','Brother Leofric','Sister Elfrida','Brother Dunstan','Sister Godiva','Brother Aldwin','Sister Isolde','Brother Bertram','Sister Yvette','Brother Everard','Sister Matilda','Brother Hugh','Sister Beatrice','Brother Ralph','Sister Joan','Brother Miles','Sister Margery','Brother Guy','Sister Cecily','Brother Odo','Sister Ethelreda','Brother Fulke','Sister Rosamund','Brother Ivo','Sister Aveline','Brother Lambert','Sister Sybil','Brother Gerard','Sister Petronilla','Brother Thurstan','Sister Hawise']::text[];
|
||||||
|
stall_names text[] := ARRAY['Henric Cotlar','Giles Turner','Ralf Cordwainer','Drogo Mercer','Ivo Chapman','Baldwin Fuller','Reynard Webber','Sigeric Dyer','Ailwin Skinner','Leofwine Bowyer','Ordgar Fletcher','Wulfhere Smith','Eadric Mason','Cynric Thatcher','Beorn Carver','Grimwald Cooper','Sæward Potter','Tovi Weaver','Ketil Wright','Orm Gardiner','Hakon Fisher','Snorri Cook','Ulf Baker','Eirik Brewer','Halfdan Butcher','Ragnar Chandler','Sweyn Saddler','Toki Horner','Grim Kelner','Arnulf Spicer','Berenger Glover','Fulk Haberdasher','Payn Cutler','Jocelin Nailor','Eluard Whittler','Gervase Joiner','Hamo Sawyer','Isembard Planer','Lancelin Turner','Mainard Wheeler','Odo Carter','Pagan Porter','Quentin Badger','Roric Packer','Savin Binder','Turold Tenter','Ulric Shearer','Warin Fuller','Yvain Mercer','Zacharias Draper','Alured Hosier','Brien Leatherseller','Conan Fellmonger','Denzil Woolman','Elwin Silkman','Faramund Linendraper','Garin Mercer','Helias Chapman','Isembart Cordwainer','Jordan Webber','Kenelm Dyer','Laurin Skinner','Milo Bowyer','Nigel Fletcher','Osmund Smith','Percy Mason','Quince Thatcher','Roland Carver','Sayer Cooper','Turgis Potter','Urian Weaver','Virgil Wright','Wymar Gardiner','York Fisher','Zeno Cook','Alaric Baker','Brice Brewer','Crispin Butcher','Drust Chandler','Emeric Saddler','Faramir Horner','Gawain Kelner','Hadwin Spicer','Idris Glover','Jasper Haberdasher','Kenrick Cutler','Lionel Nailor','Merrick Whittler','Nestor Joiner','Owyn Sawyer','Piers Planer','Quinlan Turner','Roric Wheeler','Seward Carter','Tancred Porter','Ulfric Badger','Valens Packer','Wulfhere Binder','Yngvar Tenter','Zebulon Shearer','Athelstan Fuller','Baldric Mercer','Cerdic Chapman','Dunstan Cordwainer','Eadwine Webber','Frith Dyer','Godric Skinner','Hereward Bowyer','Ingulf Fletcher','Kenelm Smith','Leofric Mason','Mærwynn Thatcher']::text[];
|
||||||
@ -0,0 +1,42 @@
|
|||||||
|
import { readFileSync, writeFileSync } from 'fs';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
import { dirname, join } from 'path';
|
||||||
|
|
||||||
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
|
const root = join(__dirname, '..');
|
||||||
|
const s = readFileSync(join(root, 'frontend/src/i18n/npcGeneratedNames.ts'), 'utf8');
|
||||||
|
|
||||||
|
function extractArray(constName) {
|
||||||
|
const start = s.indexOf(`export const ${constName}`);
|
||||||
|
if (start < 0) throw new Error('missing ' + constName);
|
||||||
|
const slice = s.slice(start, start + 80000);
|
||||||
|
const out = [];
|
||||||
|
const re = /en: '((?:\\'|[^'])*)'/g;
|
||||||
|
let m;
|
||||||
|
let depth = 0;
|
||||||
|
const subStart = slice.indexOf('[');
|
||||||
|
const subEnd = slice.indexOf('];', subStart);
|
||||||
|
const block = slice.slice(subStart, subEnd);
|
||||||
|
while ((m = re.exec(block))) {
|
||||||
|
out.push(m[1].replace(/\\'/g, "'"));
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
const elder = extractArray('ELDER_GEN_NAMES');
|
||||||
|
const medic = extractArray('MEDIC_GEN_NAMES');
|
||||||
|
const stall = extractArray('STALL_GEN_NAMES');
|
||||||
|
|
||||||
|
function sqlArr(a) {
|
||||||
|
return `ARRAY[${a.map((x) => `'${x.replace(/'/g, "''")}'`).join(',')}]::text[]`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sql =
|
||||||
|
`-- Generated by scripts/gen-npc-sql-arrays.mjs from npcGeneratedNames.ts\n` +
|
||||||
|
`-- lengths: elder=${elder.length} medic=${medic.length} stall=${stall.length}\n` +
|
||||||
|
`elder_names text[] := ${sqlArr(elder)};\n` +
|
||||||
|
`medic_names text[] := ${sqlArr(medic)};\n` +
|
||||||
|
`stall_names text[] := ${sqlArr(stall)};\n`;
|
||||||
|
|
||||||
|
writeFileSync(join(root, 'scripts/_npc_name_arrays_snippet.sql'), sql);
|
||||||
|
console.log('Wrote scripts/_npc_name_arrays_snippet.sql', elder.length, medic.length, stall.length);
|
||||||
@ -0,0 +1,209 @@
|
|||||||
|
import { readFileSync, writeFileSync } from 'fs';
|
||||||
|
import { dirname, join } from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
|
||||||
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
|
const root = join(__dirname, '..');
|
||||||
|
const snippetPath = join(root, 'scripts/_npc_name_arrays_snippet.sql');
|
||||||
|
const snippet = readFileSync(snippetPath, 'utf8');
|
||||||
|
const decl = snippet
|
||||||
|
.split('\n')
|
||||||
|
.filter((l) => l.includes('text[] :='))
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
|
const n8 = [
|
||||||
|
[1, 'Maren Thistlewood'],
|
||||||
|
[2, 'Finn Marlow'],
|
||||||
|
[3, 'Asha Kentwell'],
|
||||||
|
[4, 'Halric Morrow'],
|
||||||
|
[5, 'Wynn Cartwright'],
|
||||||
|
[6, 'Orin Aldgate'],
|
||||||
|
[7, 'Osbert Waynwood'],
|
||||||
|
[8, 'Liora Selwyn'],
|
||||||
|
];
|
||||||
|
const n926 = [
|
||||||
|
[9, 'npc.brandric_thacker.v1', 'Brandric Thacker'],
|
||||||
|
[10, 'npc.conrad_pitwright.v1', 'Conrad Pitwright'],
|
||||||
|
[11, 'npc.nessa_bramble.v1', 'Nessa Bramble'],
|
||||||
|
[12, 'npc.torin_marshwick.v1', 'Torin Marshwick'],
|
||||||
|
[13, 'npc.renulf_broadmere.v1', 'Renulf Broadmere'],
|
||||||
|
[14, 'npc.kael_ironwright.v1', 'Kael Ironwright'],
|
||||||
|
[15, 'npc.edmund_cinderwell.v1', 'Edmund Cinderwell'],
|
||||||
|
[16, 'npc.aelith_northgate.v1', 'Aelith Northgate'],
|
||||||
|
[17, 'npc.dorian_hawke.v1', 'Dorian Hawke'],
|
||||||
|
[18, 'npc.mariel_starling.v1', 'Mariel Starling'],
|
||||||
|
[19, 'npc.milo_ropewalk.v1', 'Milo Ropewalk'],
|
||||||
|
[20, 'npc.lissa_harcourt.v1', 'Lissa Harcourt'],
|
||||||
|
[21, 'npc.jasper_kindling.v1', 'Jasper Kindling'],
|
||||||
|
[22, 'npc.kess_wiley.v1', 'Kess Wiley'],
|
||||||
|
[23, 'npc.aldwin_relicton.v1', 'Aldwin Relicton'],
|
||||||
|
[24, 'npc.torvik_grimstad.v1', 'Torvik Grimstad'],
|
||||||
|
[25, 'npc.morna_fenwick.v1', 'Morna Fenwick'],
|
||||||
|
[26, 'npc.morah_ellis.v1', 'Morah Ellis'],
|
||||||
|
];
|
||||||
|
const n27108 = [
|
||||||
|
[27, 'Sera Whitcomb'],
|
||||||
|
[28, 'Bram Ashcombe'],
|
||||||
|
[29, 'Nils Copperton'],
|
||||||
|
[30, 'Mara Tinwell'],
|
||||||
|
[31, 'Agnes Stillwater'],
|
||||||
|
[32, 'Rodrick Cantrell'],
|
||||||
|
[33, 'Wulfric Strand'],
|
||||||
|
[34, 'Jada Boltwright'],
|
||||||
|
[35, 'Alaric Motlow'],
|
||||||
|
[36, 'Percival Pike'],
|
||||||
|
[37, 'Eadric Ashenford'],
|
||||||
|
[38, 'Yoric Scarn'],
|
||||||
|
[39, 'Rillian Hereward'],
|
||||||
|
[40, 'Tove Millerson'],
|
||||||
|
[41, 'Gareth Grantham'],
|
||||||
|
[42, 'Renulf Sackville'],
|
||||||
|
[43, 'Bernard Lukin'],
|
||||||
|
[44, 'Aldwin Grimston'],
|
||||||
|
[45, 'Edmund Edgerton'],
|
||||||
|
[46, 'Crispin Aylesford'],
|
||||||
|
[47, 'Brunhild Flint'],
|
||||||
|
[48, 'Oren Starward'],
|
||||||
|
[49, 'Simon Spirewell'],
|
||||||
|
[50, 'Hugh Comstock'],
|
||||||
|
[51, 'Yves Portier'],
|
||||||
|
[52, 'Cedric Brinewell'],
|
||||||
|
[53, 'Osmund Salter'],
|
||||||
|
[54, 'Rhys Reedman'],
|
||||||
|
[55, 'Godfrey Middleton'],
|
||||||
|
[56, 'Wystan Postlethwaite'],
|
||||||
|
[57, 'Ivo Ironside'],
|
||||||
|
[58, 'Roland Rivett'],
|
||||||
|
[59, 'Lucan Forrest'],
|
||||||
|
[60, 'Alaric Boghurst'],
|
||||||
|
[61, 'Norbert Fenwick'],
|
||||||
|
[62, 'Miles Myreham'],
|
||||||
|
[63, 'Cuthbert Reed'],
|
||||||
|
[64, 'Wendel Marsham'],
|
||||||
|
[65, 'Sigurd Dunstan'],
|
||||||
|
[66, 'Silas Siltwell'],
|
||||||
|
[67, 'Peter Sanderson'],
|
||||||
|
[68, 'Griselda Holt'],
|
||||||
|
[69, 'Bartholomew Howe'],
|
||||||
|
[70, 'Baldwin Bonewright'],
|
||||||
|
[71, 'Cole Aldridge'],
|
||||||
|
[72, 'Shadrach Morrow'],
|
||||||
|
[73, 'Rowan Mistwell'],
|
||||||
|
[74, 'Fergus Fogarty'],
|
||||||
|
[75, 'Dewi Tarrant'],
|
||||||
|
[76, 'Vespasian Vale'],
|
||||||
|
[77, 'Hugo Holloway'],
|
||||||
|
[78, 'Meredith Stowe'],
|
||||||
|
[79, 'Roderick Rotherham'],
|
||||||
|
[80, 'Beatrice Boghurst'],
|
||||||
|
[81, 'Ashford Hale'],
|
||||||
|
[82, 'Cyril Cinders'],
|
||||||
|
[83, 'Emrys Emberly'],
|
||||||
|
[84, 'Alicia Ashford'],
|
||||||
|
[85, 'Thorne Hawthorn'],
|
||||||
|
[86, 'Brian Briarton'],
|
||||||
|
[87, 'Rowan Rootwell'],
|
||||||
|
[88, 'Leofric Leaford'],
|
||||||
|
[89, 'Galfrid Gales'],
|
||||||
|
[90, 'Wynstan Windham'],
|
||||||
|
[91, 'Gustav Merseburg'],
|
||||||
|
[92, 'Blaise Brissot'],
|
||||||
|
[93, 'Archibald Frostwick'],
|
||||||
|
[94, 'Rhys Rimer'],
|
||||||
|
[95, 'Horace Hoarwell'],
|
||||||
|
[96, 'Isolde Ismay'],
|
||||||
|
[97, 'Solomon Sunderland'],
|
||||||
|
[98, 'Clifford Cliffeton'],
|
||||||
|
[99, 'Craig Cragwell'],
|
||||||
|
[100, 'Dustin Harwell'],
|
||||||
|
[101, 'Marshall Fordham'],
|
||||||
|
[102, 'Rivers Trent'],
|
||||||
|
[103, 'Bridges Ballard'],
|
||||||
|
[104, 'Sterling Brook'],
|
||||||
|
[105, 'Sevrin Veilcourt'],
|
||||||
|
[106, 'Sterling Starwell'],
|
||||||
|
[107, 'Neville Nevett'],
|
||||||
|
[108, 'Vera Veilhart'],
|
||||||
|
];
|
||||||
|
|
||||||
|
function esc(s) {
|
||||||
|
return s.replace(/'/g, "''");
|
||||||
|
}
|
||||||
|
|
||||||
|
function caseWhen(pairs, col) {
|
||||||
|
return pairs.map(([id, v]) => ` WHEN ${id} THEN '${esc(v)}'`).join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
function caseWhen926() {
|
||||||
|
const k = n926.map(([id, key]) => ` WHEN ${id} THEN '${esc(key)}'`).join('\n');
|
||||||
|
const n = n926.map(([id, , name]) => ` WHEN ${id} THEN '${esc(name)}'`).join('\n');
|
||||||
|
return { k, n };
|
||||||
|
}
|
||||||
|
|
||||||
|
const c926 = caseWhen926();
|
||||||
|
|
||||||
|
const sql = `-- Medieval-style personal names for NPCs; generic elders/medics/stalls use per-id keys and pools (frontend/npcGeneratedNames.ts).
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
DECLARE
|
||||||
|
${decl}
|
||||||
|
BEGIN
|
||||||
|
UPDATE public.npcs SET
|
||||||
|
name_key = 'npc.elder.byid.' || id::text || '.v1',
|
||||||
|
name = elder_names[1 + ((id * 3) % 20)]
|
||||||
|
WHERE name_key = 'npc.town_speaker_generic.v1';
|
||||||
|
|
||||||
|
UPDATE public.npcs SET
|
||||||
|
name_key = 'npc.medic.byid.' || id::text || '.v1',
|
||||||
|
name = medic_names[1 + ((id * 7) % 40)]
|
||||||
|
WHERE name_key = 'npc.roadside_medic_generic.v1';
|
||||||
|
|
||||||
|
UPDATE public.npcs SET
|
||||||
|
name_key = 'npc.stall.byid.' || id::text || '.v1',
|
||||||
|
name = stall_names[1 + ((id * 13) % 112)]
|
||||||
|
WHERE name_key = 'npc.stall_vendor_generic.v1';
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
UPDATE public.npcs SET name = v.n
|
||||||
|
FROM (VALUES
|
||||||
|
('npc.capital.merchant_clerk.v1', 'Hugh Pennington'),
|
||||||
|
('npc.capital.armorer.v1', 'Raoul d''Aubigny'),
|
||||||
|
('npc.capital.smith.v1', 'Gilles Ferron'),
|
||||||
|
('npc.capital.jeweler.v1', 'Ysabel Tremaine'),
|
||||||
|
('npc.capital.bounty_agent_a.v1', 'Roderick Vaughn'),
|
||||||
|
('npc.capital.bounty_agent_b.v1', 'Matteo Fabbri'),
|
||||||
|
('npc.capital.elder.v1', 'Anselm Corwyn'),
|
||||||
|
('npc.capital.healer.v1', 'Clothilde Mercier'),
|
||||||
|
('npc.capital.second_armorer.v1', 'Bertrand Hale'),
|
||||||
|
('npc.capital.second_jeweler.v1', 'Eleonore Rivard')
|
||||||
|
) AS v(k, n)
|
||||||
|
WHERE public.npcs.name_key = v.k;
|
||||||
|
|
||||||
|
UPDATE public.npcs SET name = CASE id
|
||||||
|
${caseWhen(n8, 'name')}
|
||||||
|
END
|
||||||
|
WHERE id BETWEEN 1 AND 8;
|
||||||
|
|
||||||
|
UPDATE public.npcs SET
|
||||||
|
name_key = CASE id
|
||||||
|
${c926.k}
|
||||||
|
END,
|
||||||
|
name = CASE id
|
||||||
|
${c926.n}
|
||||||
|
END
|
||||||
|
WHERE id BETWEEN 9 AND 27;
|
||||||
|
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Fix: WHERE id BETWEEN 9 AND 26 not 27
|
||||||
|
const sqlFixed = sql.replace('WHERE id BETWEEN 9 AND 27', 'WHERE id BETWEEN 9 AND 26');
|
||||||
|
|
||||||
|
const part27 = `UPDATE public.npcs SET name = CASE id
|
||||||
|
${caseWhen(n27108, 'name')}
|
||||||
|
END
|
||||||
|
WHERE id BETWEEN 27 AND 108;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const out = sqlFixed + '\n' + part27;
|
||||||
|
writeFileSync(join(root, 'backend/migrations/000034_npc_medieval_names.sql'), out);
|
||||||
|
console.log('Wrote 000034_npc_medieval_names.sql');
|
||||||
Loading…
Reference in New Issue