İlk commit: Stepstead backend (FastAPI)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
jts 2026-07-11 11:30:24 +03:00
commit ea049b8ccd
45 changed files with 6751 additions and 0 deletions

166
db/seed_enemies_forest.py Normal file
View 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. "
"ı 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="ı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="ı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())