160 lines
5.9 KiB
Python
160 lines
5.9 KiB
Python
"""
|
||
Envanter + consumable kullanımı + tool tanımları.
|
||
|
||
NOT (2026-07-04): Node hasat sistemi kaldırıldı — kaynak toplama artık
|
||
lokasyon bazlı (routers/travel.py: POST /world/gather). SKILL_TOOLS burada
|
||
tanımlı kalır, travel.py buradan import eder.
|
||
"""
|
||
from datetime import datetime, timezone
|
||
|
||
from fastapi import APIRouter, Depends, HTTPException
|
||
from pydantic import BaseModel, Field
|
||
from sqlalchemy import bindparam, select, text
|
||
from sqlalchemy.ext.asyncio import AsyncSession
|
||
|
||
from deps import get_current_user
|
||
from models import Character, User, get_db
|
||
|
||
router = APIRouter(prefix="/world", tags=["inventory"])
|
||
|
||
# Tool şartı: bu skill'lerle toplama için envanterde listedeki tool'lardan biri olmalı.
|
||
# Foraging (elle toplama) serbest. İlk tool'lar elle toplanan taş/sopa/sarmaşıktan craft edilir.
|
||
SKILL_TOOLS = {
|
||
"woodcutting": ["axe_stone", "axe_copper", "axe_iron"],
|
||
"mining": ["pickaxe_stone", "pickaxe_copper", "pickaxe_iron"],
|
||
"fishing": ["fishing_rod", "fishing_rod_fine", "fishing_rod_master"],
|
||
}
|
||
TOOL_LABEL = {"woodcutting": "Balta", "mining": "Kazma", "fishing": "Olta"}
|
||
|
||
|
||
async def _has_any_tool(db: AsyncSession, char_id: int, codes: list[str]) -> bool:
|
||
stmt = text("""
|
||
SELECT 1 FROM inventory inv JOIN items i ON i.id = inv.item_id
|
||
WHERE inv.character_id = :cid AND i.code IN :codes LIMIT 1
|
||
""").bindparams(bindparam("codes", expanding=True))
|
||
row = (await db.execute(stmt, {"cid": char_id, "codes": codes})).first()
|
||
return row is not None
|
||
|
||
|
||
async def consume_items(db: AsyncSession, char_id: int, item_id: int, qty: int, item_name: str) -> None:
|
||
"""Envanterden FIFO tüket — market/camp/use-item ortak paterni.
|
||
Yetersizse HTTPException(400) fırlatır."""
|
||
stacks = (await db.execute(text(
|
||
"SELECT id, qty FROM inventory WHERE character_id=:cid AND item_id=:iid ORDER BY slot"
|
||
), {"cid": char_id, "iid": item_id})).all()
|
||
have = sum(s[1] for s in stacks)
|
||
if have < qty:
|
||
raise HTTPException(400, f"{item_name} yetersiz: {qty} gerekli, {have} var")
|
||
remaining = qty
|
||
for st in stacks:
|
||
if remaining <= 0:
|
||
break
|
||
take = min(remaining, st[1])
|
||
if take == st[1]:
|
||
await db.execute(text("DELETE FROM inventory WHERE id = :id"), {"id": st[0]})
|
||
else:
|
||
await db.execute(text("UPDATE inventory SET qty = qty - :t WHERE id = :id"), {"t": take, "id": st[0]})
|
||
remaining -= take
|
||
|
||
|
||
class InventoryItem(BaseModel):
|
||
slot: int
|
||
item_code: str
|
||
item_name: str
|
||
rarity: str
|
||
qty: int
|
||
icon: str | None = None
|
||
item_type: str = "material"
|
||
effect: dict | None = None
|
||
|
||
|
||
@router.get("/inventory", response_model=list[InventoryItem])
|
||
async def inventory(user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db)):
|
||
char = (await db.execute(select(Character).where(Character.user_id == user.id))).scalar_one_or_none()
|
||
if not char:
|
||
return []
|
||
rows = (await db.execute(text("""
|
||
SELECT inv.slot, i.code, i.name, i.rarity, inv.qty, i.icon, i.type, i.effect
|
||
FROM inventory inv JOIN items i ON i.id = inv.item_id
|
||
WHERE inv.character_id = :cid
|
||
ORDER BY inv.slot
|
||
"""), {"cid": char.id})).all()
|
||
return [
|
||
InventoryItem(slot=r[0], item_code=r[1], item_name=r[2], rarity=r[3], qty=r[4],
|
||
icon=r[5], item_type=r[6], effect=r[7])
|
||
for r in rows
|
||
]
|
||
|
||
|
||
class UseItemIn(BaseModel):
|
||
item_code: str
|
||
qty: int = Field(1, ge=1, le=10)
|
||
|
||
|
||
class UseItemOut(BaseModel):
|
||
item_code: str
|
||
item_name: str
|
||
qty_used: int
|
||
effect_type: str
|
||
effect_amount: int
|
||
step_balance: int
|
||
|
||
|
||
@router.post("/use-item", response_model=UseItemOut)
|
||
async def use_item(u: UseItemIn, user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db)):
|
||
char = (await db.execute(select(Character).where(Character.user_id == user.id))).scalar_one_or_none()
|
||
if not char:
|
||
raise HTTPException(404, "Karakter yok")
|
||
|
||
item = (await db.execute(
|
||
text("SELECT id, name, type, effect FROM items WHERE code = :c"), {"c": u.item_code}
|
||
)).first()
|
||
if not item:
|
||
raise HTTPException(404, "Item yok")
|
||
item_id, item_name, item_type, effect = item[0], item[1], item[2], item[3]
|
||
if item_type != "consumable" or not effect:
|
||
raise HTTPException(400, f"{item_name} kullanılabilir bir item değil")
|
||
if effect.get("type") != "steps":
|
||
raise HTTPException(400, f"Bilinmeyen etki türü: {effect.get('type')}")
|
||
|
||
# Envanterde yeterli mi (FIFO tüket — craft ile aynı patern)
|
||
stacks = (await db.execute(text("""
|
||
SELECT id, qty FROM inventory
|
||
WHERE character_id = :cid AND item_id = :iid
|
||
ORDER BY slot
|
||
"""), {"cid": char.id, "iid": item_id})).all()
|
||
have = sum(s[1] for s in stacks)
|
||
if have < u.qty:
|
||
raise HTTPException(400, f"{item_name} yetersiz: {u.qty} gerekli, {have} var")
|
||
|
||
remaining = u.qty
|
||
for st in stacks:
|
||
if remaining <= 0:
|
||
break
|
||
take = min(remaining, st[1])
|
||
if take == st[1]:
|
||
await db.execute(text("DELETE FROM inventory WHERE id = :id"), {"id": st[0]})
|
||
else:
|
||
await db.execute(text("UPDATE inventory SET qty = qty - :t WHERE id = :id"), {"t": take, "id": st[0]})
|
||
remaining -= take
|
||
|
||
# Etki: adım bakiyesi yenile (lifetime'a sayılmaz — gerçek yürüyüş değil)
|
||
gained = int(effect["amount"]) * u.qty
|
||
char.step_balance += gained
|
||
await db.execute(
|
||
text("INSERT INTO step_log (character_id, steps_delta, source, accepted, reason) "
|
||
"VALUES (:cid, :d, 'consumable', true, :r)"),
|
||
{"cid": char.id, "d": gained, "r": f"{u.item_code} x{u.qty}"},
|
||
)
|
||
|
||
char.last_sync_at = datetime.now(timezone.utc)
|
||
await db.commit()
|
||
|
||
return UseItemOut(
|
||
item_code=u.item_code,
|
||
item_name=item_name,
|
||
qty_used=u.qty,
|
||
effect_type="steps",
|
||
effect_amount=gained,
|
||
step_balance=char.step_balance,
|
||
)
|