İlk commit: Stepstead backend (FastAPI)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
commit
ea049b8ccd
45 changed files with 6751 additions and 0 deletions
120
db/export_crafts_json.py
Normal file
120
db/export_crafts_json.py
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Stepstead craft veritabanı JSON üretici.
|
||||
recipes + items → /var/www/stepstead.com/db/data/crafts.json
|
||||
Grup: tarif kodu önekinden. Aile: kod son ekinden (malzeme ailesi).
|
||||
Çalıştırma: /opt/stepstead/venv/bin/python3 /opt/stepstead/db/export_crafts_json.py
|
||||
"""
|
||||
import asyncio
|
||||
import json
|
||||
import sys
|
||||
|
||||
import asyncpg
|
||||
|
||||
sys.path.insert(0, "/opt/stepstead")
|
||||
from config import settings
|
||||
|
||||
DSN = settings.DATABASE_URL.replace("postgresql+asyncpg://", "postgresql://")
|
||||
OUT = "/var/www/stepstead.com/db/data/crafts.json"
|
||||
|
||||
GROUPS = [
|
||||
("saw_", "Kerestecilik"), ("build_", "Marangozluk"),
|
||||
("craft_bow_", "Okçuluk"), ("craft_arrow_", "Okçuluk"),
|
||||
("craft_staff_", "Büyücülük"),
|
||||
("smelt_", "Metalurji"), ("forge_", "Demircilik"),
|
||||
("craft_axe_", "Demircilik"), ("craft_pick_", "Demircilik"),
|
||||
("jewel_", "Kuyumculuk"), ("brew_", "Simya"),
|
||||
("cook_", "Aşçılık"), ("tailor_", "Dericilik"), ("craft_rod", "Balıkçılık"),
|
||||
]
|
||||
|
||||
FAMILY = {
|
||||
# odunlar
|
||||
"oak": "Meşe", "pine": "Çam", "birch": "Huş", "beech": "Kayın", "maple": "Akçaağaç",
|
||||
"willow": "Söğüt", "ash": "Dişbudak", "cedar": "Sedir", "walnut": "Ceviz",
|
||||
"cherry": "Kiraz", "yew": "Porsuk", "mahogany": "Maun", "ebony": "Abanoz",
|
||||
# metaller
|
||||
"copper": "Bakır", "bronze": "Bronz", "iron": "Demir", "steel": "Çelik",
|
||||
"silver": "Gümüş", "gold": "Altın", "cu": "Bakır", "fe": "Demir", "stone": "Taş",
|
||||
# otlar
|
||||
"mint": "Nane", "sage": "Adaçayı", "thyme": "Kekik", "chamomile": "Papatya",
|
||||
"rosemary": "Biberiye", "basil": "Fesleğen", "nettle": "Isırgan",
|
||||
"lemonbalm": "Melisa", "fennel": "Rezene", "lavender": "Lavanta",
|
||||
"ginseng": "Ginseng", "saffron": "Safran",
|
||||
# balıklar
|
||||
"carp": "Sazan", "perch": "Levrek", "anchovy": "Hamsi", "mullet": "Kefal",
|
||||
"mackerel": "Uskumru", "bonito": "Palamut", "bluefish": "Lüfer", "bream": "Çipura",
|
||||
"trout": "Alabalık", "pike": "Turna", "catfish": "Yayın", "salmon": "Somon",
|
||||
"turbot": "Kalkan", "tuna": "Ton Balığı", "sturgeon": "Mersin",
|
||||
# hayvanlar
|
||||
"rabbit": "Tavşan", "deer": "Geyik", "boar": "Yaban Domuzu", "wolf": "Kurt", "bear": "Ayı",
|
||||
}
|
||||
|
||||
SKILL_TR = {
|
||||
"mining": "Madencilik", "fishing": "Balıkçılık", "foraging": "Toplama",
|
||||
"woodcutting": "Ağaç Kesme", "hunting": "Avcılık", "combat": "Savaş",
|
||||
"smithing": "Demircilik", "alchemy": "Simya", "cooking": "Aşçılık",
|
||||
"construction": "İnşaat",
|
||||
}
|
||||
|
||||
|
||||
def group_for(code: str) -> str:
|
||||
for prefix, g in GROUPS:
|
||||
if code.startswith(prefix):
|
||||
return g
|
||||
return "Diğer"
|
||||
|
||||
|
||||
def family_for(code: str) -> str:
|
||||
return FAMILY.get(code.rsplit("_", 1)[-1], "Genel")
|
||||
|
||||
|
||||
async def main():
|
||||
conn = await asyncpg.connect(DSN)
|
||||
item_rows = await conn.fetch("SELECT code, name, rarity, type, effect FROM items")
|
||||
imap = {r["code"]: dict(r) for r in item_rows}
|
||||
rows = await conn.fetch("""
|
||||
SELECT r.code, r.name, r.output_qty, r.skill, r.required_level,
|
||||
r.xp_reward, r.step_cost, r.ingredients, i.code AS out_code
|
||||
FROM recipes r JOIN items i ON i.id = r.output_item_id
|
||||
ORDER BY r.required_level, r.name
|
||||
""")
|
||||
await conn.close()
|
||||
|
||||
out = []
|
||||
for r in rows:
|
||||
oi = imap.get(r["out_code"], {})
|
||||
effect = oi.get("effect")
|
||||
if isinstance(effect, str):
|
||||
effect = json.loads(effect)
|
||||
ings = json.loads(r["ingredients"]) if isinstance(r["ingredients"], str) else r["ingredients"]
|
||||
out.append({
|
||||
"code": r["code"],
|
||||
"name": r["name"],
|
||||
"group": group_for(r["code"]),
|
||||
"family": family_for(r["code"]),
|
||||
"skill": SKILL_TR.get(r["skill"], r["skill"]),
|
||||
"level": r["required_level"],
|
||||
"steps": r["step_cost"],
|
||||
"xp": r["xp_reward"],
|
||||
"out_qty": r["output_qty"],
|
||||
"rarity": oi.get("rarity", "common"),
|
||||
"type": oi.get("type", ""),
|
||||
"effect": effect,
|
||||
"ings": [
|
||||
{"name": imap.get(g["item_code"], {}).get("name", g["item_code"]),
|
||||
"qty": g["qty"],
|
||||
"rarity": imap.get(g["item_code"], {}).get("rarity", "common")}
|
||||
for g in ings
|
||||
],
|
||||
})
|
||||
|
||||
groups: dict[str, int] = {}
|
||||
for o in out:
|
||||
groups[o["group"]] = groups.get(o["group"], 0) + 1
|
||||
|
||||
with open(OUT, "w", encoding="utf-8") as f:
|
||||
json.dump({"recipes": out, "groups": groups, "total": len(out)}, f, ensure_ascii=False)
|
||||
print(f"{OUT}: {len(out)} tarif, gruplar: {groups}")
|
||||
|
||||
|
||||
asyncio.run(main())
|
||||
109
db/export_items_json.py
Normal file
109
db/export_items_json.py
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Stepstead eşya veritabanı JSON üretici.
|
||||
items + item_classes + location_resources + recipes → items.json
|
||||
Her eşya için: tür, rarity, yuva, sınıf kullanım kısıtı, kaynak (nereden toplanır/üretilir).
|
||||
Çalıştırma: /opt/stepstead/venv/bin/python3 /opt/stepstead/db/export_items_json.py
|
||||
"""
|
||||
import asyncio
|
||||
import json
|
||||
import sys
|
||||
|
||||
import asyncpg
|
||||
|
||||
sys.path.insert(0, "/opt/stepstead")
|
||||
from config import settings
|
||||
|
||||
DSN = settings.DATABASE_URL.replace("postgresql+asyncpg://", "postgresql://")
|
||||
OUT = "/var/www/stepstead.com/items/data/items.json"
|
||||
|
||||
TYPE_TR = {
|
||||
"material": "Ham Madde", "tool": "Alet", "gear": "Ekipman",
|
||||
"consumable": "Tüketilebilir", "furniture": "Mobilya",
|
||||
}
|
||||
SLOT_TR = {
|
||||
"weapon": "Silah", "shield": "Kalkan", "chest": "Gövde", "head": "Baş",
|
||||
"hands": "Eldiven", "feet": "Ayak", "waist": "Bel", "back": "Sırt",
|
||||
"neck": "Boyun", "finger": "Yüzük", "wrist": "Bilek", "tool": "Alet",
|
||||
}
|
||||
SKILL_TR = {
|
||||
"mining": "Madencilik", "fishing": "Balıkçılık", "foraging": "Toplama",
|
||||
"woodcutting": "Ağaç Kesme", "hunting": "Avcılık", "combat": "Savaş",
|
||||
"smithing": "Demircilik", "alchemy": "Simya", "cooking": "Aşçılık",
|
||||
"construction": "İnşaat",
|
||||
}
|
||||
|
||||
|
||||
async def main():
|
||||
conn = await asyncpg.connect(DSN)
|
||||
items = await conn.fetch("""
|
||||
SELECT id, code, name, type, rarity, equip_slot, two_handed,
|
||||
base_value, effect, description
|
||||
FROM items ORDER BY type, rarity, name
|
||||
""")
|
||||
# sınıf kullanım kısıtları
|
||||
ic = await conn.fetch("""
|
||||
SELECT ic.item_id, c.name AS class_name, c.sort_order
|
||||
FROM item_classes ic JOIN game_classes c ON c.id = ic.class_id
|
||||
ORDER BY c.sort_order, c.name
|
||||
""")
|
||||
classes_by_item: dict[int, list[str]] = {}
|
||||
for r in ic:
|
||||
classes_by_item.setdefault(r["item_id"], []).append(r["class_name"])
|
||||
all_classes = [dict(r) for r in await conn.fetch(
|
||||
"SELECT name FROM game_classes ORDER BY sort_order, name")]
|
||||
all_class_names = [c["name"] for c in all_classes]
|
||||
|
||||
# kaynak: nereden toplanır (bölge işaretleme)
|
||||
gathers = await conn.fetch("""
|
||||
SELECT lr.item_code, l.name AS loc, lr.skill
|
||||
FROM location_resources lr JOIN locations l ON l.id = lr.location_id
|
||||
ORDER BY l.name
|
||||
""")
|
||||
gather_by_code: dict[str, list[dict]] = {}
|
||||
for r in gathers:
|
||||
gather_by_code.setdefault(r["item_code"], []).append(
|
||||
{"loc": r["loc"], "skill": SKILL_TR.get(r["skill"], r["skill"])})
|
||||
|
||||
# kaynak: üretilebilir mi
|
||||
craftable = set(r["code"] for r in await conn.fetch("""
|
||||
SELECT i.code FROM recipes r JOIN items i ON i.id = r.output_item_id
|
||||
"""))
|
||||
await conn.close()
|
||||
|
||||
out = []
|
||||
for r in items:
|
||||
eff = r["effect"]
|
||||
if isinstance(eff, str):
|
||||
eff = json.loads(eff)
|
||||
cls = classes_by_item.get(r["id"], [])
|
||||
# kayıt yoksa ve takılabilir bir eşyaysa: herkes; değilse boş
|
||||
equip = r["equip_slot"]
|
||||
out.append({
|
||||
"code": r["code"],
|
||||
"name": r["name"],
|
||||
"type": TYPE_TR.get(r["type"], r["type"]),
|
||||
"type_code": r["type"],
|
||||
"rarity": r["rarity"],
|
||||
"slot": SLOT_TR.get(equip) if equip else None,
|
||||
"slot_code": equip,
|
||||
"two_handed": r["two_handed"],
|
||||
"value": r["base_value"],
|
||||
"effect": eff,
|
||||
"desc": r["description"],
|
||||
"classes": cls, # boş = tüm sınıflar
|
||||
"gather": gather_by_code.get(r["code"], []),
|
||||
"craftable": r["code"] in craftable,
|
||||
})
|
||||
|
||||
types: dict[str, int] = {}
|
||||
for o in out:
|
||||
types[o["type"]] = types.get(o["type"], 0) + 1
|
||||
|
||||
with open(OUT, "w", encoding="utf-8") as f:
|
||||
json.dump({"items": out, "types": types, "classes": all_class_names,
|
||||
"total": len(out)}, f, ensure_ascii=False)
|
||||
print(f"{OUT}: {len(out)} eşya, türler: {types}")
|
||||
|
||||
|
||||
asyncio.run(main())
|
||||
196
db/init_schema.sql
Normal file
196
db/init_schema.sql
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
-- stepstead.com — initial schema (MVP)
|
||||
-- Idempotent: safe to re-run.
|
||||
BEGIN;
|
||||
|
||||
-- =========================================================================
|
||||
-- updated_at trigger
|
||||
-- =========================================================================
|
||||
CREATE OR REPLACE FUNCTION set_updated_at()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = NOW();
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- =========================================================================
|
||||
-- 1. AUTH / KULLANICI
|
||||
-- =========================================================================
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
email VARCHAR(255) NOT NULL UNIQUE,
|
||||
password_hash VARCHAR(255) NOT NULL,
|
||||
display_name VARCHAR(64),
|
||||
email_verified BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
email_verify_token VARCHAR(128),
|
||||
email_verify_sent_at TIMESTAMPTZ,
|
||||
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
is_admin BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
last_login_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_users_email_verify_token ON users(email_verify_token)
|
||||
WHERE email_verify_token IS NOT NULL;
|
||||
|
||||
DROP TRIGGER IF EXISTS trg_users_updated_at ON users;
|
||||
CREATE TRIGGER trg_users_updated_at BEFORE UPDATE ON users
|
||||
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||
|
||||
CREATE TABLE IF NOT EXISTS password_reset_tokens (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
token VARCHAR(128) NOT NULL UNIQUE,
|
||||
expires_at TIMESTAMPTZ NOT NULL,
|
||||
used_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_pwreset_user ON password_reset_tokens(user_id);
|
||||
|
||||
-- =========================================================================
|
||||
-- 2. KARAKTER
|
||||
-- =========================================================================
|
||||
CREATE TABLE IF NOT EXISTS characters (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
name VARCHAR(32) NOT NULL UNIQUE,
|
||||
level INTEGER NOT NULL DEFAULT 1 CHECK (level BETWEEN 1 AND 50),
|
||||
total_xp BIGINT NOT NULL DEFAULT 0,
|
||||
gold BIGINT NOT NULL DEFAULT 0,
|
||||
step_balance BIGINT NOT NULL DEFAULT 0,
|
||||
step_lifetime BIGINT NOT NULL DEFAULT 0,
|
||||
pos_chunk_x INTEGER NOT NULL DEFAULT 0,
|
||||
pos_chunk_y INTEGER NOT NULL DEFAULT 0,
|
||||
pos_x INTEGER NOT NULL DEFAULT 0,
|
||||
pos_y INTEGER NOT NULL DEFAULT 0,
|
||||
last_sync_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_characters_user ON characters(user_id);
|
||||
|
||||
DROP TRIGGER IF EXISTS trg_characters_updated_at ON characters;
|
||||
CREATE TRIGGER trg_characters_updated_at BEFORE UPDATE ON characters
|
||||
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||
|
||||
-- =========================================================================
|
||||
-- 3. SKILL (hibrit: her skill ayrı XP, karakter level = toplam)
|
||||
-- =========================================================================
|
||||
CREATE TABLE IF NOT EXISTS skills (
|
||||
character_id BIGINT NOT NULL REFERENCES characters(id) ON DELETE CASCADE,
|
||||
skill_name VARCHAR(32) NOT NULL,
|
||||
xp BIGINT NOT NULL DEFAULT 0,
|
||||
level INTEGER NOT NULL DEFAULT 1 CHECK (level BETWEEN 1 AND 50),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
PRIMARY KEY (character_id, skill_name)
|
||||
);
|
||||
|
||||
DROP TRIGGER IF EXISTS trg_skills_updated_at ON skills;
|
||||
CREATE TRIGGER trg_skills_updated_at BEFORE UPDATE ON skills
|
||||
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||
|
||||
-- =========================================================================
|
||||
-- 4. ITEM CATALOG + ENVANTER
|
||||
-- =========================================================================
|
||||
CREATE TABLE IF NOT EXISTS items (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
code VARCHAR(64) NOT NULL UNIQUE,
|
||||
name VARCHAR(128) NOT NULL,
|
||||
type VARCHAR(32) NOT NULL,
|
||||
rarity VARCHAR(16) NOT NULL DEFAULT 'common',
|
||||
stack_max INTEGER NOT NULL DEFAULT 99,
|
||||
icon VARCHAR(128),
|
||||
description TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS inventory (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
character_id BIGINT NOT NULL REFERENCES characters(id) ON DELETE CASCADE,
|
||||
item_id BIGINT NOT NULL REFERENCES items(id),
|
||||
qty INTEGER NOT NULL DEFAULT 1 CHECK (qty > 0),
|
||||
slot INTEGER NOT NULL,
|
||||
UNIQUE (character_id, slot)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_inventory_char ON inventory(character_id);
|
||||
|
||||
-- =========================================================================
|
||||
-- 5. HARITA (chunk + node)
|
||||
-- =========================================================================
|
||||
CREATE TABLE IF NOT EXISTS map_chunks (
|
||||
chunk_x INTEGER NOT NULL,
|
||||
chunk_y INTEGER NOT NULL,
|
||||
biome VARCHAR(32) NOT NULL,
|
||||
tile_data JSONB,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
PRIMARY KEY (chunk_x, chunk_y)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS node_spawns (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
chunk_x INTEGER NOT NULL,
|
||||
chunk_y INTEGER NOT NULL,
|
||||
x INTEGER NOT NULL,
|
||||
y INTEGER NOT NULL,
|
||||
node_type VARCHAR(32) NOT NULL,
|
||||
item_id BIGINT REFERENCES items(id),
|
||||
skill_name VARCHAR(32) NOT NULL,
|
||||
required_level INTEGER NOT NULL DEFAULT 1,
|
||||
step_cost INTEGER NOT NULL DEFAULT 10,
|
||||
xp_reward INTEGER NOT NULL DEFAULT 10,
|
||||
respawn_at TIMESTAMPTZ,
|
||||
is_active BOOLEAN NOT NULL DEFAULT TRUE
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_node_chunk ON node_spawns(chunk_x, chunk_y) WHERE is_active;
|
||||
|
||||
-- =========================================================================
|
||||
-- 6. ADIM LOG (anti-cheat audit)
|
||||
-- =========================================================================
|
||||
CREATE TABLE IF NOT EXISTS step_log (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
character_id BIGINT NOT NULL REFERENCES characters(id) ON DELETE CASCADE,
|
||||
ts TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
steps_delta INTEGER NOT NULL,
|
||||
source VARCHAR(32) NOT NULL,
|
||||
accepted BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
reason TEXT
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_step_log_char_ts ON step_log(character_id, ts DESC);
|
||||
|
||||
-- =========================================================================
|
||||
-- 7. PAZAR + TAKAS
|
||||
-- =========================================================================
|
||||
CREATE TABLE IF NOT EXISTS market_listings (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
seller_id BIGINT NOT NULL REFERENCES characters(id) ON DELETE CASCADE,
|
||||
item_id BIGINT NOT NULL REFERENCES items(id),
|
||||
qty INTEGER NOT NULL CHECK (qty > 0),
|
||||
unit_price BIGINT NOT NULL CHECK (unit_price > 0),
|
||||
status VARCHAR(16) NOT NULL DEFAULT 'open',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
closed_at TIMESTAMPTZ
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_market_open ON market_listings(item_id, unit_price) WHERE status='open';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS trade_logs (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
seller_id BIGINT NOT NULL REFERENCES characters(id),
|
||||
buyer_id BIGINT NOT NULL REFERENCES characters(id),
|
||||
item_id BIGINT NOT NULL REFERENCES items(id),
|
||||
qty INTEGER NOT NULL,
|
||||
unit_price BIGINT NOT NULL,
|
||||
ts TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- =========================================================================
|
||||
-- 8. SOHBET
|
||||
-- =========================================================================
|
||||
CREATE TABLE IF NOT EXISTS chat_messages (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
channel VARCHAR(32) NOT NULL,
|
||||
sender_id BIGINT NOT NULL REFERENCES characters(id) ON DELETE CASCADE,
|
||||
text TEXT NOT NULL,
|
||||
ts TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_chat_channel_ts ON chat_messages(channel, ts DESC);
|
||||
|
||||
COMMIT;
|
||||
33
db/migrate_2026_07_04_market_daily.sql
Normal file
33
db/migrate_2026_07_04_market_daily.sql
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
-- 2026-07-04: Market/vendor ekonomisi + lokasyona özel olaylar + günlük hedef/streak
|
||||
-- İdempotent — tekrar çalıştırılabilir.
|
||||
|
||||
-- Vendor fiyatlandırması: rarity bazlı taban değer (gold)
|
||||
ALTER TABLE items ADD COLUMN IF NOT EXISTS base_value INTEGER NOT NULL DEFAULT 5;
|
||||
UPDATE items SET base_value = CASE rarity
|
||||
WHEN 'common' THEN 5
|
||||
WHEN 'uncommon' THEN 15
|
||||
WHEN 'rare' THEN 40
|
||||
WHEN 'epic' THEN 120
|
||||
WHEN 'legendary' THEN 400
|
||||
ELSE 5 END;
|
||||
-- Tool'lar hammaddeden değerli olsun
|
||||
UPDATE items SET base_value = base_value * 3 WHERE type = 'tool';
|
||||
|
||||
-- Olaylar lokasyona hedeflenebilir (NULL = her yerde çıkabilir).
|
||||
-- Eşleşme: seyahatin VARIŞ lokasyon kodu listede ise.
|
||||
ALTER TABLE events ADD COLUMN IF NOT EXISTS location_codes TEXT[];
|
||||
|
||||
-- Günlük hedef + streak
|
||||
ALTER TABLE characters ADD COLUMN IF NOT EXISTS daily_steps BIGINT NOT NULL DEFAULT 0;
|
||||
ALTER TABLE characters ADD COLUMN IF NOT EXISTS daily_date DATE;
|
||||
ALTER TABLE characters ADD COLUMN IF NOT EXISTS streak_days INTEGER NOT NULL DEFAULT 0;
|
||||
ALTER TABLE characters ADD COLUMN IF NOT EXISTS last_claim_date DATE;
|
||||
|
||||
-- Kamp/construction: karakter başına lokasyonda bir kamp (gather adım maliyeti -%25)
|
||||
CREATE TABLE IF NOT EXISTS camps (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
character_id BIGINT NOT NULL REFERENCES characters(id) ON DELETE CASCADE,
|
||||
location_id BIGINT NOT NULL REFERENCES locations(id),
|
||||
built_at timestamptz NOT NULL DEFAULT now(),
|
||||
UNIQUE(character_id, location_id)
|
||||
);
|
||||
25
db/migrate_2026_07_07_roles.sql
Normal file
25
db/migrate_2026_07_07_roles.sql
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
-- Rol tabanlı yetkilendirme (2026-07-07)
|
||||
-- İzin kodları: upload, lore, map_view, map_edit, users ("*" = hepsi)
|
||||
BEGIN;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS roles (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
name VARCHAR(40) UNIQUE NOT NULL,
|
||||
permissions JSONB NOT NULL DEFAULT '[]',
|
||||
is_system BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
INSERT INTO roles(name, permissions, is_system) VALUES
|
||||
('admin', '["*"]', TRUE),
|
||||
('design', '["upload"]', FALSE),
|
||||
('loremaster', '["lore","map_view"]', FALSE)
|
||||
ON CONFLICT (name) DO NOTHING;
|
||||
|
||||
ALTER TABLE users ADD COLUMN IF NOT EXISTS role_id BIGINT REFERENCES roles(id) ON DELETE SET NULL;
|
||||
|
||||
-- Mevcut admin hesapları (ADMIN_EMAILS ile aynı) admin rolüne bağla
|
||||
UPDATE users SET role_id = (SELECT id FROM roles WHERE name='admin')
|
||||
WHERE email IN ('jts@jts.rocks','afm@jts.rocks') AND role_id IS NULL;
|
||||
|
||||
COMMIT;
|
||||
17
db/migrate_2026_07_08_equipment.sql
Normal file
17
db/migrate_2026_07_08_equipment.sql
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
-- Stepstead — Ekipman yuvası + sınıf kullanım kısıtı (2026-07-08)
|
||||
-- items.equip_slot : eşya hangi yuvaya takılır (weapon/shield/head/chest/hands/feet/waist/back/neck/finger/wrist/tool/none)
|
||||
-- items.two_handed : çift el silah (yay, asa) — yan-el (kalkan) engeller
|
||||
-- item_classes : eşyayı KULLANABİLEN sınıflar. Kayıt YOKSA = tüm sınıflar kullanabilir (tool, mücevher, deri, tüketilebilir).
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE items ADD COLUMN IF NOT EXISTS equip_slot varchar(16);
|
||||
ALTER TABLE items ADD COLUMN IF NOT EXISTS two_handed boolean NOT NULL DEFAULT false;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS item_classes (
|
||||
item_id bigint NOT NULL REFERENCES items(id) ON DELETE CASCADE,
|
||||
class_id int NOT NULL REFERENCES game_classes(id) ON DELETE CASCADE,
|
||||
PRIMARY KEY (item_id, class_id)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_item_classes_class ON item_classes(class_id);
|
||||
|
||||
COMMIT;
|
||||
24
db/migrate_2026_07_09_city_vendors.sql
Normal file
24
db/migrate_2026_07_09_city_vendors.sql
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
-- Vendor NPC'leri şehirlere (locations) bağla + oyuncu konsinye stoğu (24s ömürlü).
|
||||
|
||||
-- 1) NPC → konum bağı
|
||||
ALTER TABLE npcs ADD COLUMN IF NOT EXISTS location_id BIGINT REFERENCES locations(id) ON DELETE SET NULL;
|
||||
|
||||
-- 7 vendor'ı 3 şehre dağıt (koy=1, pazar=2, liman=3)
|
||||
UPDATE npcs SET location_id=1 WHERE id IN (4,5,6); -- Köy: Demirci Baldor, Şifacı Elda, Oduncu Corvin
|
||||
UPDATE npcs SET location_id=2 WHERE id IN (7,9); -- Pazarköy: Demirci Torin, Şifacı Mira
|
||||
UPDATE npcs SET location_id=3 WHERE id IN (8,10); -- Liman: Balıkçı Neyla, Demirci Grum
|
||||
|
||||
-- 2) Konsinye stok: oyuncuların vendor'a sattığı ürünler, herkese satılık, 24s ömürlü.
|
||||
-- Vendor'ın sabit stoğu = npc_items (kalıcı). Bu tablo geçici oyuncu malları.
|
||||
CREATE TABLE IF NOT EXISTS vendor_stock (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
npc_id BIGINT NOT NULL REFERENCES npcs(id) ON DELETE CASCADE,
|
||||
item_id BIGINT NOT NULL REFERENCES items(id) ON DELETE CASCADE,
|
||||
qty INTEGER NOT NULL CHECK (qty > 0),
|
||||
unit_price INTEGER NOT NULL, -- alıcının ödeyeceği gold (vendor fiyatı)
|
||||
expires_at TIMESTAMPTZ NOT NULL, -- bu andan sonra kaybolur (satışta yenilenir)
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
UNIQUE (npc_id, item_id)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS vendor_stock_npc_idx ON vendor_stock(npc_id);
|
||||
CREATE INDEX IF NOT EXISTS vendor_stock_exp_idx ON vendor_stock(expires_at);
|
||||
3
db/migrate_2026_07_09_road_waypoints.sql
Normal file
3
db/migrate_2026_07_09_road_waypoints.sql
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
-- Yol geometrisi: iki lokasyon arası kıvrımlı hat için ara noktalar.
|
||||
-- waypoints = normalize (0-1) [[x,y],...] — uç noktalar (loc_a, loc_b) hariç.
|
||||
ALTER TABLE roads ADD COLUMN IF NOT EXISTS waypoints JSONB NOT NULL DEFAULT '[]'::jsonb;
|
||||
244
db/seed_craftables.py
Normal file
244
db/seed_craftables.py
Normal file
|
|
@ -0,0 +1,244 @@
|
|||
"""
|
||||
250 örnek craftable üretir: item + tarif. Idempotent (code üzerinden ON CONFLICT DO NOTHING).
|
||||
Gruplar: Kerestecilik, Marangozluk, Okçuluk, Metalurji, Demircilik, Kuyumculuk,
|
||||
Simya, Aşçılık, Dericilik, Balıkçılık.
|
||||
Çalıştırma: cd /opt/stepstead && ./venv/bin/python3 db/seed_craftables.py
|
||||
"""
|
||||
import asyncio
|
||||
import json
|
||||
import sys
|
||||
|
||||
import asyncpg
|
||||
|
||||
sys.path.insert(0, "/opt/stepstead")
|
||||
from config import settings
|
||||
|
||||
DSN = settings.DATABASE_URL.replace("postgresql+asyncpg://", "postgresql://")
|
||||
|
||||
# (kod, Türkçe ad, tier)
|
||||
WOODS = [
|
||||
("oak", "Meşe", 1), ("pine", "Çam", 2), ("birch", "Huş", 3), ("beech", "Kayın", 4),
|
||||
("maple", "Akçaağaç", 5), ("willow", "Söğüt", 6), ("ash", "Dişbudak", 7), ("cedar", "Sedir", 8),
|
||||
("walnut", "Ceviz", 9), ("cherry", "Kiraz", 10), ("yew", "Porsuk", 11),
|
||||
("mahogany", "Maun", 12), ("ebony", "Abanoz", 13),
|
||||
]
|
||||
METALS = [
|
||||
("copper", "Bakır", 1), ("bronze", "Bronz", 2), ("iron", "Demir", 3),
|
||||
("steel", "Çelik", 4), ("silver", "Gümüş", 5), ("gold", "Altın", 6),
|
||||
]
|
||||
HERBS = [
|
||||
("mint", "Nane", 1), ("sage", "Adaçayı", 2), ("thyme", "Kekik", 3), ("chamomile", "Papatya", 4),
|
||||
("rosemary", "Biberiye", 5), ("basil", "Fesleğen", 6), ("nettle", "Isırgan", 7),
|
||||
("lemonbalm", "Melisa", 8), ("fennel", "Rezene", 9), ("lavender", "Lavanta", 10),
|
||||
("ginseng", "Ginseng", 11), ("saffron", "Safran", 12),
|
||||
]
|
||||
FISH = [
|
||||
("carp", "Sazan", 1), ("perch", "Levrek", 2), ("anchovy", "Hamsi", 3), ("mullet", "Kefal", 4),
|
||||
("mackerel", "Uskumru", 5), ("bonito", "Palamut", 6), ("bluefish", "Lüfer", 7),
|
||||
("bream", "Çipura", 8), ("trout", "Alabalık", 9), ("pike", "Turna", 10),
|
||||
("catfish", "Yayın", 11), ("salmon", "Somon", 12), ("turbot", "Kalkan", 13),
|
||||
("tuna", "Ton Balığı", 14), ("sturgeon", "Mersin", 15),
|
||||
]
|
||||
ANIMALS = [
|
||||
("rabbit", "Tavşan", 1), ("deer", "Geyik", 2), ("boar", "Yaban Domuzu", 3),
|
||||
("wolf", "Kurt", 4), ("bear", "Ayı", 5),
|
||||
]
|
||||
|
||||
FURNITURE = [("chair", "Sandalye", 3), ("table", "Masa", 4), ("bed", "Yatak", 6),
|
||||
("cabinet", "Dolap", 5), ("door", "Kapı", 4), ("chest", "Sandık", 5)] # (kod, ad, kereste)
|
||||
WEAPONS = [("axe", "Balta"), ("pickaxe", "Kazma"), ("sword", "Kılıç"),
|
||||
("dagger", "Hançer"), ("shield", "Kalkan"), ("armor", "Zırh")]
|
||||
JEWELRY = [("ring", "Yüzük"), ("necklace", "Kolye"), ("bracelet", "Bilezik")]
|
||||
LEATHERGOODS = [("bag", "Çanta"), ("belt", "Kemer"), ("boots", "Bot"), ("gloves", "Eldiven")]
|
||||
|
||||
|
||||
def rarity_for(tier: int, max_tier: int) -> str:
|
||||
r = tier / max_tier
|
||||
if r <= 0.25:
|
||||
return "common"
|
||||
if r <= 0.5:
|
||||
return "uncommon"
|
||||
if r <= 0.75:
|
||||
return "rare"
|
||||
if r < 1.0:
|
||||
return "epic"
|
||||
return "legendary"
|
||||
|
||||
|
||||
items: list[dict] = [] # {code,name,type,rarity,stack_max,description,effect}
|
||||
recipes: list[dict] = [] # {code,name,out,out_qty,skill,level,xp,steps,ings}
|
||||
|
||||
|
||||
def add_item(code, name, typ, rarity="common", stack=99, desc=None, effect=None):
|
||||
items.append({"code": code, "name": name, "type": typ, "rarity": rarity,
|
||||
"stack": stack, "desc": desc, "effect": effect})
|
||||
|
||||
|
||||
def add_recipe(code, name, out, skill, level, xp, steps, ings, out_qty=1):
|
||||
recipes.append({"code": code, "name": name, "out": out, "out_qty": out_qty,
|
||||
"skill": skill, "level": min(level, 50), "xp": xp, "steps": steps,
|
||||
"ings": [{"item_code": c, "qty": q} for c, q in ings]})
|
||||
|
||||
|
||||
def build():
|
||||
# --- Ham maddeler ---
|
||||
for w, tr, t in WOODS:
|
||||
add_item(f"log_{w}", f"{tr} Odunu", "material", rarity_for(t, 13))
|
||||
for m, tr, t in METALS:
|
||||
if m in ("bronze", "steel"):
|
||||
pass # cevheri yok, alaşım
|
||||
else:
|
||||
add_item(f"ore_{m}", f"{tr} Cevheri", "material", rarity_for(t, 6))
|
||||
add_item(f"ingot_{m}", f"{tr} Külçe", "material", rarity_for(t, 6))
|
||||
add_item("ore_tin", "Kalay Cevheri", "material", "common")
|
||||
add_item("coal", "Kömür", "material", "common")
|
||||
for h, tr, t in HERBS:
|
||||
add_item(f"herb_{h}", f"{tr}", "material", rarity_for(t, 12))
|
||||
for f, tr, t in FISH:
|
||||
add_item(f"fish_{f}", f"{tr}", "material", rarity_for(t, 15))
|
||||
for a, tr, t in ANIMALS:
|
||||
add_item(f"leather_{a}", f"{tr} Derisi", "material", rarity_for(t, 5))
|
||||
add_item(f"meat_{a}", f"{tr} Eti", "material", rarity_for(t, 5))
|
||||
|
||||
# --- 1) Kerestecilik: 13 kereste ---
|
||||
for w, tr, t in WOODS:
|
||||
add_item(f"plank_{w}", f"{tr} Kereste", "material", rarity_for(t, 13))
|
||||
add_recipe(f"saw_{w}", f"{tr} Kereste", f"plank_{w}", "woodcutting",
|
||||
max(1, t * 3 - 2), 4 + 2 * t, 2 + t, [(f"log_{w}", 2)], out_qty=2)
|
||||
|
||||
# --- 2) Marangozluk: 13 odun × 6 mobilya = 78 ---
|
||||
for w, wtr, t in WOODS:
|
||||
for fc, ftr, planks in FURNITURE:
|
||||
add_item(f"{fc}_{w}", f"{wtr} {ftr}", "furniture", rarity_for(t, 13), stack=10)
|
||||
add_recipe(f"build_{fc}_{w}", f"{wtr} {ftr}", f"{fc}_{w}", "construction",
|
||||
max(1, t * 3 - 2), 8 + 3 * t, 4 + t + planks, [(f"plank_{w}", planks)])
|
||||
|
||||
# --- 3) Okçuluk: 13 yay + 13 ok = 26 ---
|
||||
for w, wtr, t in WOODS:
|
||||
add_item(f"bow_{w}", f"{wtr} Yay", "tool", rarity_for(t, 13), stack=1)
|
||||
add_recipe(f"craft_bow_{w}", f"{wtr} Yay", f"bow_{w}", "combat",
|
||||
max(1, t * 3 - 2), 10 + 3 * t, 6 + t, [(f"plank_{w}", 2), ("vine", 1)])
|
||||
add_item(f"arrow_{w}", f"{wtr} Ok", "material", rarity_for(t, 13))
|
||||
add_recipe(f"craft_arrow_{w}", f"{wtr} Ok (10)", f"arrow_{w}", "combat",
|
||||
max(1, t * 3 - 2), 5 + 2 * t, 3 + t, [(f"plank_{w}", 1), ("stone_small", 1)], out_qty=10)
|
||||
|
||||
# --- 4) Metalurji: 4 yeni külçe (bakır+demir eritme zaten var) ---
|
||||
add_recipe("smelt_bronze", "Bronz Külçe", "ingot_bronze", "smithing", 4, 12, 5,
|
||||
[("ore_copper", 1), ("ore_tin", 1)])
|
||||
add_recipe("smelt_steel", "Çelik Külçe", "ingot_steel", "smithing", 10, 18, 6,
|
||||
[("ore_iron", 2), ("coal", 1)])
|
||||
add_recipe("smelt_silver", "Gümüş Külçe", "ingot_silver", "smithing", 15, 22, 7, [("ore_silver", 2)])
|
||||
add_recipe("smelt_gold", "Altın Külçe", "ingot_gold", "smithing", 20, 28, 8, [("ore_gold", 2)])
|
||||
|
||||
# --- 5) Demircilik: 6 metal × 6 = 36, bakır balta+kazma mevcut → 34 ---
|
||||
for m, mtr, t in METALS:
|
||||
for wc, wtr in WEAPONS:
|
||||
if m == "copper" and wc in ("axe", "pickaxe"):
|
||||
continue # craft_axe_cu / craft_pick_cu zaten var
|
||||
code = f"{wc}_{m}"
|
||||
typ = "tool" if wc in ("axe", "pickaxe") else "gear"
|
||||
add_item(code, f"{mtr} {wtr}", typ, rarity_for(t, 6), stack=1)
|
||||
n_ingot = 3 if wc in ("shield", "armor") else 2
|
||||
add_recipe(f"forge_{wc}_{m}", f"{mtr} {wtr}", code, "smithing",
|
||||
max(1, t * 5 - 3), 10 + 5 * t, 6 + 2 * t,
|
||||
[(f"ingot_{m}", n_ingot), ("log_oak", 1)])
|
||||
|
||||
# --- 6) Kuyumculuk: gümüş+altın × 3 takı = 6 ---
|
||||
for m, mtr, t in [("silver", "Gümüş", 5), ("gold", "Altın", 6)]:
|
||||
for jc, jtr in JEWELRY:
|
||||
add_item(f"{jc}_{m}", f"{mtr} {jtr}", "gear", rarity_for(t, 6), stack=1)
|
||||
add_recipe(f"jewel_{jc}_{m}", f"{mtr} {jtr}", f"{jc}_{m}", "smithing",
|
||||
t * 5, 15 + 4 * t, 8, [(f"ingot_{m}", 1)])
|
||||
|
||||
# --- 7) Simya: 12 ot × 3 = 36, nane çayı mevcut → 35 ---
|
||||
for h, htr, t in HERBS:
|
||||
if h != "mint": # brew_tea_mint zaten var
|
||||
add_item(f"tea_{h}", f"{htr} Çayı", "consumable", rarity_for(t, 12),
|
||||
desc=f"Adım bakiyesini +{40 + 5 * t} yeniler.",
|
||||
effect={"type": "steps", "amount": 40 + 5 * t})
|
||||
add_recipe(f"brew_tea_{h}", f"{htr} Çayı", f"tea_{h}", "alchemy",
|
||||
max(1, t * 4 - 3), 6 + 2 * t, 4, [(f"herb_{h}", 2)])
|
||||
add_item(f"potion_{h}", f"{htr} İksiri", "consumable", rarity_for(t, 12),
|
||||
desc=f"Adım bakiyesini +{80 + 10 * t} yeniler.",
|
||||
effect={"type": "steps", "amount": 80 + 10 * t})
|
||||
add_recipe(f"brew_potion_{h}", f"{htr} İksiri", f"potion_{h}", "alchemy",
|
||||
min(50, t * 4), 12 + 3 * t, 6, [(f"herb_{h}", 3), ("vine", 1)])
|
||||
add_item(f"salve_{h}", f"{htr} Merhemi", "consumable", rarity_for(t, 12),
|
||||
desc="İyileştirici merhem (gelecek özellik).")
|
||||
add_recipe(f"brew_salve_{h}", f"{htr} Merhemi", f"salve_{h}", "alchemy",
|
||||
min(50, t * 4 + 1), 10 + 3 * t, 5, [(f"herb_{h}", 2), ("stone_small", 1)])
|
||||
|
||||
# --- 8) Aşçılık: 14 balık (sazan mevcut) + 5 et × 3 = 29 ---
|
||||
for f, ftr, t in FISH:
|
||||
if f == "carp":
|
||||
continue # cook_carp zaten var
|
||||
add_item(f"cooked_{f}", f"Izgara {ftr}", "consumable", rarity_for(t, 15),
|
||||
desc=f"Adım bakiyesini +{25 + 3 * t} yeniler.",
|
||||
effect={"type": "steps", "amount": 25 + 3 * t})
|
||||
add_recipe(f"cook_{f}", f"Izgara {ftr}", f"cooked_{f}", "cooking",
|
||||
max(1, t * 3 - 2), 6 + 2 * t, 4, [(f"fish_{f}", 1)])
|
||||
for a, atr, t in ANIMALS:
|
||||
for mc, mtr, bonus in [("grilled", "Izgara", 30), ("stew", "Güveç", 50), ("dried", "Kurutulmuş", 40)]:
|
||||
add_item(f"{mc}_{a}", f"{mtr} {atr} Eti", "consumable", rarity_for(t, 5),
|
||||
desc=f"Adım bakiyesini +{bonus + 8 * t} yeniler.",
|
||||
effect={"type": "steps", "amount": bonus + 8 * t})
|
||||
ings = [(f"meat_{a}", 1)] if mc != "stew" else [(f"meat_{a}", 1), ("herb_mint", 1)]
|
||||
add_recipe(f"cook_{mc}_{a}", f"{mtr} {atr} Eti", f"{mc}_{a}", "cooking",
|
||||
min(50, t * 6), 8 + 4 * t, 5, ings)
|
||||
|
||||
# --- 9) Dericilik: 5 deri × 4 = 20 ---
|
||||
for a, atr, t in ANIMALS:
|
||||
for lc, ltr in LEATHERGOODS:
|
||||
add_item(f"{lc}_{a}", f"{atr} Derisi {ltr}", "gear", rarity_for(t, 5), stack=1)
|
||||
add_recipe(f"tailor_{lc}_{a}", f"{atr} Derisi {ltr}", f"{lc}_{a}", "hunting",
|
||||
min(50, t * 7 - 4), 10 + 5 * t, 6 + t, [(f"leather_{a}", 2), ("vine", 1)])
|
||||
|
||||
# --- 10) Balıkçılık: 2 gelişmiş olta ---
|
||||
add_item("fishing_rod_fine", "Usta Oltası", "tool", "rare", stack=1)
|
||||
add_recipe("craft_rod_fine", "Usta Oltası", "fishing_rod_fine", "fishing", 10, 25, 8,
|
||||
[("plank_willow", 2), ("vine", 2)])
|
||||
add_item("fishing_rod_master", "Efsane Olta", "tool", "epic", stack=1)
|
||||
add_recipe("craft_rod_master", "Efsane Olta", "fishing_rod_master", "fishing", 25, 50, 12,
|
||||
[("plank_yew", 2), ("vine", 3), ("ingot_silver", 1)])
|
||||
|
||||
# --- 11) İnşaat ekstra: 3 ---
|
||||
add_item("campfire", "Kamp Ateşi", "furniture", "common", stack=10)
|
||||
add_recipe("build_campfire", "Kamp Ateşi", "campfire", "construction", 1, 8, 5,
|
||||
[("stick", 3), ("stone_small", 2)])
|
||||
add_item("fence", "Çit", "furniture", "common", stack=50)
|
||||
add_recipe("build_fence", "Çit", "fence", "construction", 2, 6, 4, [("plank_pine", 1), ("vine", 1)])
|
||||
add_item("sign", "Tabela", "furniture", "common", stack=20)
|
||||
add_recipe("build_sign", "Tabela", "sign", "construction", 1, 5, 3, [("plank_oak", 1), ("stick", 1)])
|
||||
|
||||
|
||||
async def main():
|
||||
build()
|
||||
print(f"Üretilen: {len(recipes)} tarif, {len(items)} item tanımı")
|
||||
assert len(recipes) == 250, f"250 bekleniyor, {len(recipes)} üretildi"
|
||||
|
||||
conn = await asyncpg.connect(DSN)
|
||||
ins_i = 0
|
||||
for it in items:
|
||||
r = await conn.execute("""
|
||||
INSERT INTO items (code, name, type, rarity, stack_max, description, effect)
|
||||
VALUES ($1,$2,$3,$4,$5,$6,$7) ON CONFLICT (code) DO NOTHING
|
||||
""", it["code"], it["name"], it["type"], it["rarity"], it["stack"],
|
||||
it["desc"], json.dumps(it["effect"]) if it["effect"] else None)
|
||||
ins_i += int(r.endswith("1"))
|
||||
ins_r = 0
|
||||
for rc in recipes:
|
||||
r = await conn.execute("""
|
||||
INSERT INTO recipes (code, name, output_item_id, output_qty, skill, required_level,
|
||||
xp_reward, step_cost, ingredients)
|
||||
SELECT $1,$2,i.id,$3,$4,$5,$6,$7,$8::jsonb FROM items i WHERE i.code=$9
|
||||
ON CONFLICT (code) DO NOTHING
|
||||
""", rc["code"], rc["name"], rc["out_qty"], rc["skill"], rc["level"],
|
||||
rc["xp"], rc["steps"], json.dumps(rc["ings"]), rc["out"])
|
||||
ins_r += int(r.endswith("1"))
|
||||
ti = await conn.fetchval("SELECT count(*) FROM items")
|
||||
tr = await conn.fetchval("SELECT count(*) FROM recipes")
|
||||
await conn.close()
|
||||
print(f"DB'ye eklendi: {ins_i} item, {ins_r} tarif. Toplam: {ti} item, {tr} tarif.")
|
||||
|
||||
|
||||
asyncio.run(main())
|
||||
166
db/seed_enemies_forest.py
Normal file
166
db/seed_enemies_forest.py
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
"""
|
||||
Orman düşmanları — 12 çeşit hostile NPC (2026-07-08).
|
||||
|
||||
NPC sayfasının yazdığı `npcs` tablosuna eklenir (stance='hostile').
|
||||
Yer = forest region'larına (map_tags kind='region' category='forest', O-1..O-10)
|
||||
round-robin dağıtılır. İsim'e göre idempotent (varsa günceller, yoksa ekler).
|
||||
|
||||
Çalıştırma:
|
||||
cd /opt/stepstead && ./venv/bin/python3 db/seed_enemies_forest.py
|
||||
"""
|
||||
import asyncio
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, "/opt/stepstead")
|
||||
|
||||
import asyncpg # noqa: E402
|
||||
from config import settings # noqa: E402
|
||||
|
||||
|
||||
def desc(hp, dmg, atk, dc, weak, drops, danger, lore):
|
||||
"""Özellikleri okunur bir description bloğuna paketle."""
|
||||
return (
|
||||
f"{lore}\n\n"
|
||||
f"⚔ Saldırı türü: {atk}\n"
|
||||
f"❤ Can (HP): {hp}\n"
|
||||
f"🗡 Hasar: {dmg}\n"
|
||||
f"🎯 Savaş zorluğu (combat DC): {dc}\n"
|
||||
f"💀 Tehlike: {danger}\n"
|
||||
f"🩹 Zayıflık: {weak}\n"
|
||||
f"🎁 Düşürdükleri: {drops}"
|
||||
)
|
||||
|
||||
|
||||
# (name, role, stance, description)
|
||||
ENEMIES = [
|
||||
("Mana Solucanı", "Büyülü Yaratık", "hostile",
|
||||
desc(hp=18, dmg="4-8 (mana yakımı)", atk="Büyü — mana emer",
|
||||
dc=13, weak="Fiziksel darbe, demir silah",
|
||||
drops="Mana özü ×1-2, parlak salgı",
|
||||
danger="Orta",
|
||||
lore="Yeraltı mana damarlarında beslenen, ışıldayan segmentli bir solucan. "
|
||||
"Yaklaşana enerjisini emer; öldürüldüğünde içindeki saf mana damlar.")),
|
||||
|
||||
("Aç Kurt", "Canavar", "hostile",
|
||||
desc(hp=22, dmg="6-10 (ısırık)", atk="Yakın dövüş — sürü halinde",
|
||||
dc=13, weak="Ateş, yüksek zemin",
|
||||
drops="Kurt eti ×1-2, kurt derisi ×1",
|
||||
danger="Orta",
|
||||
lore="Kışın açlıktan gözü dönmüş orman kurdu. Tek başına ürkek ama "
|
||||
"sürüye katılınca korkusuzca saldırır.")),
|
||||
|
||||
("Yaşlı Ağaç Adam", "Bitki Devi", "hostile",
|
||||
desc(hp=55, dmg="12-18 (dal savurma)", atk="Yakın dövüş — ağır ve yavaş",
|
||||
dc=16, weak="Ateş, balta (woodcutting)",
|
||||
drops="Kadim odun ×2-3, canlı kök, meşe palamudu",
|
||||
danger="Yüksek",
|
||||
lore="Yüzyıllık bir meşenin uyanmış ruhu. Toprağı sarsan adımlarla yürür; "
|
||||
"yavaştır ama tek darbesi kemik kırar. Ateşten ölesiye korkar.")),
|
||||
|
||||
("Dev Orman Faresi", "Haşere", "hostile",
|
||||
desc(hp=8, dmg="2-4 (kemirme)", atk="Yakın dövüş — kalabalık",
|
||||
dc=9, weak="Hemen her şey",
|
||||
drops="Fare kuyruğu, paslı çöp, bazen 1-3 gold",
|
||||
danger="Düşük",
|
||||
lore="İnsan boyunun yarısı kadar, hastalık taşıyan bir kemirgen. Tek başına "
|
||||
"zararsız ama erzak çuvalını bir gecede boşaltır.")),
|
||||
|
||||
("Zehirli Orman Örümceği", "Haşere", "hostile",
|
||||
desc(hp=16, dmg="5-9 (zehirli ısırık)", atk="Yakın dövüş — zehir + ağ",
|
||||
dc=14, weak="Ateş, alan silahları",
|
||||
drops="Örümcek zehri ×1, ipek ×1-2",
|
||||
danger="Orta",
|
||||
lore="Karanlık kovuklara dev ağlar geren, avını zehirle felç eden bir örümcek. "
|
||||
"Ağı hareketi yavaşlatır, ısırığı damarlarda yanar.")),
|
||||
|
||||
("Sarmaşık Cini", "Bitki Ruhu", "hostile",
|
||||
desc(hp=24, dmg="6-11 (sarma + diken)", atk="Yakın dövüş — kavrayıp sıkar",
|
||||
dc=14, weak="Ateş, kesici balta",
|
||||
drops="Canlı sarmaşık ×2, diken tozu, çiçek özü",
|
||||
danger="Orta",
|
||||
lore="Küçük ama sinsi bir bitki ruhu. Yaprakların arasına gizlenir, "
|
||||
"geçeni sarmaşıklarıyla sarıp dikenlerine çeker.")),
|
||||
|
||||
("Mantar Spor Yaratığı", "Sporlu Canavar", "hostile",
|
||||
desc(hp=20, dmg="4-7 (spor bulutu)", atk="Alan — zehirli spor patlaması",
|
||||
dc=13, weak="Ateş, uzak menzil",
|
||||
drops="Işıldayan mantar ×2, spor tozu, şifa mantarı",
|
||||
danger="Orta",
|
||||
lore="Yürüyen dev bir mantar kümesi. Vurulunca zehirli spor bulutu patlatır; "
|
||||
"yakın dövüşte nefes almak tehlikelidir.")),
|
||||
|
||||
("Gölge Kurdu (Alfa)", "Canavar (Elit)", "hostile",
|
||||
desc(hp=40, dmg="10-16 (gölge pençesi)", atk="Yakın dövüş — hızlı, görünmez sıçrar",
|
||||
dc=17, weak="Işık, gümüş silah",
|
||||
drops="Gölge postu ×1, alfa dişi, gümüş ore ×1",
|
||||
danger="Yüksek",
|
||||
lore="Karanlık ormanın en yaşlı kurt sürüsünün lideri. Gölgelerde erir, "
|
||||
"arkandan belirir. Işık tuttuğunda bir an donar — tek fırsatın odur.")),
|
||||
|
||||
("Orman Haydutu", "Goblin", "hostile",
|
||||
desc(hp=26, dmg="7-12 (paslı hançer)", atk="Yakın dövüş — pusu + hırsızlık",
|
||||
dc=14, weak="Cephesel saldırı, gürültü",
|
||||
drops="Çalıntı kese (5-20 gold), paslı hançer, kuru erzak",
|
||||
danger="Orta",
|
||||
lore="Yol kenarında pusu kuran sinsi bir goblin. Önce keseni çalmaya çalışır, "
|
||||
"başaramazsa hançerine sarılır.")),
|
||||
|
||||
("Kanlı Yarasa", "Uçan Haşere", "hostile",
|
||||
desc(hp=12, dmg="3-6 (kan emme)", atk="Havadan — dalış + kan emer",
|
||||
dc=11, weak="Ateş, geniş savurma",
|
||||
drops="Yarasa kanadı ×1-2, kan kesesi",
|
||||
danger="Düşük",
|
||||
lore="Mağara ağızlarında ve yaşlı ağaç kovuklarında asılı duran kan emici. "
|
||||
"Sürü halinde daldığında kanını damla damla çeker.")),
|
||||
|
||||
("Bataklık Sülüğü", "Asalak", "hostile",
|
||||
desc(hp=14, dmg="3-7 (yapışıp emer)", atk="Yakın dövüş — yapışır, sürekli hasar",
|
||||
dc=12, weak="Tuz, ateş",
|
||||
drops="Sülük özü, iyileştirici salgı ×1",
|
||||
danger="Düşük-Orta",
|
||||
lore="Ormanın nemli bataklık kıyısında saklanan dev sülük. Bacağına yapışır, "
|
||||
"koparana dek yavaşça kan emer. Salgısı ilginç şekilde şifalıdır.")),
|
||||
|
||||
("Lanetli Karga Sürüsü", "Uçan Sürü", "hostile",
|
||||
desc(hp=30, dmg="5-9 (gaga yağmuru)", atk="Alan — sürü halinde gözlere dalış",
|
||||
dc=15, weak="Alan silahları, ateş, ses",
|
||||
drops="Kara tüy ×3, lanetli boncuk, bazen gümüş ore",
|
||||
danger="Orta-Yüksek",
|
||||
lore="Tek bir kötü ruhu paylaşan bir karga sürüsü. Ayrı ayrı zayıf ama "
|
||||
"birlikte gökten kara bir bulut gibi iner, gözleri hedef alır.")),
|
||||
]
|
||||
|
||||
|
||||
async def main():
|
||||
dsn = settings.DATABASE_URL.replace("+asyncpg", "")
|
||||
c = await asyncpg.connect(dsn)
|
||||
|
||||
forests = [r["id"] for r in await c.fetch(
|
||||
"SELECT id FROM map_tags WHERE kind='region' AND category='forest' ORDER BY id"
|
||||
)]
|
||||
if not forests:
|
||||
print("UYARI: forest region bulunamadı, map_tag_id NULL bırakılıyor.")
|
||||
|
||||
added = updated = 0
|
||||
for i, (name, role, stance, description) in enumerate(ENEMIES):
|
||||
tag = forests[i % len(forests)] if forests else None
|
||||
existing = await c.fetchval("SELECT id FROM npcs WHERE name=$1", name)
|
||||
if existing:
|
||||
await c.execute(
|
||||
"UPDATE npcs SET role=$2, stance=$3, description=$4, map_tag_id=$5, "
|
||||
"updated_at=now() WHERE id=$1",
|
||||
existing, role, stance, description, tag)
|
||||
updated += 1
|
||||
else:
|
||||
await c.execute(
|
||||
"INSERT INTO npcs(name,role,stance,description,map_tag_id) "
|
||||
"VALUES($1,$2,$3,$4,$5)",
|
||||
name, role, stance, description, tag)
|
||||
added += 1
|
||||
|
||||
total = await c.fetchval("SELECT count(*) FROM npcs WHERE stance='hostile'")
|
||||
print(f"Eklendi: {added}, güncellendi: {updated}. Toplam hostile NPC: {total}")
|
||||
await c.close()
|
||||
|
||||
|
||||
asyncio.run(main())
|
||||
134
db/seed_equipment.py
Normal file
134
db/seed_equipment.py
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Stepstead — Ekipman yuvaları + sınıf kullanım kısıtları + eksik eşyalar (2026-07-08).
|
||||
Idempotent: tekrar çalıştırılabilir.
|
||||
|
||||
Yapar:
|
||||
1. Caster silahı: 3 Asa (staff) + tarifleri (combat, plank+ingot).
|
||||
2. Orphan balık fish_mullet'i Fırtına Kıyısı'na kaynak olarak ekler.
|
||||
3. items.equip_slot + two_handed alanlarını kod önekinden doldurur.
|
||||
4. item_classes tablosunu doldurur — hangi sınıf hangi eşyayı KULLANABİLİR.
|
||||
(Kayıt yoksa = tüm sınıflar kullanabilir: tool, mücevher, deri, tüketilebilir.)
|
||||
|
||||
Çalıştırma: /opt/stepstead/venv/bin/python3 /opt/stepstead/db/seed_equipment.py
|
||||
"""
|
||||
import asyncio
|
||||
import json
|
||||
import sys
|
||||
|
||||
import asyncpg
|
||||
|
||||
sys.path.insert(0, "/opt/stepstead")
|
||||
from config import settings
|
||||
|
||||
DSN = settings.DATABASE_URL.replace("postgresql+asyncpg://", "postgresql://")
|
||||
|
||||
# --- Sınıf id'leri (game_classes) ---
|
||||
PALADIN, PRIEST, MAGE, HUNTER, WARRIOR, MONK, THIEF, WARLOCK = 2, 3, 4, 5, 6, 7, 8, 9
|
||||
|
||||
# --- Yeni caster silahı: Asa (staff) ---
|
||||
# (code, ad, rarity, req_level, plank_family, cap_ingot, base_value)
|
||||
STAVES = [
|
||||
("staff_ash", "Dişbudak Asa", "rare", 20, "plank_ash", "ingot_silver", 130),
|
||||
("staff_yew", "Porsuk Asa", "epic", 30, "plank_yew", "ingot_gold", 380),
|
||||
("staff_ebony", "Abanoz Asa", "legendary", 40, "plank_ebony", "ingot_gold", 1250),
|
||||
]
|
||||
|
||||
# --- Ekipman yuvası: kod öneki -> slot ---
|
||||
SLOT_PREFIX = [
|
||||
("sword_", "weapon"), ("dagger_", "weapon"), ("bow_", "weapon"), ("staff_", "weapon"),
|
||||
("shield_", "shield"), ("armor_", "chest"),
|
||||
("boots_", "feet"), ("gloves_", "hands"), ("belt_", "waist"), ("bag_", "back"),
|
||||
("necklace_", "neck"), ("ring_", "finger"), ("bracelet_", "wrist"),
|
||||
("axe_", "tool"), ("pickaxe_", "tool"), ("fishing_rod", "tool"),
|
||||
]
|
||||
TWO_HANDED_PREFIX = ("bow_", "staff_")
|
||||
|
||||
# --- Sınıf kullanım kısıtı: kod öneki -> izinli sınıf id listesi ---
|
||||
# Liste boşsa (öneki burada yoksa) eşya herkese açıktır.
|
||||
CLASS_PREFIX = [
|
||||
("sword_", [PALADIN, WARRIOR]),
|
||||
("shield_", [PALADIN, WARRIOR]),
|
||||
("armor_", [PALADIN, WARRIOR]),
|
||||
("bow_", [HUNTER]),
|
||||
("dagger_", [PRIEST, MAGE, HUNTER, MONK, THIEF, WARLOCK]),
|
||||
("staff_", [PRIEST, MAGE, WARLOCK]),
|
||||
]
|
||||
|
||||
|
||||
async def upsert_item(conn, code, name, itype, rarity, base_value, description):
|
||||
return await conn.fetchval("""
|
||||
INSERT INTO items(code,name,type,rarity,base_value,description)
|
||||
VALUES($1,$2,$3,$4,$5,$6)
|
||||
ON CONFLICT(code) DO UPDATE SET name=EXCLUDED.name, type=EXCLUDED.type,
|
||||
rarity=EXCLUDED.rarity, base_value=EXCLUDED.base_value, description=EXCLUDED.description
|
||||
RETURNING id
|
||||
""", code, name, itype, rarity, base_value, description)
|
||||
|
||||
|
||||
async def upsert_recipe(conn, code, name, out_id, skill, lvl, xp, steps, ings):
|
||||
await conn.execute("""
|
||||
INSERT INTO recipes(code,name,output_item_id,output_qty,skill,required_level,xp_reward,step_cost,ingredients)
|
||||
VALUES($1,$2,$3,1,$4,$5,$6,$7,$8::jsonb)
|
||||
ON CONFLICT(code) DO UPDATE SET name=EXCLUDED.name, output_item_id=EXCLUDED.output_item_id,
|
||||
skill=EXCLUDED.skill, required_level=EXCLUDED.required_level, xp_reward=EXCLUDED.xp_reward,
|
||||
step_cost=EXCLUDED.step_cost, ingredients=EXCLUDED.ingredients
|
||||
""", code, name, out_id, skill, lvl, xp, steps, json.dumps(ings))
|
||||
|
||||
|
||||
async def main():
|
||||
conn = await asyncpg.connect(DSN)
|
||||
|
||||
# 1) Asalar + tarifleri
|
||||
for code, name, rarity, lvl, plank, ingot, val in STAVES:
|
||||
iid = await upsert_item(conn, code, name, "gear", rarity, val,
|
||||
"Büyücü sınıflarının kullandığı çift el asa.")
|
||||
await upsert_recipe(conn, f"craft_{code}", name, iid, "combat", lvl,
|
||||
lvl * 4, 12,
|
||||
[{"qty": 2, "item_code": plank}, {"qty": 1, "item_code": ingot},
|
||||
{"qty": 1, "item_code": "vine"}])
|
||||
|
||||
# 2) Orphan balık -> Fırtına Kıyısı
|
||||
kiyi = await conn.fetchval("SELECT id FROM locations WHERE code='firtina_kiyisi'")
|
||||
if kiyi and await conn.fetchval("SELECT 1 FROM items WHERE code='fish_mullet'"):
|
||||
await conn.execute("""
|
||||
INSERT INTO location_resources(location_id,item_code,skill,step_cost,xp,cooldown_s)
|
||||
VALUES($1,'fish_mullet','fishing',6,14,300)
|
||||
ON CONFLICT(location_id,item_code) DO NOTHING
|
||||
""", kiyi)
|
||||
|
||||
# 3) equip_slot + two_handed
|
||||
items = await conn.fetch("SELECT id, code FROM items")
|
||||
for r in items:
|
||||
code = r["code"]
|
||||
slot = None
|
||||
for pref, s in SLOT_PREFIX:
|
||||
if code.startswith(pref):
|
||||
slot = s
|
||||
break
|
||||
two = any(code.startswith(p) for p in TWO_HANDED_PREFIX)
|
||||
await conn.execute("UPDATE items SET equip_slot=$1, two_handed=$2 WHERE id=$3",
|
||||
slot, two, r["id"])
|
||||
|
||||
# 4) item_classes — sınıf kullanım kısıtı
|
||||
await conn.execute("TRUNCATE item_classes")
|
||||
n = 0
|
||||
for r in items:
|
||||
code = r["code"]
|
||||
for pref, class_ids in CLASS_PREFIX:
|
||||
if code.startswith(pref):
|
||||
for cid in class_ids:
|
||||
await conn.execute(
|
||||
"INSERT INTO item_classes(item_id,class_id) VALUES($1,$2) ON CONFLICT DO NOTHING",
|
||||
r["id"], cid)
|
||||
n += 1
|
||||
break
|
||||
|
||||
slot_counts = await conn.fetch(
|
||||
"SELECT COALESCE(equip_slot,'-') slot, count(*) c FROM items GROUP BY 1 ORDER BY 2 DESC")
|
||||
await conn.close()
|
||||
print(f"Asalar+tarifler eklendi ({len(STAVES)}). item_classes satır: {n}")
|
||||
print("equip_slot dağılımı:", {r["slot"]: r["c"] for r in slot_counts})
|
||||
|
||||
|
||||
asyncio.run(main())
|
||||
152
db/seed_events_combat.py
Normal file
152
db/seed_events_combat.py
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
"""
|
||||
Lokasyona özel olaylar — combat/hunting ağırlıklı 8 yeni kart (2026-07-04).
|
||||
|
||||
events.location_codes: seyahatin VARIŞ lokasyonu listede ise olay çıkabilir;
|
||||
NULL = her yerde. Çalıştırma (idempotent):
|
||||
cd /opt/stepstead && ./venv/bin/python3 db/seed_events_combat.py
|
||||
"""
|
||||
import asyncio
|
||||
import json
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, "/opt/stepstead")
|
||||
|
||||
import asyncpg # noqa: E402
|
||||
from config import settings # noqa: E402
|
||||
|
||||
EVENTS = [
|
||||
{
|
||||
"code": "kurt_surusu",
|
||||
"name": "Kurt Sürüsü",
|
||||
"text": "Alacakaranlıkta parlayan gözler... Bir kurt sürüsü yolunu kesti. Liderleri dişlerini gösteriyor.",
|
||||
"locations": ["av_topraklari", "karanlik_orman"],
|
||||
"choices": [
|
||||
{"text": "⚔ Savaş!", "skill": "combat", "dc": 13,
|
||||
"success": {"text": "Sürüyü püskürttün, lider kurdu yere serdin.",
|
||||
"items": [{"code": "meat_wolf", "qty": 2}, {"code": "leather_wolf", "qty": 1}],
|
||||
"xp": {"skill": "combat", "amount": 60}},
|
||||
"fail": {"text": "Kurtlar seni kovaladı, kan kaybederek kaçtın.", "steps": -150}},
|
||||
{"text": "🏃 Kaç", "outcome": {"text": "Uzun bir koşuyla kurtuldun ama yorgun düştün.", "steps": -80}},
|
||||
],
|
||||
},
|
||||
{
|
||||
"code": "vahsi_domuz",
|
||||
"name": "Vahşi Domuz",
|
||||
"text": "Çalılıklar hışırdıyor — kocaman bir yaban domuzu toprağı eşeliyor. Henüz seni fark etmedi.",
|
||||
"locations": ["av_topraklari", "fisilti_ormani", "akca_koru"],
|
||||
"choices": [
|
||||
{"text": "🏹 Sessizce avla", "skill": "hunting", "dc": 12,
|
||||
"success": {"text": "Tek atışla indirdin. Temiz bir av.",
|
||||
"items": [{"code": "meat_boar", "qty": 2}, {"code": "leather_boar", "qty": 1}],
|
||||
"xp": {"skill": "hunting", "amount": 50}},
|
||||
"fail": {"text": "Dal kırıldı, domuz sana saldırdı! Zor kaçtın.", "steps": -100}},
|
||||
{"text": "🤫 Rahat bırak", "outcome": {"text": "Domuz homurdanarak uzaklaştı.", "steps": 0}},
|
||||
],
|
||||
},
|
||||
{
|
||||
"code": "geyik_izi",
|
||||
"name": "Taze Geyik İzi",
|
||||
"text": "Toprakta taze geyik izleri var. İzler sık çalılığın içine gidiyor.",
|
||||
"locations": ["cayirlar", "av_topraklari", "gumus_camlik"],
|
||||
"choices": [
|
||||
{"text": "🦌 İzi sür", "skill": "hunting", "dc": 10,
|
||||
"success": {"text": "İzi sürüp genç bir geyik avladın.",
|
||||
"items": [{"code": "meat_deer", "qty": 1}, {"code": "leather_deer", "qty": 1}],
|
||||
"xp": {"skill": "hunting", "amount": 40}},
|
||||
"fail": {"text": "İzi çalılıkta kaybettin, boşa yürüdün.", "steps": -60}},
|
||||
{"text": "➡ Yoluna devam et", "outcome": {"text": "Avı başka zamana bıraktın.", "steps": 0}},
|
||||
],
|
||||
},
|
||||
{
|
||||
"code": "magara_orumcegi",
|
||||
"name": "Mağara Örümceği",
|
||||
"text": "Madenin karanlık bir kovuğunda dev bir örümcek ağını germiş. Ağın arkasında parlayan bir cevher damarı var.",
|
||||
"locations": ["derin_maden", "ejder_dagi", "tas_ocagi"],
|
||||
"choices": [
|
||||
{"text": "⚔ Örümceği öldür", "skill": "combat", "dc": 14,
|
||||
"success": {"text": "Örümceği alt ettin, damardaki cevheri söktün.",
|
||||
"items": [{"code": "ore_silver", "qty": 2}],
|
||||
"xp": {"skill": "combat", "amount": 70}},
|
||||
"fail": {"text": "Ağa dolandın! Kendini zor kurtardın.", "steps": -120}},
|
||||
{"text": "🕯 Geri çekil", "outcome": {"text": "O gözlerle uğraşmaya değmez. Geri döndün.", "steps": -30}},
|
||||
],
|
||||
},
|
||||
{
|
||||
"code": "korsan_yagmasi",
|
||||
"name": "Korsan Artığı",
|
||||
"text": "Kıyıya vurmuş kırık bir filika... İçinde kilitli küçük bir para sandığı var. Ama iki serseri de sandığı görmüş, sana doğru geliyorlar.",
|
||||
"locations": ["liman", "firtina_kiyisi"],
|
||||
"choices": [
|
||||
{"text": "⚔ Sandık için dövüş", "skill": "combat", "dc": 15,
|
||||
"success": {"text": "Serserileri kaçırttın. Sandıktan gold çıktı!",
|
||||
"gold": 40, "xp": {"skill": "combat", "amount": 80}},
|
||||
"fail": {"text": "İki kişiye gücün yetmedi, dayak yiyip uzaklaştın.", "steps": -180}},
|
||||
{"text": "🚶 Sandığı onlara bırak", "outcome": {"text": "Belaya bulaşmadın.", "steps": 0}},
|
||||
],
|
||||
},
|
||||
{
|
||||
"code": "yasli_avci",
|
||||
"name": "Yaşlı Avcı",
|
||||
"text": "Kamp ateşinin başında yaşlı bir avcı oturuyor. 'Otur evlat,' diyor, 'sana av hikâyeleri anlatayım.'",
|
||||
"locations": ["av_topraklari", "cayirlar"],
|
||||
"choices": [
|
||||
{"text": "🔥 Otur ve dinle", "outcome": {"text": "Saatlerce iz sürme ve tuzak kurma taktikleri dinledin.",
|
||||
"xp": {"skill": "hunting", "amount": 45}, "steps": -40}},
|
||||
{"text": "🙏 Teşekkür edip geç", "outcome": {"text": "Vaktin yoktu, yola devam ettin.", "steps": 0}},
|
||||
],
|
||||
},
|
||||
{
|
||||
"code": "balikci_aginda",
|
||||
"name": "Terk Edilmiş Ağ",
|
||||
"text": "Kıyıda yarısı suda bir balıkçı ağı duruyor. İçinde hâlâ çırpınan balıklar var ama ağ kayalara dolanmış.",
|
||||
"locations": ["aynali_gol", "firtina_kiyisi", "liman"],
|
||||
"choices": [
|
||||
{"text": "🎣 Ağı çöz", "skill": "fishing", "dc": 11,
|
||||
"success": {"text": "Ağı ustaca çözdün, balıklar senin.",
|
||||
"items": [{"code": "fish_mackerel", "qty": 2}, {"code": "fish_bream", "qty": 1}],
|
||||
"xp": {"skill": "fishing", "amount": 40}},
|
||||
"fail": {"text": "Ağ iyice dolandı, üstüne bir de sırılsıklam oldun.", "steps": -70}},
|
||||
{"text": "🌊 Denize bırak", "outcome": {"text": "Balıkları özgür bıraktın, için rahat.", "steps": 0}},
|
||||
],
|
||||
},
|
||||
{
|
||||
"code": "ayi_ini",
|
||||
"name": "Ayı İni",
|
||||
"text": "Kadim ormanın derinliklerinde bir in... İçeriden horultu geliyor. Girişte yarım kalmış bir bal peteği duruyor.",
|
||||
"locations": ["kadim_orman", "karanlik_orman"],
|
||||
"choices": [
|
||||
{"text": "⚔ İne gir, ayıyla yüzleş", "skill": "combat", "dc": 16,
|
||||
"success": {"text": "Efsanevi bir dövüştü. Ayıyı devirdin!",
|
||||
"items": [{"code": "meat_bear", "qty": 2}, {"code": "leather_bear", "qty": 1}],
|
||||
"xp": {"skill": "combat", "amount": 100}},
|
||||
"fail": {"text": "Ayı seni köpek gibi kovaladı. Canını zor kurtardın.", "steps": -250}},
|
||||
{"text": "🍯 Sadece peteği al ve kaç", "skill": "hunting", "dc": 12,
|
||||
"success": {"text": "Sessizce peteği kapıp uzaklaştın.",
|
||||
"items": [{"code": "herb_chamomile", "qty": 2}],
|
||||
"xp": {"skill": "hunting", "amount": 30}},
|
||||
"fail": {"text": "Ayı uyandı! Peteği bırakıp kaçtın.", "steps": -100}},
|
||||
{"text": "🚶 Hiç bulaşma", "outcome": {"text": "Uyuyan ayıyı rahat bıraktın.", "steps": 0}},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
dsn = settings.DATABASE_URL.replace("postgresql+asyncpg://", "postgresql://")
|
||||
conn = await asyncpg.connect(dsn)
|
||||
for ev in EVENTS:
|
||||
await conn.execute("""
|
||||
INSERT INTO events (code, name, text, choices, location_codes)
|
||||
VALUES ($1, $2, $3, $4::jsonb, $5)
|
||||
ON CONFLICT (code) DO UPDATE
|
||||
SET name = EXCLUDED.name, text = EXCLUDED.text,
|
||||
choices = EXCLUDED.choices, location_codes = EXCLUDED.location_codes
|
||||
""", ev["code"], ev["name"], ev["text"], json.dumps(ev["choices"]), ev["locations"])
|
||||
total = await conn.fetchval("SELECT count(*) FROM events")
|
||||
targeted = await conn.fetchval("SELECT count(*) FROM events WHERE location_codes IS NOT NULL")
|
||||
print(f"OK — toplam {total} olay ({targeted} lokasyona özel)")
|
||||
await conn.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
617
db/seed_quests.py
Normal file
617
db/seed_quests.py
Normal file
|
|
@ -0,0 +1,617 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Stepstead — Yan görev içeriği (2026-07-08).
|
||||
WoW / Witcher tarzı ~90 yan görev + görev veren NPC'ler + yeni düşmanlar + görev itemleri.
|
||||
|
||||
Yapar (idempotent — tekrar çalıştırılabilir):
|
||||
1. Görev veren NPC'ler (friendly/neutral) → şehir/kasaba/tapınak/maden map_tag'lerine.
|
||||
2. Yeni düşmanlar (hostile NPC) → maden/tapınak/çorak/orman/çayır bölgelerine.
|
||||
3. Görev itemleri → items tablosu. Toplanabilenler location_resources'a (oyun lokasyonu).
|
||||
Düşmandan düşenler item olarak eklenir (drop düşman açıklamasında yazılı).
|
||||
4. ~90 yan görev (quests) + adım adım hedefler (quest_objectives).
|
||||
|
||||
Seed işareti: quests.author='Görev Tohumu', npcs (giver) description sonuna [seed:quest].
|
||||
Çalıştırma: cd /opt/stepstead && ./venv/bin/python3 db/seed_quests.py
|
||||
"""
|
||||
import asyncio
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, "/opt/stepstead")
|
||||
|
||||
import asyncpg # noqa: E402
|
||||
from config import settings # noqa: E402
|
||||
|
||||
SEED_TAG = "Görev Tohumu"
|
||||
GIVER_MARK = "[seed:quest-giver]"
|
||||
|
||||
|
||||
def edesc(hp, dmg, atk, dc, weak, drops, danger, lore):
|
||||
return (f"{lore}\n\n⚔ Saldırı türü: {atk}\n❤ Can (HP): {hp}\n🗡 Hasar: {dmg}\n"
|
||||
f"🎯 Savaş zorluğu (combat DC): {dc}\n💀 Tehlike: {danger}\n"
|
||||
f"🩹 Zayıflık: {weak}\n🎁 Düşürdükleri: {drops}")
|
||||
|
||||
|
||||
# ─────────────────────────── GÖREV VERENLER ───────────────────────────
|
||||
# (key, ad, rol, yer_adı[map_tags.name], açıklama)
|
||||
GIVERS = [
|
||||
("doran", "Kaptan Doran", "Kale Muhafızı", "Aethelguard Citadel",
|
||||
"Aethelguard Kalesi'nin sert ama adil muhafız kaptanı. Bölgedeki tehditleri maceracılara havale eder."),
|
||||
("lysa", "Rahibe Lysa", "Şifacı Rahibe", "Aethelguard Citadel",
|
||||
"Kale mabedinin şefkatli rahibesi. Hastalara şifalı ot ve kutsal su lazım olur."),
|
||||
("aldwin", "Tüccar Aldwin", "Tüccar", "Aethelguard Citadel",
|
||||
"Zengin ama korkak bir tüccar. Kervanları eşkıyalardan çekmediği için sürekli yardım arar."),
|
||||
("verena", "Simyacı Verena", "Simyacı", "Aethelguard Citadel",
|
||||
"Meraklı bir simyacı; deneyleri için nadir salgı, zehir ve mantar ister."),
|
||||
("orin", "Kütüphaneci Orin", "Bilgin", "Aethelguard Citadel",
|
||||
"Kadim metinlerin peşindeki yaşlı bilgin. Harabelerdeki tabletleri ve tomarları toplatır."),
|
||||
("bramble", "Hancı Bramble", "Hancı", "Riftwood Crossing",
|
||||
"Riftwood Geçidi hanının gürültücü sahibi. Müşterileri için et, balık ve dedikodu toplar."),
|
||||
("kael", "Avcı Ustası Kael", "Avcı Ustası", "Riftwood Crossing",
|
||||
"Bölgenin en iyi izcisi. Çırak yetiştirir; canavar avı ve deri siparişleri verir."),
|
||||
("sefa", "Kervan Lideri Sefa", "Kervancı", "Riftwood Crossing",
|
||||
"Kıtalar arası mal taşıyan cesur kervancı. Yol güvenliği ve teslimat işleri dağıtır."),
|
||||
("grungni", "Ustabaşı Grungni", "Maden Ustabaşı", "Rivenhaven",
|
||||
"Rivenhaven madeninin huysuz ustabaşı. Madeni istila eden yaratıklardan ve göçüklerden bıkmış."),
|
||||
("nadia", "Kâhin Nadia", "Kâhin", "T-1",
|
||||
"Terk edilmiş tapınağın son kâhini. Huzursuz ölüleri ve bozulan mühürleri dert eder."),
|
||||
("thariel", "Baş Rahip Thariel", "Baş Rahip", "T-2",
|
||||
"Işık tarikatının baş rahibi. Lanetli sunakları arındıracak ve kutsal emanetleri bulacak eller arar."),
|
||||
("bram", "Orman Bekçisi Bram", "Korucu", "Whisperwood",
|
||||
"Whisperwood'un yalnız koruyucusu. Ormanın dengesini bozan her şeyi izler."),
|
||||
("elindra", "Elf İzci Elindra", "İzci", "Luminara Grove",
|
||||
"Luminara'nın zarif elf izcisi. Kutsal ağaçları ve nadir çiçekleri korur."),
|
||||
("morvath", "Münzevi Morvath", "Münzevi Büyücü", "Xylos Prime",
|
||||
"Ormanın derininde yaşayan tuhaf büyücü. Reagent'ları karşılığında sırlar fısıldar."),
|
||||
("thessa", "Thessa Mor", "Görev Veren", "Rivenhaven",
|
||||
"Gizemli görev dağıtıcısı; kimin için çalıştığı belirsiz, ama altını gerçek."),
|
||||
("garrick", "Nalbant Garrick", "Nalbant", "Riftwood Crossing",
|
||||
"Kervan atlarına bakan güçlü kuvvetli nalbant. Demir ve kömür sıkıntısı çeker."),
|
||||
("wystan", "Yaşlı Wystan", "Emekli Şövalye", "Aethelguard Citadel",
|
||||
"Bir zamanlar ünlü bir şövalye; artık dizleri tutmuyor, işlerini gençlere yaptırıyor."),
|
||||
("seline", "Balıkçı Seline", "Balıkçı Reisi", "Riftwood Crossing",
|
||||
"Kıyı balıkçılarının sözü geçen reisi. Fırtına, korsan ve dev balık derdi hiç bitmez."),
|
||||
]
|
||||
|
||||
# ─────────────────────────── YENİ DÜŞMANLAR ───────────────────────────
|
||||
# (ad, rol, bölge_adı[map_tags.name], desc)
|
||||
ENEMIES = [
|
||||
# Maden
|
||||
("Kobold Madenci", "Kobold", "Rivenhaven",
|
||||
edesc(20, "5-9 (kazma)", "Yakın dövüş — sürü", 13, "Ateş, dar geçitte kıstırma",
|
||||
"Kobold kulağı ×1-2, ham bakır, mum", "Orta",
|
||||
"Madenin karanlık dehlizlerini istila eden küçük, aç gözlü kobold. Parlak her şeyi çalar.")),
|
||||
("Taş Golemi", "Golem", "Rivenhaven",
|
||||
edesc(70, "14-20 (yumruk)", "Yakın dövüş — ağır", 17, "Kazma (mining), keskin köşeler",
|
||||
"Golem çekirdeği ×1, kristal parçası ×2-3", "Yüksek",
|
||||
"Maden derinliklerinde biriken mananın canlandırdığı taş yığını. Yavaş ama ezici.")),
|
||||
("Kör Mağara Trolü", "Trol", "M-1",
|
||||
edesc(90, "16-24 (sopa)", "Yakın dövüş — öfke", 18, "Ateş, ışık (kör eder)",
|
||||
"Trol derisi ×1-2, kemik sopa, iri diş", "Çok Yüksek",
|
||||
"Gözleri körelmiş dev bir mağara trolü. Sesi ve kokuyu takip eder; ateşten deliye döner.")),
|
||||
("Derin Örümcek", "Dev Örümcek", "M-2",
|
||||
edesc(28, "7-12 (zehir)", "Yakın dövüş — zehirli", 14, "Ateş, geniş alan",
|
||||
"Örümcek zehir kesesi ×1-2, ipek", "Orta-Yüksek",
|
||||
"Mağaranın dibinde ağ ören, at büyüklüğünde solgun örümcek. Zehri felç eder.")),
|
||||
# Tapınak / undead
|
||||
("Huzursuz İskelet", "Hortlak", "T-1",
|
||||
edesc(24, "6-10 (paslı kılıç)", "Yakın dövüş", 13, "Kutsal su, kör (blunt) silah",
|
||||
"İskelet kemiği ×1-2, paslı madeni para", "Orta",
|
||||
"Mühür bozulunca uyanan eski muhafız iskeletleri. Kutsal su karşısında dağılır.")),
|
||||
("Lanetli Rahip Hayaleti", "Hayalet (Elit)", "T-2",
|
||||
edesc(60, "12-18 (mana lanet)", "Büyü — lanet + korku", 17, "Kutsal emanet, ışık büyüsü",
|
||||
"Hayalet ektoplazması ×1-2, lanetli tomar", "Yüksek",
|
||||
"Tapınağı ihanetle lekeleyen rahibin huzursuz ruhu. Sunak arındırılmadan yok edilemez.")),
|
||||
("Taş Bekçi Heykeli", "Muhafız Golem", "T-2",
|
||||
edesc(80, "15-22 (balyoz kol)", "Yakın dövüş — ağır", 18, "Kazma, çekirdeğini kırmak",
|
||||
"Kutsal taş ×2, golem çekirdeği ×1", "Yüksek",
|
||||
"Tapınağı bekleyen kadim heykel. Yabancıyı kutsal alanda görünce canlanır.")),
|
||||
# Çorak (B-1 wasteland)
|
||||
("Çorak Hortlağı", "Zombi", "B-1",
|
||||
edesc(30, "7-11 (pençe)", "Yakın dövüş — hastalık", 14, "Ateş, kafa darbesi",
|
||||
"Çürük et, kayıp madalyon (nadir)", "Orta",
|
||||
"Çorak toprakta sürünen, hastalık saçan cesetler. Yavaş ama kalabalık gelir.")),
|
||||
("Kum Solucanı", "Dev Solucan", "B-1",
|
||||
edesc(75, "14-20 (yutma)", "Pusu — yer altından", 17, "Titreşimden kaçınmak, ateş",
|
||||
"Kum solucanı dişi ×1-2, kristalize tuz", "Çok Yüksek",
|
||||
"Çorak kumun altında bekleyen kör dev solucan. Ayak sesini duyunca patlarcasına fırlar.")),
|
||||
("Leş Akbabası", "Uçan Yırtıcı", "B-1",
|
||||
edesc(22, "6-9 (gaga)", "Havadan dalış", 13, "Ok/yay, yüksek zemin",
|
||||
"Akbaba gagası, tüy demeti", "Orta",
|
||||
"Çorakta leş kokusunu kilometrelerce öteden alan iri akbaba. Zayıfı seçer.")),
|
||||
("Gölge İblisi", "İblis (Elit)", "B-1",
|
||||
edesc(95, "18-26 (kara alev)", "Büyü — kara alev + korku", 19, "Kutsal emanet, ışık",
|
||||
"İblis boynuzu ×1, kükürt, kara kristal", "Ölümcül",
|
||||
"Yarıktan sızan alt-düzey iblis. Gölgeden gölgeye atlar, dokunduğunu yakar.")),
|
||||
# Çayır / genel
|
||||
("Yaban Domuzu Reisi", "Canavar (Elit)", "Solstice Mire",
|
||||
edesc(50, "11-16 (azı dişi)", "Hücum — boynuzlama", 15, "Tuzak, mızrak",
|
||||
"Domuz dişi ×1-2, kalın deri, yaban eti", "Orta-Yüksek",
|
||||
"Sürünün yaşlı, yaralı ama azgın reisi. Hücumu ağaç deviren güçtedir.")),
|
||||
("Dağ Ayısı", "Canavar (Elit)", "Emberfall",
|
||||
edesc(65, "13-19 (pençe)", "Yakın dövüş — pençe+ısırık", 16, "Ateş, mesafe",
|
||||
"Ayı pençesi ×1-2, kalın post, bal", "Yüksek",
|
||||
"İninden erken uyanmış huysuz bir bozayı. Toprağını savunurken acımasızdır.")),
|
||||
("Bataklık Harpisi", "Harpi", "Solstice Mire",
|
||||
edesc(34, "8-13 (pençe dalış)", "Havadan taciz + çığlık", 15, "Ok/yay, ağ",
|
||||
"Harpi tüyü ×1-2, çalınmış mücevher", "Orta-Yüksek",
|
||||
"Bataklık üstünde çığlıklarla uçan yarı kuş yaratık. Parlak eşya için saldırır.")),
|
||||
("Vahşi Şaman Goblin", "Goblin Büyücü", "Rivenwood",
|
||||
edesc(40, "9-14 (şok totemi)", "Büyü — totem + şimşek", 15, "Totemini kırmak, hızlı baskın",
|
||||
"Goblin kulağı ×2, totem parçası, garip iksir", "Orta-Yüksek",
|
||||
"Goblin çetesini totemleriyle güçlendiren sinsi şaman. Önce totemi düşmeli.")),
|
||||
("Gulyabani", "Yamyam Yaratık", "Xylos Prime",
|
||||
edesc(45, "10-15 (tırnak)", "Pusu — karanlıktan", 16, "Ateş meşalesi, ışık",
|
||||
"Solgun et, mezar tozu, kayıp yüzük", "Yüksek",
|
||||
"Karanlık ormanda mezarları eşen soluk benizli yamyam. Işıktan tiksinir.")),
|
||||
("Orman Şeytanı (Leshy)", "Bitki Ruhu (Elit)", "Ironbark Reach",
|
||||
edesc(85, "15-22 (kök kırbaç)", "Yakın dövüş + köklerle bağlama", 18, "Ateş, gümüş balta",
|
||||
"Kadim meşe özü ×2, canlı kök, uğur boncuğu", "Çok Yüksek",
|
||||
"Ormanın öfkeli koruyucu ruhu. Witcher masalındaki gibi kurbanlarını köklerle sarar.")),
|
||||
("Su Cini (Vodyanoy)", "Su Canavarı", "Aetherbloom",
|
||||
edesc(48, "10-15 (pençe)", "Sudan pusu", 16, "Kuru zemine çekmek, ateş",
|
||||
"Pullu deri, inci, sülük salgısı", "Orta-Yüksek",
|
||||
"Gölet ve derelerde pusuya yatan pullu su cini. Balıkçıları suya çeker.")),
|
||||
]
|
||||
|
||||
# ─────────────────────────── GÖREV İTEMLERİ ───────────────────────────
|
||||
# type: material (çoğu). gather_loc: oyun lokasyonu code'u (varsa location_resources'a eklenir), None=sadece drop.
|
||||
# (code, ad, rarity, gather_loc, skill, step, xp, açıklama)
|
||||
QITEMS = [
|
||||
# Toplanabilir reagent'lar
|
||||
("herb_healroot", "Şifalı Kök", "common", "fisilti_ormani", "foraging", 5, 9, "Rahibe Lysa'nın merhemleri için kök."),
|
||||
("herb_moonflower", "Ay Çiçeği", "uncommon", "cayirlar", "foraging", 6, 12, "Sadece dolunayda açan soluk mavi çiçek."),
|
||||
("herb_bluecap", "Mavi Mantar", "common", "kadim_orman", "foraging", 5, 9, "Nemli kütüklerde biten mavi şapkalı mantar."),
|
||||
("mat_wildhoney", "Yaban Balı", "uncommon", "akca_koru", "foraging", 7, 13, "Vahşi arı kovanından çıkarılan koyu bal."),
|
||||
("mat_crystal", "Kristal Parçası", "uncommon", "derin_maden", "mining", 7, 13, "Maden derinliğinde parıldayan ham kristal."),
|
||||
("mat_ironslag", "Demir Cürufu", "common", "tas_ocagi", "mining", 5, 9, "Eritme artığı; nalbantın işine yarar."),
|
||||
("mat_pearlshell", "İnci Kabuğu", "uncommon", "aynali_gol", "fishing", 6, 12, "İçinde bazen inci taşıyan sedefli kabuk."),
|
||||
("mat_holywater", "Kutsal Su", "rare", "firtina_kiyisi", "fishing", 8, 16, "Kutsanmış kaynaktan alınan berrak su."),
|
||||
("mat_ancientoak", "Kadim Meşe Özü", "rare", "kadim_orman", "woodcutting", 8, 16, "Yüzyıllık meşenin sızan özü; büyüde güçlüdür."),
|
||||
("mat_salt", "Kristalize Tuz", "common", "firtina_kiyisi", "foraging", 5, 9, "Kıyı kayalarında biriken tuz kristali."),
|
||||
# Sadece drop / bulunacak eşyalar
|
||||
("q_wolf_fang", "Kurt Dişi", "common", None, None, 0, 0, "Kurt sürülerinden toplanan sivri diş."),
|
||||
("q_spider_venom", "Örümcek Zehir Kesesi", "uncommon", None, None, 0, 0, "Dev örümceklerden alınan zehir kesesi."),
|
||||
("q_kobold_ear", "Kobold Kulağı", "common", None, None, 0, 0, "Kobold avının kanıtı."),
|
||||
("q_skeleton_bone", "İskelet Kemiği", "common", None, None, 0, 0, "Huzursuz ölülerden kalan kemik."),
|
||||
("q_golem_core", "Golem Çekirdeği", "rare", None, None, 0, 0, "Golemi çalıştıran manalı taş çekirdek."),
|
||||
("q_ectoplasm", "Hayalet Ektoplazması", "rare", None, None, 0, 0, "Hayaletten sızan titreşen madde."),
|
||||
("q_harpy_feather", "Harpi Tüyü", "uncommon", None, None, 0, 0, "Bataklık harpisinin keskin tüyü."),
|
||||
("q_bear_claw", "Ayı Pençesi", "uncommon", None, None, 0, 0, "Dağ ayısının kavisli pençesi."),
|
||||
("q_goblin_ear", "Goblin Kulağı", "common", None, None, 0, 0, "Goblin çetesi avının kanıtı."),
|
||||
("q_troll_hide", "Trol Derisi", "rare", None, None, 0, 0, "Kalın, ok geçirmez trol derisi."),
|
||||
("q_demon_horn", "İblis Boynuzu", "epic", None, None, 0, 0, "Gölge iblisinden koparılan kara boynuz."),
|
||||
("q_worm_tooth", "Kum Solucanı Dişi", "rare", None, None, 0, 0, "Kum solucanının testere gibi dişi."),
|
||||
("q_boar_tusk", "Domuz Dişi", "common", None, None, 0, 0, "Yaban domuzu reisinin azı dişi."),
|
||||
("q_leech_slime", "Sülük Salgısı", "common", None, None, 0, 0, "Bataklık sülüğünden alınan yapışkan salgı."),
|
||||
("q_medallion", "Kayıp Madalyon", "rare", None, None, 0, 0, "Bir soylunun kayıp aile madalyonu."),
|
||||
("q_ancient_tablet","Kadim Tablet", "rare", None, None, 0, 0, "Eski bir dille yazılı taş tablet."),
|
||||
("q_cursed_scroll", "Lanetli Tomar", "rare", None, None, 0, 0, "Karanlık büyü içeren mühürlü tomar."),
|
||||
("q_holy_relic", "Kutsal Emanet", "epic", None, None, 0, 0, "Işık tarikatının kayıp kutsal emaneti."),
|
||||
("q_lost_ring", "Kayıp Yüzük", "uncommon", None, None, 0, 0, "Bir maceracının mezardan çıkan yüzüğü."),
|
||||
("q_totem_shard", "Totem Parçası", "common", None, None, 0, 0, "Goblin şamanın kırık totemi."),
|
||||
]
|
||||
|
||||
# ─────────────────────────── YAN GÖREVLER ───────────────────────────
|
||||
# (başlık, giver_key, bölge_adı[map_tags.name] veya None, özet, [hedef notları])
|
||||
QUESTS = [
|
||||
# ── Kaptan Doran — bounty / güvenlik ──
|
||||
("Kurt Belası", "doran", "Veridian Glade",
|
||||
"Aç kurtlar kale yollarına inmeye başladı. Doran onları seyrekleştirmeni istiyor.",
|
||||
["Veridian Glade'de 6 Aç Kurt öldür.", "Kanıt olarak 4 Kurt Dişi topla.", "Kaptan Doran'a dön."]),
|
||||
("Örümcek Yuvası", "doran", "Luminara Grove",
|
||||
"Zehirli örümcekler bir tüccarı zehirledi. Yuvayı temizle.",
|
||||
["Luminara Grove'da 5 Zehirli Orman Örümceği öldür.", "3 Örümcek Zehir Kesesi getir."]),
|
||||
("Haydut Tehdidi", "doran", "Solstice Mire",
|
||||
"Orman haydutları kervan yolunu kesiyor. Reisleri bir ders almalı.",
|
||||
["Solstice Mire'da 5 Orman Haydutu öldür.", "Çaldıkları keseyi geri al.", "Doran'a bildir."]),
|
||||
("Gölge Kurdu Avı", "doran", "Emberfall",
|
||||
"Bir alfa kurt sürüsü kontrolden çıkardı. Elit hedefi indir.",
|
||||
["Emberfall'da Gölge Kurdu (Alfa)'yı öldür.", "Postunu kanıt getir."]),
|
||||
("Kanatlı Bela", "doran", "Xylos Prime",
|
||||
"Kanlı yarasalar geceleri nöbetçilere saldırıyor.",
|
||||
["Xylos Prime'da 8 Kanlı Yarasa öldür.", "5 Yarasa Kanadı topla."]),
|
||||
("Karga Laneti", "doran", "Veridian Glade",
|
||||
"Lanetli bir karga sürüsü tarlaları talan ediyor; uğursuzluk saçıyorlar.",
|
||||
["Lanetli Karga Sürüsü'nü dağıt.", "Lanetli boncuğu Rahibe Lysa'ya götür."]),
|
||||
|
||||
# ── Rahibe Lysa — şifa / topla ──
|
||||
("Merhem İçin Kök", "lysa", "Fısıltı Ormanı",
|
||||
"Revirdeki yaralılar için şifalı kök tükendi.",
|
||||
["Fısıltı Ormanı'nda 8 Şifalı Kök topla.", "Rahibe Lysa'ya teslim et."]),
|
||||
("Dolunay Çiçeği", "lysa", None,
|
||||
"Ateşi düşmeyen bir çocuk için nadir ay çiçeği gerek.",
|
||||
["Uçsuz Çayırlar'da 6 Ay Çiçeği topla.", "Lysa'ya getir."]),
|
||||
("Kutsal Su Seferi", "lysa", None,
|
||||
"Vaftiz ayini için kutsanmış kaynak suyu lazım.",
|
||||
["Fırtına Kıyısı'ndan 4 Kutsal Su al.", "Mabede götür."]),
|
||||
("Salgın Durdurulmalı", "lysa", "B-1",
|
||||
"Çorak topraktan gelen hortlaklar hastalık yayıyor; kaynağı azalt.",
|
||||
["Çorak Hortlağı'ndan 6 öldür.", "5 Mavi Mantar topla (panzehir için).", "Lysa'ya dön."]),
|
||||
("Arı Kovanı Riski", "lysa", None,
|
||||
"Yaralı yanıkları için yaban balı gerekiyor ama kovanlar tehlikeli.",
|
||||
["Akça Koru'dan 5 Yaban Balı topla.", "Lysa'ya teslim et."]),
|
||||
|
||||
# ── Tüccar Aldwin — teslimat / eskort ──
|
||||
("Kervan Koruması", "aldwin", "Solstice Mire",
|
||||
"Aldwin'in kervanı bataklıktan geçerken haydut korkusu içinde.",
|
||||
["Kervan yolundaki 4 Orman Haydutu'nu temizle.", "Aldwin'e güvenli yolu bildir."]),
|
||||
("Kayıp Sevkiyat", "aldwin", "Rivenwood",
|
||||
"Bir yük arabası devrildi, mallar ormana saçıldı.",
|
||||
["Rivenwood'da 5 kayıp sandığı bul (araştır).", "Goblinlerin çaldığını Aldwin'e söyle."]),
|
||||
("Değerli Teslimat", "aldwin", "Riftwood Crossing",
|
||||
"Kırılgan bir sipariş Riftwood'a ulaştırılmalı.",
|
||||
["Paketi Hancı Bramble'a götür.", "İmzalı makbuzu Aldwin'e geri getir."]),
|
||||
("Borçlu Kaçtı", "aldwin", "Karaağaç Karanlığı",
|
||||
"Aldwin'e borçlu bir adam karanlık ormana kaçmış.",
|
||||
["Karaağaç Karanlığı'nda borçluyu bul.", "Ya altını ya sözünü Aldwin'e getir."]),
|
||||
("Rakip Casusu", "aldwin", "Aethelguard Citadel",
|
||||
"Aldwin rakip bir tüccarın kendisini gözetlediğinden şüpheleniyor.",
|
||||
["Kalede casusu takip et (araştır).", "Bulguları Aldwin'e ilet."]),
|
||||
|
||||
# ── Simyacı Verena — reagent ──
|
||||
("Zehir Deneyi", "verena", "Luminara Grove",
|
||||
"Verena panzehir formülü için taze örümcek zehri istiyor.",
|
||||
["4 Örümcek Zehir Kesesi topla.", "Verena'ya getir."]),
|
||||
("Mantar Koleksiyonu", "verena", "Kadim Orman",
|
||||
"Nadir mavi mantarlar bir iksirin anahtarı.",
|
||||
["Kadim Orman'da 10 Mavi Mantar topla.", "Verena'nın laboratuvarına bırak."]),
|
||||
("Kristalin Sırrı", "verena", "Rivenhaven",
|
||||
"Verena maden kristalinin manasını incelemek istiyor.",
|
||||
["Derin Maden'den 6 Kristal Parçası çıkar.", "Verena'ya teslim et."]),
|
||||
("Ektoplazma Örneği", "verena", "T-2",
|
||||
"Hayalet maddesi Verena'nın en riskli deneyi için gerekli.",
|
||||
["Lanetli Rahip Hayaleti'nden 2 Hayalet Ektoplazması al.", "Kırılmadan Verena'ya götür."]),
|
||||
("Sülük Salgısı", "verena", "Slyvandor",
|
||||
"Yapışkan sülük salgısı yeni bir yara bandı denemesi için.",
|
||||
["Bataklık Sülüğü'nden 5 Sülük Salgısı topla.", "Verena'ya ver."]),
|
||||
("İblis Kükürdü", "verena", "B-1",
|
||||
"Verena tehlikeli bir simya için iblis boynuzu tozu istiyor — delilik mi cesaret mi?",
|
||||
["Gölge İblisi'nden 1 İblis Boynuzu al.", "Verena'ya teslim et (dikkatli!)."]),
|
||||
|
||||
# ── Kütüphaneci Orin — araştır / kadim ──
|
||||
("Kayıp Tabletler", "orin", "M-1",
|
||||
"Orin mağara duvarlarındaki kadim yazıtları istiyor.",
|
||||
["M-1 mağarasında 3 Kadim Tablet bul.", "Orin'e getir."]),
|
||||
("Yasak Tomar", "orin", "T-1",
|
||||
"Tapınak arşivinde lanetli bir tomar saklı; Orin onu güvenle incelemek istiyor.",
|
||||
["Huzursuz İskeletleri geçip 1 Lanetli Tomar al.", "Orin'e teslim et."]),
|
||||
("Harabelerin Sırrı", "orin", "Rivenhaven",
|
||||
"Madende eski bir uygarlığın izleri var.",
|
||||
["Rivenhaven'da 4 oyma taş incele (araştır).", "Çizimleri Orin'e ver."]),
|
||||
("Kadim Meşenin Bilgeliği", "orin", "Kadim Orman",
|
||||
"Orin kadim meşe özünün büyülü kaydını çözmek istiyor.",
|
||||
["3 Kadim Meşe Özü topla.", "Orin'in masasına bırak."]),
|
||||
("Kayıp Günlük", "orin", "Whisperwood",
|
||||
"Kaybolan bir kaşifin günlüğü ormanda bir yerlerde.",
|
||||
["Whisperwood'da kaşifin kampını bul (araştır).", "Günlüğü Orin'e getir."]),
|
||||
|
||||
# ── Hancı Bramble — yemek / dedikodu ──
|
||||
("Han Mutfağı: Et", "bramble", "Av Toprakları",
|
||||
"Han için taze yaban eti lazım.",
|
||||
["Av Toprakları'nda avlan, 8 yaban eti getir.", "Bramble'a teslim et."]),
|
||||
("Han Mutfağı: Balık", "bramble", None,
|
||||
"Cuma ziyafeti için taze balık gerek.",
|
||||
["Aynalı Göl'de 10 balık tut.", "Bramble'a getir."]),
|
||||
("Bal Likörü", "bramble", "Akça Koru",
|
||||
"Bramble'ın meşhur bal likörü için yaban balı bitti.",
|
||||
["6 Yaban Balı topla.", "Bramble'a ver."]),
|
||||
("Fıçı Baskını", "bramble", "Rivenwood",
|
||||
"Goblinler han fıçılarını çaldı!",
|
||||
["Rivenwood'da 4 Orman Haydutu/Goblin öldür.", "Çalınan fıçıyı geri getir."]),
|
||||
("Dedikodu Toplayıcı", "bramble", "Riftwood Crossing",
|
||||
"Bramble kasabadaki söylentileri merak ediyor.",
|
||||
["3 kasabalıyla konuş (araştır).", "Duyduklarını Bramble'a anlat."]),
|
||||
|
||||
# ── Avcı Ustası Kael — av / deri ──
|
||||
("Çırak Sınavı: Tavşan", "kael", "Av Toprakları",
|
||||
"Kael'in çırağı olmak için önce mütevazı bir avla başla.",
|
||||
["Av Toprakları'nda 6 tavşan avla.", "6 tavşan derisi getir."]),
|
||||
("Reis Domuz", "kael", "Solstice Mire",
|
||||
"Yaban domuzu reisi bir avcıyı yaraladı; onu indir.",
|
||||
["Yaban Domuzu Reisi'ni öldür.", "2 Domuz Dişi getir."]),
|
||||
("Ayı Sorunu", "kael", "Emberfall",
|
||||
"Erken uyanan bir dağ ayısı avlanma alanını basıyor.",
|
||||
["Dağ Ayısı'nı öldür.", "Ayı Pençesi ve postu Kael'e getir."]),
|
||||
("Kaliteli Deri Siparişi", "kael", "Av Toprakları",
|
||||
"Bir zırhçı için kalın deri gerekiyor.",
|
||||
["Geyik avla, 8 geyik derisi topla.", "Kael'e teslim et."]),
|
||||
("Harpi Tehdidi", "kael", "Solstice Mire",
|
||||
"Bataklık harpileri avcıların tuzaklarını yağmalıyor.",
|
||||
["3 Bataklık Harpisi öldür.", "4 Harpi Tüyü getir."]),
|
||||
("Kaçak Yavru", "kael", "Whisperwood",
|
||||
"Nadir bir geyik yavrusu sürüden ayrı düşmüş; kurtlardan önce bul.",
|
||||
["Whisperwood'da yavruyu bul (araştır).", "Güvenle Kael'e getir."]),
|
||||
|
||||
# ── Kervan Lideri Sefa — yol / teslimat ──
|
||||
("Yol Temizliği", "sefa", "Ironbark Reach",
|
||||
"Sefa'nın rotasını sarmaşık cinleri kapatmış.",
|
||||
["Ironbark Reach'te 4 Sarmaşık Cini öldür.", "Yolu Sefa'ya rapor et."]),
|
||||
("Kıtalar Arası Paket", "sefa", "Riftwood Crossing",
|
||||
"Meridia'ya gidecek hassas bir paket koruma istiyor.",
|
||||
["Paketi limana kadar koru (eskort).", "Sefa'ya teslim onayını getir."]),
|
||||
("Kayıp Kervancı", "sefa", "Karaağaç Karanlığı",
|
||||
"Sefa'nın bir adamı karanlık ormanda kayıp.",
|
||||
["Karaağaç Karanlığı'nda kervancıyı ara (araştır).", "Gulyabani tehdidini temizle.", "Adamı geri getir."]),
|
||||
("Nal Sevkiyatı", "sefa", "Riftwood Crossing",
|
||||
"Nalbant Garrick'e demir cürufu ulaştırılmalı.",
|
||||
["Taş Ocağı'ndan 8 Demir Cürufu topla.", "Garrick'e teslim et."]),
|
||||
("Korsan Uyarısı", "sefa", None,
|
||||
"Kıyıda korsan görüldü; Sefa deniz rotasını merak ediyor.",
|
||||
["Fırtına Kıyısı'nı gözle (araştır).", "Seline ile konuş.", "Sefa'ya bildir."]),
|
||||
|
||||
# ── Ustabaşı Grungni — maden ──
|
||||
("Kobold İstilası", "grungni", "Rivenhaven",
|
||||
"Kobold sürüsü madenin alt katını ele geçirdi.",
|
||||
["Rivenhaven'da 8 Kobold Madenci öldür.", "6 Kobold Kulağı getir."]),
|
||||
("Taştan Bela", "grungni", "Rivenhaven",
|
||||
"Bir taş golemi maden geçidini tıkadı.",
|
||||
["Taş Golemi'ni parçala.", "Golem Çekirdeği'ni Grungni'ye getir."]),
|
||||
("Göçük Kurtarması", "grungni", "Rivenhaven",
|
||||
"Göçük altında kalan madenciler var.",
|
||||
["3 madenciyi kazıp çıkar (araştır).", "Güvenli çıkışı Grungni'ye bildir."]),
|
||||
("Kristal Kotası", "grungni", "Derin Maden",
|
||||
"Kalenin siparişi için kristal çıkarılmalı.",
|
||||
["Derin Maden'den 10 Kristal Parçası çıkar.", "Grungni'ye teslim et."]),
|
||||
("Derindeki Örümcek", "grungni", "M-2",
|
||||
"En alt galeride dev bir örümcek yuva kurmuş.",
|
||||
["Derin Örümcek'i öldür.", "Yuvayı yak.", "Grungni'ye haber ver."]),
|
||||
("Kör Trol", "grungni", "M-1",
|
||||
"Kör bir mağara trolü madencileri dehşete düşürüyor.",
|
||||
["Kör Mağara Trolü'nü indir.", "Trol Derisi'ni kanıt getir."]),
|
||||
|
||||
# ── Kâhin Nadia — tapınak/undead ──
|
||||
("Huzursuz Ölüler", "nadia", "T-1",
|
||||
"Tapınak mührü zayıfladı, iskeletler uyandı.",
|
||||
["T-1'de 8 Huzursuz İskelet dağıt.", "6 İskelet Kemiği topla."]),
|
||||
("Mührü Onar", "nadia", "T-1",
|
||||
"Kırık mühür yeniden çizilmeli.",
|
||||
["3 Kutsal Su getir.", "Sunakta ritüeli tamamla.", "Nadia'ya bildir."]),
|
||||
("Kayıp Kâhin", "nadia", "M-1",
|
||||
"Nadia'nın çırağı kehanet ararken mağarada kaybolmuş.",
|
||||
["M-1'de çırağı bul (araştır).", "Sağ salim Nadia'ya getir."]),
|
||||
("Kehanet Tableti", "nadia", "M-2",
|
||||
"Bir kehanet kadim bir tablete kazınmış.",
|
||||
["M-2'de 2 Kadim Tablet bul.", "Nadia'ya oku."]),
|
||||
|
||||
# ── Baş Rahip Thariel — kutsal ──
|
||||
("Lanetli Sunak", "thariel", "T-2",
|
||||
"İhanetle lekelenen sunak arındırılmalı.",
|
||||
["Lanetli Rahip Hayaleti'ni yok et.", "Sunağı kutsal su ile arındır.", "Thariel'e dön."]),
|
||||
("Kutsal Emanet", "thariel", "T-2",
|
||||
"Tarikatın kutsal emaneti yağmalanmış, izini sür.",
|
||||
["Taş Bekçi Heykeli'ni geç.", "1 Kutsal Emanet'i geri al.", "Thariel'e teslim et."]),
|
||||
("Karanlığı Kov", "thariel", "B-1",
|
||||
"Çorak topraktan yayılan karanlık tapınağı tehdit ediyor.",
|
||||
["B-1'de 1 Gölge İblisi'ni kov.", "İblis Boynuzu'nu mühürle.", "Thariel'e getir."]),
|
||||
("Kutsal Su Kuyusu", "thariel", None,
|
||||
"Mabet kuyusu kurudu; kutsanacak temiz su gerek.",
|
||||
["Fırtına Kıyısı'ndan 6 Kutsal Su getir.", "Thariel kutsayacak."]),
|
||||
("Hac Yolu Güvenliği", "thariel", "T-1",
|
||||
"Hacılar huzursuz ölülerden korkuyor.",
|
||||
["T-1 yolundaki 6 Huzursuz İskelet'i temizle.", "Thariel'e güvenli yolu bildir."]),
|
||||
|
||||
# ── Orman Bekçisi Bram — orman dengesi ──
|
||||
("Ormanın Dengesi", "bram", "Whisperwood",
|
||||
"Yaşlı Ağaç Adam öfkelenip yolcuları eziyor; sakinleştir ya da indir.",
|
||||
["Whisperwood'da Yaşlı Ağaç Adam'ı yatıştır ya da öldür.", "Bram'a bildir."]),
|
||||
("Spor Salgını", "bram", "Rivenwood",
|
||||
"Mantar spor yaratıkları ormanı zehirliyor.",
|
||||
["Rivenwood'da 6 Mantar Spor Yaratığı öldür.", "Zehirli sporları yak."]),
|
||||
("Kaçak Ateş", "bram", "Emberfall",
|
||||
"Emberfall'da kontrolsüz bir yangın çıkabilir; kaynağı bul.",
|
||||
["Emberfall'da yangın izini araştır.", "Bram'a raporla."]),
|
||||
("Leshy Uyanışı", "bram", "Ironbark Reach",
|
||||
"Bir orman şeytanı (Leshy) uyanmış; Witcher misali onu alt et.",
|
||||
["Ironbark Reach'te Orman Şeytanı (Leshy)'yi öldür.", "Kadim Meşe Özü'nü Bram'a getir."]),
|
||||
("Fare İstilası", "bram", "Aetherbloom",
|
||||
"Dev orman fareleri ağaç köklerini kemiriyor.",
|
||||
["Aetherbloom'da 8 Dev Orman Faresi öldür.", "Bram'a bildir."]),
|
||||
|
||||
# ── Elf İzci Elindra — nadir doğa ──
|
||||
("Ay Çiçeği Kutsaması", "elindra", None,
|
||||
"Elindra'nın ritüeli için el değmemiş ay çiçekleri gerek.",
|
||||
["Uçsuz Çayırlar'da 8 Ay Çiçeği topla.", "Elindra'ya getir."]),
|
||||
("Kutsal Ağaç Bekçisi", "elindra", "Luminara Grove",
|
||||
"Örümcekler kutsal ağaca ağ örüyor.",
|
||||
["Luminara Grove'da 6 Zehirli Orman Örümceği öldür.", "Ağacı temizle."]),
|
||||
("İnci Gözyaşı", "elindra", None,
|
||||
"Elindra'nın kolyesi için berrak bir göl incisi gerek.",
|
||||
["Aynalı Göl'de 5 İnci Kabuğu topla.", "Elindra'ya ver."]),
|
||||
("Su Cini Tehdidi", "elindra", "Aetherbloom",
|
||||
"Bir su cini derenin balıklarını ve balıkçıları yutuyor.",
|
||||
["Aetherbloom'da Su Cini (Vodyanoy)'u öldür.", "Elindra'ya haber ver."]),
|
||||
|
||||
# ── Münzevi Morvath — gizemli reagent ──
|
||||
("Reagent Toplayıcı", "morvath", "Xylos Prime",
|
||||
"Morvath büyüsü için üç farklı reagent istiyor.",
|
||||
["4 Kadim Meşe Özü, 4 Mavi Mantar, 4 Kristal Parçası getir.", "Morvath'a ver."]),
|
||||
("Kara Tomarın Peşinde", "morvath", "T-1",
|
||||
"Morvath yasak bilgi için lanetli bir tomar istiyor.",
|
||||
["T-1'den 1 Lanetli Tomar al.", "Morvath'a gizlice getir."]),
|
||||
("Gölge Özü", "morvath", "B-1",
|
||||
"Morvath karanlık bir deney için iblis maddesi arıyor.",
|
||||
["Gölge İblisi'nden 1 İblis Boynuzu al.", "Morvath'a teslim et."]),
|
||||
("Totem Kırıcı", "morvath", "Rivenwood",
|
||||
"Vahşi şaman goblin Morvath'ın çizdiği sınırı bozuyor.",
|
||||
["Vahşi Şaman Goblin'i öldür.", "2 Totem Parçası getir."]),
|
||||
|
||||
# ── Thessa Mor — gizemli / karışık ──
|
||||
("Kayıp Madalyon", "thessa", "B-1",
|
||||
"Thessa bir soylunun kayıp madalyonunu istiyor; nedenini söylemiyor.",
|
||||
["Çorak Hortlağı'ndan 1 Kayıp Madalyon düşür.", "Thessa'ya getir."]),
|
||||
("Mezar Yüzüğü", "thessa", "Karaağaç Karanlığı",
|
||||
"Bir maceracının yüzüğü gulyabaninin ininde.",
|
||||
["Gulyabani'yi öldür.", "1 Kayıp Yüzük al.", "Thessa'ya teslim et."]),
|
||||
("İsimsiz İş", "thessa", "M-2",
|
||||
"Thessa mağaranın dibinden bir şey getirmeni istiyor — soru sorma.",
|
||||
["M-2'de gizli sandığı bul (araştır).", "Açmadan Thessa'ya getir."]),
|
||||
("Sessiz Tanık", "thessa", "Rivenhaven",
|
||||
"Bir olaya tanık olan madenci ortadan kaybolmuş.",
|
||||
["Rivenhaven'da tanığı ara (araştır).", "Bulguları Thessa'ya fısılda."]),
|
||||
("Kum Altındaki Sır", "thessa", "B-1",
|
||||
"Çorakta bir şey gömülü; kum solucanı üstünde bekliyor.",
|
||||
["Kum Solucanı'nı öldür.", "1 Kum Solucanı Dişi ve gömülü kutuyu getir."]),
|
||||
|
||||
# ── Nalbant Garrick — demir/kömür ──
|
||||
("Demir Cürufu Lazım", "garrick", "Taş Ocağı",
|
||||
"Garrick'in nal ocağı için cüruf tükendi.",
|
||||
["Taş Ocağı'ndan 10 Demir Cürufu topla.", "Garrick'e getir."]),
|
||||
("At Nalı Siparişi", "garrick", "Riftwood Crossing",
|
||||
"Kervan atları için toplu nal gerek; malzeme topla.",
|
||||
["6 Demir Cürufu ve 3 bakır külçe getir.", "Garrick'e teslim et."]),
|
||||
("Kayıp Örs", "garrick", "Rivenwood",
|
||||
"Garrick'in kıymetli örsü çalınmış — goblin işi.",
|
||||
["Rivenwood'da goblin kampını bul (araştır).", "Örsü geri getir."]),
|
||||
|
||||
# ── Yaşlı Wystan — nostalji / bounty ──
|
||||
("Eski Kılıcım", "wystan", "M-1",
|
||||
"Wystan gençliğinin kılıcını bir mağarada bırakmış.",
|
||||
["M-1'de eski kılıcı bul (araştır).", "Wystan'a getir."]),
|
||||
("Son Bir Av", "wystan", "Emberfall",
|
||||
"Wystan bir kez daha büyük bir av görmek istiyor; onun adına indir.",
|
||||
["Dağ Ayısı'nı öldür.", "Postu Wystan'a göster."]),
|
||||
("Şövalyenin Onuru", "wystan", "B-1",
|
||||
"Wystan'ı yenen iblis hâlâ çorakta; intikamını al.",
|
||||
["Gölge İblisi'ni öldür.", "İblis Boynuzu'nu Wystan'a getir."]),
|
||||
("Zırhımı Onar", "wystan", "Taş Ocağı",
|
||||
"Wystan'ın eski zırhı için metal gerek.",
|
||||
["Taş Ocağı'ndan 8 Demir Cürufu topla.", "Bir demirciye onartıp Wystan'a getir."]),
|
||||
|
||||
# ── Balıkçı Seline — deniz/göl ──
|
||||
("Fırtına Avı", "seline", None,
|
||||
"Fırtına öncesi balık sürüsü kıyıya yaklaştı.",
|
||||
["Fırtına Kıyısı'nda 12 balık tut.", "Seline'ye getir."]),
|
||||
("İnci Dalışı", "seline", None,
|
||||
"Seline'nin borcu için değerli inci gerek.",
|
||||
["Aynalı Göl'de 8 İnci Kabuğu topla.", "Seline'ye ver."]),
|
||||
("Dev Balık Efsanesi", "seline", None,
|
||||
"Gölde efsanevi bir balık görülmüş; kanıt getir.",
|
||||
["Aynalı Göl'ü araştır.", "Efsane balığı yakala.", "Seline'ye göster."]),
|
||||
("Korsan Kampı", "seline", "B-1",
|
||||
"Kıyı korsanları balıkçı ağlarını yağmalıyor.",
|
||||
["Kıyıdaki 4 haydudu/korsanı temizle.", "Seline'ye bildir."]),
|
||||
("Sülük Bela", "seline", "Slyvandor",
|
||||
"Bataklık sülükleri balık ağlarına doluşuyor.",
|
||||
["Slyvandor'da 6 Bataklık Sülüğü öldür.", "5 Sülük Salgısı Verena için topla."]),
|
||||
|
||||
# ── Ek karışık (dünya doldurma) ──
|
||||
("Tuz Toplayıcı", "bramble", None,
|
||||
"Han'ın eti kürlemek için tuza ihtiyacı var.",
|
||||
["Fırtına Kıyısı'ndan 8 Kristalize Tuz topla.", "Bramble'a getir."]),
|
||||
("Akbaba Sürüsü", "doran", "B-1",
|
||||
"Leş akbabaları çorak yolcularına saldırıyor.",
|
||||
["B-1'de 6 Leş Akbabası öldür.", "Doran'a bildir."]),
|
||||
("Kutsal Taş", "thariel", "T-2",
|
||||
"Sunak onarımı için bekçi heykelinden kutsal taş gerek.",
|
||||
["Taş Bekçi Heykeli'ni yık.", "2 Kutsal Taş getir."]),
|
||||
("Bal ve Mum", "grungni", "Akça Koru",
|
||||
"Madenciler karanlıkta çalışmak için mum, ustabaşı da tatlı istiyor.",
|
||||
["Akça Koru'dan 5 Yaban Balı topla.", "Grungni'ye getir."]),
|
||||
("Goblin Reisi", "kael", "Rivenwood",
|
||||
"Goblin çetesinin şamanı bölgeyi terörize ediyor.",
|
||||
["Vahşi Şaman Goblin'i öldür.", "4 Goblin Kulağı getir."]),
|
||||
("Kristalize Tuz Siparişi", "verena", None,
|
||||
"Verena koruyucu tuz çemberi için saf tuz istiyor.",
|
||||
["Fırtına Kıyısı'ndan 10 Kristalize Tuz topla.", "Verena'ya ver."]),
|
||||
("Yolcu Kurtarma", "sefa", "Solstice Mire",
|
||||
"Bataklıkta mahsur kalan bir yolcu yardım bekliyor.",
|
||||
["Solstice Mire'da yolcuyu bul (araştır).", "Harpilerden koru.", "Sefa'ya getir."]),
|
||||
("Kadim Kapı", "orin", "M-2",
|
||||
"Orin mağaranın dibindeki mühürlü kapıyı çözmek istiyor.",
|
||||
["M-2'de kapının 3 sembolünü incele (araştır).", "Çizimi Orin'e getir."]),
|
||||
("Şifalı Çorba", "lysa", "Av Toprakları",
|
||||
"Hasta çocuklar için besleyici et suyu gerek.",
|
||||
["Av Toprakları'nda 6 yaban eti getir.", "Lysa'ya teslim et."]),
|
||||
("Kayıp Çoban", "bram", "Uçsuz Çayırlar mı?",
|
||||
"Çayırlarda bir çoban ve sürüsü kaybolmuş.",
|
||||
["Uçsuz Çayırlar'ı araştır.", "Çobanı bul.", "Bram'a bildir."]),
|
||||
("Örümcek İpeği", "verena", "M-2",
|
||||
"Verena dayanıklı örümcek ipeği ile deney yapacak.",
|
||||
["Derin Örümcek'ten 4 ipek topla.", "Verena'ya getir."]),
|
||||
("Kayıp Madenci Ailesi", "grungni", "Rivenhaven",
|
||||
"Bir madencinin ailesi haber bekliyor; adamı bul.",
|
||||
["Rivenhaven'da kayıp madenciyi ara (araştır).", "Haberi Grungni'ye ilet."]),
|
||||
("Tapınak Çanı", "nadia", "T-2",
|
||||
"Tapınak çanı düşmüş; ruhları uzak tutan sesi geri getir.",
|
||||
["T-2'de düşen çanı bul (araştır).", "Bekçiyi geçip çanı as.", "Nadia'ya bildir."]),
|
||||
]
|
||||
|
||||
|
||||
async def resolve_map_tag(c, name):
|
||||
if not name or name.endswith("mı?"):
|
||||
return None
|
||||
return await c.fetchval("SELECT id FROM map_tags WHERE name=$1", name)
|
||||
|
||||
|
||||
async def main():
|
||||
dsn = settings.DATABASE_URL.replace("+asyncpg", "")
|
||||
c = await asyncpg.connect(dsn)
|
||||
|
||||
# 1) Görev verenler (upsert by name)
|
||||
giver_ids = {}
|
||||
for key, name, role, place, about in GIVERS:
|
||||
tag = await c.fetchval("SELECT id FROM map_tags WHERE name=$1", place)
|
||||
desc = f"{about}\n\n{GIVER_MARK}"
|
||||
gid = await c.fetchval("SELECT id FROM npcs WHERE name=$1", name)
|
||||
if gid:
|
||||
await c.execute("UPDATE npcs SET role=$2,stance='friendly',description=$3,"
|
||||
"map_tag_id=$4,updated_at=now() WHERE id=$1", gid, role, desc, tag)
|
||||
else:
|
||||
gid = await c.fetchval("INSERT INTO npcs(name,role,stance,description,map_tag_id) "
|
||||
"VALUES($1,$2,'friendly',$3,$4) RETURNING id", name, role, desc, tag)
|
||||
giver_ids[key] = gid
|
||||
|
||||
# 2) Düşmanlar (upsert by name)
|
||||
for name, role, region, desc in ENEMIES:
|
||||
tag = await c.fetchval("SELECT id FROM map_tags WHERE name=$1", region)
|
||||
eid = await c.fetchval("SELECT id FROM npcs WHERE name=$1", name)
|
||||
if eid:
|
||||
await c.execute("UPDATE npcs SET role=$2,stance='hostile',description=$3,"
|
||||
"map_tag_id=$4,updated_at=now() WHERE id=$1", eid, role, desc, tag)
|
||||
else:
|
||||
await c.execute("INSERT INTO npcs(name,role,stance,description,map_tag_id) "
|
||||
"VALUES($1,$2,'hostile',$3,$4)", name, role, desc, tag)
|
||||
|
||||
# 3) Görev itemleri (upsert by code) + toplanabilir olanları location_resources'a
|
||||
for code, name, rarity, loc, skill, step, xp, about in QITEMS:
|
||||
iid = await c.fetchval("""
|
||||
INSERT INTO items(code,name,type,rarity,base_value,description)
|
||||
VALUES($1,$2,'material',$3,$4,$5)
|
||||
ON CONFLICT(code) DO UPDATE SET name=EXCLUDED.name,rarity=EXCLUDED.rarity,
|
||||
description=EXCLUDED.description RETURNING id
|
||||
""", code, name, rarity, {"common":8,"uncommon":20,"rare":60,"epic":180}.get(rarity,8), about)
|
||||
if loc:
|
||||
lid = await c.fetchval("SELECT id FROM locations WHERE code=$1", loc)
|
||||
if lid:
|
||||
await c.execute("""
|
||||
INSERT INTO location_resources(location_id,item_code,skill,step_cost,xp,cooldown_s)
|
||||
VALUES($1,$2,$3,$4,$5,300)
|
||||
ON CONFLICT(location_id,item_code) DO NOTHING
|
||||
""", lid, code, skill, step, xp)
|
||||
|
||||
# 4) Quest'ler — önce eski seed'i temizle, sonra ekle
|
||||
await c.execute("DELETE FROM quests WHERE author=$1", SEED_TAG)
|
||||
qn = 0
|
||||
for i, (title, gkey, region, summary, objs) in enumerate(QUESTS):
|
||||
gid = giver_ids.get(gkey)
|
||||
rtag = await resolve_map_tag(c, region)
|
||||
qid = await c.fetchval("""
|
||||
INSERT INTO quests(title,quest_type,summary,giver_npc_id,region_tag_id,author,sort_order)
|
||||
VALUES($1,'side',$2,$3,$4,$5,$6) RETURNING id
|
||||
""", title, summary, gid, rtag, SEED_TAG, i)
|
||||
for j, note in enumerate(objs):
|
||||
await c.execute("INSERT INTO quest_objectives(quest_id,note,ordering,map_tag_id) "
|
||||
"VALUES($1,$2,$3,$4)", qid, note, j, rtag)
|
||||
qn += 1
|
||||
|
||||
tot_q = await c.fetchval("SELECT count(*) FROM quests")
|
||||
tot_npc = await c.fetchval("SELECT count(*) FROM npcs")
|
||||
tot_hostile = await c.fetchval("SELECT count(*) FROM npcs WHERE stance='hostile'")
|
||||
tot_items = await c.fetchval("SELECT count(*) FROM items")
|
||||
await c.close()
|
||||
print(f"Görev veren: {len(GIVERS)}, yeni düşman: {len(ENEMIES)}, görev itemi: {len(QITEMS)}")
|
||||
print(f"Yan görev eklendi: {qn}. DB toplam quest: {tot_q}, NPC: {tot_npc} "
|
||||
f"(hostile {tot_hostile}), item: {tot_items}")
|
||||
|
||||
|
||||
asyncio.run(main())
|
||||
249
db/seed_world_graph.py
Normal file
249
db/seed_world_graph.py
Normal file
|
|
@ -0,0 +1,249 @@
|
|||
"""
|
||||
Örnek dünya: 15 lokasyon + 21 yol + lokasyon kaynakları + 12 D&D olayı.
|
||||
Idempotent (code üzerinden upsert). Harita koordinatları 0-1000 (statik görsel için).
|
||||
Yeni ham maddeler (13 odun, cevherler, otlar, balıklar, av) lokasyonlara tier'a göre dağıtılır.
|
||||
Çalıştırma: cd /opt/stepstead && ./venv/bin/python3 db/seed_world_graph.py
|
||||
"""
|
||||
import asyncio
|
||||
import json
|
||||
import sys
|
||||
|
||||
import asyncpg
|
||||
|
||||
sys.path.insert(0, "/opt/stepstead")
|
||||
from config import settings
|
||||
|
||||
DSN = settings.DATABASE_URL.replace("postgresql+asyncpg://", "postgresql://")
|
||||
|
||||
# (code, ad, tür, açıklama, x, y)
|
||||
LOCATIONS = [
|
||||
("koy", "Stepstead Köyü", "village", "Yolculuğun başladığı huzurlu köy. Etrafta taş, sopa ve sarmaşık bulunur.", 163, 186),
|
||||
("pazar", "Pazarköy", "city", "Tüccarların uğrak yeri, canlı bir kasaba.", 232, 392),
|
||||
("liman", "Liman Kenti", "city", "Büyük gemilerin yanaştığı ticaret limanı. Derin deniz balıkçılığı buradan yapılır.", 140, 630),
|
||||
("fisilti_ormani", "Fısıltı Ormanı", "forest", "Köye en yakın orman. Meşe ve çam ağaçları.", 205, 110),
|
||||
("gumus_camlik", "Gümüş Çamlık", "forest", "Sisli tepelerde huş ve kayın ağaçları.", 110, 300),
|
||||
("akca_koru", "Akça Koru", "forest", "Akçaağaç ve söğütleriyle ünlü sakin koru.", 282, 255),
|
||||
("kadim_orman", "Kadim Orman", "forest", "Yüzyıllık dişbudak, sedir ve ceviz ağaçları.", 320, 468),
|
||||
("karanlik_orman", "Karaağaç Karanlığı", "forest", "En değerli ağaçların büyüdüğü tekinsiz orman: porsuk, maun, abanoz...", 262, 600),
|
||||
("tas_ocagi", "Taş Ocağı", "mine", "Bakır ve kalay damarları, bol kömür.", 610, 290),
|
||||
("derin_maden", "Derin Maden", "mine", "Demir ve gümüş çıkarılan eski maden.", 750, 320),
|
||||
("ejder_dagi", "Ejder Dağı", "mine", "Altın damarlarının saklandığı efsanevi zirve.", 790, 115),
|
||||
("aynali_gol", "Aynalı Göl", "lake", "Sazan ve alabalığın bol olduğu berrak göl.", 723, 655),
|
||||
("firtina_kiyisi", "Fırtına Kıyısı", "lake", "Dalgalı denizde uskumru, palamut ve lüfer.", 685, 905),
|
||||
("cayirlar", "Uçsuz Çayırlar", "meadow", "Şifalı otların yetiştiği rüzgârlı çayırlar.", 590, 760),
|
||||
("av_topraklari", "Av Toprakları", "hunting", "Tavşan ve geyik avlanır. Daha derinde kurt vadisi.", 855, 712),
|
||||
]
|
||||
|
||||
# (a, b, adım, olay_şansı)
|
||||
ROADS = [
|
||||
("koy", "fisilti_ormani", 320, 0.30),
|
||||
("koy", "tas_ocagi", 350, 0.30),
|
||||
("koy", "aynali_gol", 380, 0.25),
|
||||
("koy", "cayirlar", 400, 0.30),
|
||||
("koy", "pazar", 450, 0.20),
|
||||
("fisilti_ormani", "gumus_camlik", 400, 0.35),
|
||||
("gumus_camlik", "akca_koru", 450, 0.35),
|
||||
("akca_koru", "kadim_orman", 500, 0.40),
|
||||
("kadim_orman", "karanlik_orman", 600, 0.50),
|
||||
("gumus_camlik", "av_topraklari", 500, 0.40),
|
||||
("av_topraklari", "karanlik_orman", 650, 0.50),
|
||||
("tas_ocagi", "derin_maden", 450, 0.35),
|
||||
("derin_maden", "ejder_dagi", 600, 0.45),
|
||||
("aynali_gol", "pazar", 300, 0.20),
|
||||
("pazar", "liman", 500, 0.25),
|
||||
("liman", "firtina_kiyisi", 250, 0.25),
|
||||
("aynali_gol", "firtina_kiyisi", 550, 0.35),
|
||||
("cayirlar", "av_topraklari", 450, 0.35),
|
||||
("cayirlar", "gumus_camlik", 350, 0.30),
|
||||
("derin_maden", "liman", 550, 0.30),
|
||||
("cayirlar", "tas_ocagi", 300, 0.25),
|
||||
]
|
||||
|
||||
# lokasyon → [(item_code, skill, adım, xp, cooldown_s)]
|
||||
RESOURCES = {
|
||||
"koy": [
|
||||
("stick", "foraging", 4, 3, 120), ("stone_small", "foraging", 5, 3, 120),
|
||||
("vine", "foraging", 6, 4, 120), ("herb_mint", "foraging", 8, 6, 180),
|
||||
],
|
||||
"fisilti_ormani": [
|
||||
("log_oak", "woodcutting", 12, 8, 240), ("log_pine", "woodcutting", 14, 10, 240),
|
||||
("stick", "foraging", 4, 3, 120), ("vine", "foraging", 6, 4, 120),
|
||||
("herb_sage", "foraging", 9, 7, 180),
|
||||
],
|
||||
"gumus_camlik": [
|
||||
("log_birch", "woodcutting", 16, 12, 240), ("log_beech", "woodcutting", 18, 14, 240),
|
||||
("herb_thyme", "foraging", 10, 8, 180), ("stick", "foraging", 4, 3, 120),
|
||||
],
|
||||
"akca_koru": [
|
||||
("log_maple", "woodcutting", 20, 16, 300), ("log_willow", "woodcutting", 22, 18, 300),
|
||||
("herb_chamomile", "foraging", 11, 9, 180), ("vine", "foraging", 6, 4, 120),
|
||||
],
|
||||
"kadim_orman": [
|
||||
("log_ash", "woodcutting", 24, 20, 300), ("log_cedar", "woodcutting", 26, 22, 300),
|
||||
("log_walnut", "woodcutting", 28, 24, 300), ("herb_nettle", "foraging", 12, 10, 180),
|
||||
],
|
||||
"karanlik_orman": [
|
||||
("log_cherry", "woodcutting", 30, 26, 360), ("log_yew", "woodcutting", 34, 30, 360),
|
||||
("log_mahogany", "woodcutting", 38, 34, 360), ("log_ebony", "woodcutting", 42, 38, 360),
|
||||
("herb_ginseng", "foraging", 16, 14, 240), ("herb_saffron", "foraging", 18, 16, 240),
|
||||
],
|
||||
"tas_ocagi": [
|
||||
("ore_copper", "mining", 15, 10, 240), ("ore_tin", "mining", 15, 10, 240),
|
||||
("coal", "mining", 12, 8, 240), ("stone_small", "foraging", 5, 3, 120),
|
||||
],
|
||||
"derin_maden": [
|
||||
("ore_iron", "mining", 20, 16, 300), ("coal", "mining", 12, 8, 240),
|
||||
("ore_silver", "mining", 26, 22, 300),
|
||||
],
|
||||
"ejder_dagi": [
|
||||
("ore_gold", "mining", 34, 30, 360), ("ore_silver", "mining", 26, 22, 300),
|
||||
("ore_iron", "mining", 20, 16, 300),
|
||||
],
|
||||
"aynali_gol": [
|
||||
("fish_carp", "fishing", 14, 9, 240), ("fish_perch", "fishing", 15, 10, 240),
|
||||
("fish_trout", "fishing", 18, 14, 240), ("vine", "foraging", 6, 4, 120),
|
||||
],
|
||||
"firtina_kiyisi": [
|
||||
("fish_anchovy", "fishing", 14, 9, 240), ("fish_mackerel", "fishing", 16, 12, 240),
|
||||
("fish_bonito", "fishing", 18, 14, 240), ("fish_bluefish", "fishing", 20, 16, 240),
|
||||
("fish_bream", "fishing", 20, 16, 240),
|
||||
],
|
||||
"liman": [
|
||||
("fish_pike", "fishing", 24, 20, 300), ("fish_catfish", "fishing", 26, 22, 300),
|
||||
("fish_salmon", "fishing", 28, 24, 300), ("fish_turbot", "fishing", 30, 26, 300),
|
||||
("fish_tuna", "fishing", 34, 30, 360), ("fish_sturgeon", "fishing", 38, 34, 360),
|
||||
],
|
||||
"cayirlar": [
|
||||
("herb_rosemary", "foraging", 10, 8, 180), ("herb_basil", "foraging", 10, 8, 180),
|
||||
("herb_lemonbalm", "foraging", 12, 10, 180), ("herb_fennel", "foraging", 12, 10, 180),
|
||||
("herb_lavender", "foraging", 14, 12, 180), ("stick", "foraging", 4, 3, 120),
|
||||
],
|
||||
"av_topraklari": [
|
||||
("meat_rabbit", "hunting", 16, 12, 300), ("leather_rabbit", "hunting", 16, 12, 300),
|
||||
("meat_deer", "hunting", 20, 16, 300), ("leather_deer", "hunting", 20, 16, 300),
|
||||
("meat_boar", "hunting", 24, 20, 360), ("leather_boar", "hunting", 24, 20, 360),
|
||||
("meat_wolf", "hunting", 28, 24, 360), ("leather_wolf", "hunting", 28, 24, 360),
|
||||
("meat_bear", "hunting", 34, 30, 420), ("leather_bear", "hunting", 34, 30, 420),
|
||||
],
|
||||
"pazar": [
|
||||
("herb_mint", "foraging", 8, 6, 180), ("stick", "foraging", 4, 3, 120),
|
||||
],
|
||||
}
|
||||
|
||||
# D&D olay kartları. choices: skill check'li {text, skill, dc, success{}, fail{}} ya da düz {text, outcome{}}
|
||||
EVENTS = [
|
||||
("gezgin_tacir", "Gezgin Tacir", "Yolda rengarenk arabasıyla bir tacirle karşılaşıyorsun. Sana el sallıyor.", [
|
||||
{"text": "Selam ver ve sohbet et", "outcome": {"text": "Tacir sohbetten memnun, sana bir nane çayı ikram ediyor.", "items": [{"code": "tea_mint", "qty": 1}]}},
|
||||
{"text": "Başını öne eğip yoluna devam et", "outcome": {"text": "Tacir omuz silkip uzaklaşıyor."}},
|
||||
]),
|
||||
("haydut_pususu", "Haydut Pususu", "Çalıların arasından iki haydut fırlıyor! 'Adımlarını ya da canını!'", [
|
||||
{"text": "Savaş!", "skill": "combat", "dc": 12,
|
||||
"success": {"text": "Haydutları püskürttün! Kaçarken düşürdükleri keseyi aldın.", "items": [{"code": "stone_small", "qty": 3}], "xp": {"skill": "combat", "amount": 25}},
|
||||
"fail": {"text": "Dayak yedin ve kaçtın. Yorgunluktan adımların eridi.", "steps": -150}},
|
||||
{"text": "Kaçmayı dene", "skill": "hunting", "dc": 10,
|
||||
"success": {"text": "Çevik adımlarla izini kaybettirdin.", "xp": {"skill": "hunting", "amount": 15}},
|
||||
"fail": {"text": "Yakalandın, keseni silkelediler.", "steps": -100}},
|
||||
{"text": "Adımlarından 100 ver", "outcome": {"text": "Haydutlar parayı alıp gözden kayboldu.", "steps": -100}},
|
||||
]),
|
||||
("yarali_gezgin", "Yaralı Gezgin", "Yol kenarında ayağı burkulmuş bir gezgin oturuyor, yardım istiyor.", [
|
||||
{"text": "Yarasını sar (Simya)", "skill": "alchemy", "dc": 8,
|
||||
"success": {"text": "Gezgin minnetle teşekkür edip çantasından ot uzatıyor.", "items": [{"code": "herb_mint", "qty": 2}], "xp": {"skill": "alchemy", "amount": 20}},
|
||||
"fail": {"text": "Elinden geleni yaptın ama pek işe yaramadı. Zaman kaybettin.", "steps": -30}},
|
||||
{"text": "Görmezden gel", "outcome": {"text": "Vicdanın sızlasa da yoluna devam ettin."}},
|
||||
]),
|
||||
("kayip_sandik", "Kayıp Sandık", "Patikanın kenarında yarı gömülü eski bir sandık görüyorsun.", [
|
||||
{"text": "Kazıp aç", "outcome": {"text": "İçinden işe yarar malzemeler çıktı!", "items": [{"code": "stone_small", "qty": 2}, {"code": "vine", "qty": 2}]}},
|
||||
{"text": "Tuzak olabilir, dokunma", "outcome": {"text": "Temkinli davranıp uzaklaştın."}},
|
||||
]),
|
||||
("dev_ayi", "Dev Ayı", "Önünde iki ayağı üzerinde doğrulmuş kocaman bir ayı! Homurtusu yeri titretiyor.", [
|
||||
{"text": "Savaş!", "skill": "combat", "dc": 15,
|
||||
"success": {"text": "Zorlu bir mücadele sonunda ayıyı alt ettin!", "items": [{"code": "meat_bear", "qty": 1}, {"code": "leather_bear", "qty": 1}], "xp": {"skill": "combat", "amount": 40}},
|
||||
"fail": {"text": "Ayı seni fena hırpaladı. Canını zor kurtardın.", "steps": -200}},
|
||||
{"text": "Yavaşça geri çekil", "skill": "hunting", "dc": 8,
|
||||
"success": {"text": "Ses çıkarmadan uzaklaştın.", "xp": {"skill": "hunting", "amount": 10}},
|
||||
"fail": {"text": "Bir dala bastın! Ayı kovaladı, koşmaktan tükendin.", "steps": -120}},
|
||||
]),
|
||||
("mantar_halkasi", "Mantar Halkası", "Bir açıklıkta mükemmel daire çizen mantarlar... Ortasında bir şey parlıyor.", [
|
||||
{"text": "Dikkatlice topla (Toplama)", "skill": "foraging", "dc": 10,
|
||||
"success": {"text": "Halkanın ortasından nadir otlar topladın!", "items": [{"code": "herb_chamomile", "qty": 2}], "xp": {"skill": "foraging", "amount": 20}},
|
||||
"fail": {"text": "Mantarlar zehirliymiş, başın döndü.", "steps": -60}},
|
||||
{"text": "Uğursuzluktur, dokunma", "outcome": {"text": "Halkaya saygıyla selam verip geçtin."}},
|
||||
]),
|
||||
("eski_harabe", "Eski Harabe", "Sarmaşıklarla kaplı yıkık bir taş yapı. İçinden serin bir esinti geliyor.", [
|
||||
{"text": "İçini araştır (İnşaat)", "skill": "construction", "dc": 12,
|
||||
"success": {"text": "Sağlam taşlar ve eski aletler buldun!", "items": [{"code": "stone_small", "qty": 4}], "xp": {"skill": "construction", "amount": 25}},
|
||||
"fail": {"text": "Bir taş kaydı, az daha altında kalıyordun. Hızla çıktın.", "steps": -80}},
|
||||
{"text": "Dışarıdan bak, devam et", "outcome": {"text": "Harabeyi haritana not edip ilerledin."}},
|
||||
]),
|
||||
("nehir_gecidi", "Nehir Geçidi", "Yol taşkın bir dereyle kesilmiş. Sığ bir geçit var ama akıntı hızlı.", [
|
||||
{"text": "Geçerken balık da yakala (Balıkçılık)", "skill": "fishing", "dc": 8,
|
||||
"success": {"text": "Hem karşıya geçtin hem eline bir sazan takıldı!", "items": [{"code": "fish_carp", "qty": 1}], "xp": {"skill": "fishing", "amount": 15}},
|
||||
"fail": {"text": "Ayağın kaydı, sırılsıklam oldun.", "steps": -40}},
|
||||
{"text": "Uzun ama güvenli yoldan dolaş", "outcome": {"text": "Ekstra yürüdün ama kuru kaldın.", "steps": -50}},
|
||||
]),
|
||||
("gizemli_yasli", "Gizemli Yaşlı", "Bastonlu bir ihtiyar taşın üstünde oturmuş, sana bakıp gülümsüyor: 'Otların dilinden anlar mısın evlat?'", [
|
||||
{"text": "Yanına otur ve dinle", "outcome": {"text": "Sana otların sırlarını anlattı. Bilgeleştin.", "xp": {"skill": "alchemy", "amount": 20}}},
|
||||
{"text": "Kibarca reddet", "outcome": {"text": "İhtiyar gülümseyip gözlerini kapadı."}},
|
||||
]),
|
||||
("kirik_araba", "Kırık Araba", "Bir çiftçinin arabasının tekeri kırılmış, yükü yola saçılmış.", [
|
||||
{"text": "Tamir et (İnşaat)", "skill": "construction", "dc": 10,
|
||||
"success": {"text": "Tekeri taktın! Çiftçi teşekkür olarak sarmaşık ipi verdi.", "items": [{"code": "vine", "qty": 3}], "xp": {"skill": "construction", "amount": 20}},
|
||||
"fail": {"text": "Uğraştın ama beceremedin. Çiftçi yine de teşekkür etti.", "steps": -30}},
|
||||
{"text": "Yardım edemem, acelem var", "outcome": {"text": "Çiftçi arkandan söylendi."}},
|
||||
]),
|
||||
("saganak", "Sağanak", "Gökyüzü kararıyor, bardaktan boşanırcasına yağmur başlıyor!", [
|
||||
{"text": "Hızlıca sığınak yap (İnşaat)", "skill": "construction", "dc": 8,
|
||||
"success": {"text": "Dallardan derme çatma ama işlevsel bir sığınak! Yağmuru kuru geçirdin.", "xp": {"skill": "construction", "amount": 15}},
|
||||
"fail": {"text": "Sığınak çöktü, iliklerine kadar ıslandın.", "steps": -60}},
|
||||
{"text": "Yağmurda yürümeye devam et", "outcome": {"text": "Islandın, üşüdün, yoruldun ama yol aldın.", "steps": -30}},
|
||||
]),
|
||||
("sansli_bulus", "Şanslı Buluş", "Patikanın hemen kenarında birinin unuttuğu küçük bir malzeme yığını duruyor.", [
|
||||
{"text": "Topla", "outcome": {"text": "Sopa ve taşları sırt çantana koydun.", "items": [{"code": "stick", "qty": 2}, {"code": "stone_small", "qty": 2}]}},
|
||||
{"text": "Sahibi gelir, bırak", "outcome": {"text": "Dürüstlüğünle gurur duyarak ilerledin."}},
|
||||
]),
|
||||
]
|
||||
|
||||
|
||||
async def main():
|
||||
conn = await asyncpg.connect(DSN)
|
||||
|
||||
for code, name, typ, desc, x, y in LOCATIONS:
|
||||
await conn.execute("""
|
||||
INSERT INTO locations (code, name, type, description, map_x, map_y)
|
||||
VALUES ($1,$2,$3,$4,$5,$6)
|
||||
ON CONFLICT (code) DO UPDATE SET name=$2, type=$3, description=$4, map_x=$5, map_y=$6
|
||||
""", code, name, typ, desc, x, y)
|
||||
ids = {r["code"]: r["id"] for r in await conn.fetch("SELECT id, code FROM locations")}
|
||||
|
||||
for a, b, cost, chance in ROADS:
|
||||
await conn.execute("""
|
||||
INSERT INTO roads (loc_a, loc_b, step_cost, event_chance)
|
||||
VALUES ($1,$2,$3,$4)
|
||||
ON CONFLICT (loc_a, loc_b) DO UPDATE SET step_cost=$3, event_chance=$4
|
||||
""", ids[a], ids[b], cost, chance)
|
||||
|
||||
n_res = 0
|
||||
for loc, res in RESOURCES.items():
|
||||
for item, skill, steps, xp, cd in res:
|
||||
await conn.execute("""
|
||||
INSERT INTO location_resources (location_id, item_code, skill, step_cost, xp, cooldown_s)
|
||||
VALUES ($1,$2,$3,$4,$5,$6)
|
||||
ON CONFLICT (location_id, item_code) DO UPDATE SET skill=$3, step_cost=$4, xp=$5, cooldown_s=$6
|
||||
""", ids[loc], item, skill, steps, xp, cd)
|
||||
n_res += 1
|
||||
|
||||
for code, name, txt, choices in EVENTS:
|
||||
await conn.execute("""
|
||||
INSERT INTO events (code, name, text, choices) VALUES ($1,$2,$3,$4::jsonb)
|
||||
ON CONFLICT (code) DO UPDATE SET name=$2, text=$3, choices=$4::jsonb
|
||||
""", code, name, txt, json.dumps(choices))
|
||||
|
||||
# Konumu olmayan karakterleri köye koy
|
||||
moved = await conn.execute(
|
||||
"UPDATE characters SET location_id=$1 WHERE location_id IS NULL", ids["koy"])
|
||||
|
||||
print(f"Lokasyon: {len(LOCATIONS)}, yol: {len(ROADS)}, kaynak: {n_res}, olay: {len(EVENTS)}. Karakter taşındı: {moved}")
|
||||
await conn.close()
|
||||
|
||||
|
||||
asyncio.run(main())
|
||||
Loading…
Add table
Add a link
Reference in a new issue