add lode
This commit is contained in:
@@ -38,6 +38,7 @@ from .assassinate import Assassinate
|
||||
from .move_to_direction import MoveToDirection
|
||||
from .cast import Cast
|
||||
from .buy import Buy
|
||||
from .mine import Mine
|
||||
|
||||
# 注册到 ActionRegistry(标注是否为实际可执行动作)
|
||||
register_action(actual=False)(Action)
|
||||
@@ -72,6 +73,7 @@ register_action(actual=True)(Assassinate)
|
||||
register_action(actual=True)(MoveToDirection)
|
||||
register_action(actual=True)(Cast)
|
||||
register_action(actual=True)(Buy)
|
||||
register_action(actual=True)(Mine)
|
||||
# Talk 已移动到 mutual_action 模块,在那里注册
|
||||
|
||||
__all__ = [
|
||||
@@ -109,8 +111,7 @@ __all__ = [
|
||||
"MoveToDirection",
|
||||
"Cast",
|
||||
"Buy",
|
||||
# Talk 已移动到 mutual_action 模块
|
||||
# Occupy 已移动到 mutual_action 模块
|
||||
"Mine",
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ from src.classes.action import TimedAction
|
||||
from src.classes.cultivation import Realm
|
||||
from src.classes.event import Event
|
||||
from src.classes.item import Item
|
||||
from src.classes.lode import ORE_ITEM_IDS
|
||||
from src.classes.weapon import get_random_weapon_by_realm
|
||||
from src.classes.auxiliary import get_random_auxiliary_by_realm
|
||||
from src.classes.single_choice import handle_item_exchange
|
||||
@@ -32,7 +33,7 @@ class Cast(TimedAction):
|
||||
Realm.Nascent_Soul: 0.1,
|
||||
}
|
||||
|
||||
DOABLES_REQUIREMENTS = f"拥有{COST}个同境界材料"
|
||||
DOABLES_REQUIREMENTS = f"拥有{COST}个同境界矿石材料"
|
||||
PARAMS = {"target_realm": "目标境界名称('练气'、'筑基'、'金丹'、'元婴')"}
|
||||
IS_MAJOR = False
|
||||
|
||||
@@ -48,13 +49,12 @@ class Cast(TimedAction):
|
||||
def _count_materials(self, realm: Realm) -> int:
|
||||
"""
|
||||
统计符合条件的材料数量。
|
||||
注意:仅统计 Item 类的直接实例,不统计 Weapon/Auxiliary 等子类(它们也是 Item,但通常不作为铸造原材料)。
|
||||
注意:仅统计 Item 类的直接实例,且必须在 ORE_ITEM_IDS 中。
|
||||
"""
|
||||
count = 0
|
||||
for item, qty in self.avatar.items.items():
|
||||
# 这里使用 type(item) is Item 来严格限制必须是基础材料
|
||||
# 如果项目里有其他继承自 Item 的材料类,可能需要放宽这个限制
|
||||
if type(item).__name__ == "Item" and item.realm == realm:
|
||||
# 增加判断:item.id 必须在 ORE_ITEM_IDS 中
|
||||
if type(item).__name__ == "Item" and item.realm == realm and item.id in ORE_ITEM_IDS:
|
||||
count += qty
|
||||
return count
|
||||
|
||||
@@ -80,7 +80,6 @@ class Cast(TimedAction):
|
||||
res = resolve_query(target_realm, expected_types=[Realm])
|
||||
if res.is_valid:
|
||||
self.target_realm = res.obj
|
||||
self.target_realm = Realm(target_realm)
|
||||
|
||||
cost = self._get_cost()
|
||||
|
||||
@@ -92,7 +91,7 @@ class Cast(TimedAction):
|
||||
for item, qty in self.avatar.items.items():
|
||||
if to_deduct <= 0:
|
||||
break
|
||||
if type(item).__name__ == "Item" and item.realm == self.target_realm:
|
||||
if type(item).__name__ == "Item" and item.realm == self.target_realm and item.id in ORE_ITEM_IDS:
|
||||
take = min(qty, to_deduct)
|
||||
items_to_modify.append((item, take))
|
||||
to_deduct -= take
|
||||
|
||||
72
src/classes/action/mine.py
Normal file
72
src/classes/action/mine.py
Normal file
@@ -0,0 +1,72 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import random
|
||||
from src.classes.action import TimedAction
|
||||
from src.classes.event import Event
|
||||
from src.classes.region import NormalRegion
|
||||
|
||||
|
||||
class Mine(TimedAction):
|
||||
"""
|
||||
挖矿动作,在有矿脉的区域进行挖矿,持续6个月
|
||||
可以获得矿脉对应的矿石
|
||||
"""
|
||||
|
||||
ACTION_NAME = "挖矿"
|
||||
EMOJI = "⛏️"
|
||||
DESC = "在当前区域挖掘矿脉,获取矿石材料"
|
||||
DOABLES_REQUIREMENTS = "在有矿脉的普通区域,且avatar的境界必须大于等于矿脉的境界"
|
||||
PARAMS = {}
|
||||
|
||||
duration_months = 6
|
||||
|
||||
def _execute(self) -> None:
|
||||
"""
|
||||
执行挖矿动作
|
||||
"""
|
||||
region = self.avatar.tile.region
|
||||
lodes = getattr(region, "lodes", [])
|
||||
if len(lodes) == 0:
|
||||
return
|
||||
available_lodes = [
|
||||
lode for lode in lodes
|
||||
if self.avatar.cultivation_progress.realm >= lode.realm
|
||||
]
|
||||
if len(available_lodes) == 0:
|
||||
return
|
||||
|
||||
# 目前固定100%成功率
|
||||
if random.random() < 1.0:
|
||||
target_lode = random.choice(available_lodes)
|
||||
# 随机选择该矿脉的一种物品
|
||||
item = random.choice(target_lode.items)
|
||||
# 基础获得1个,额外物品来自effects
|
||||
base_quantity = 1
|
||||
extra_items = int(self.avatar.effects.get("extra_mine_items", 0) or 0)
|
||||
total_quantity = base_quantity + extra_items
|
||||
self.avatar.add_item(item, total_quantity)
|
||||
|
||||
def can_start(self) -> tuple[bool, str]:
|
||||
region = self.avatar.tile.region
|
||||
if not isinstance(region, NormalRegion):
|
||||
return False, "当前不在普通区域"
|
||||
lodes = getattr(region, "lodes", [])
|
||||
if len(lodes) == 0:
|
||||
return False, "当前区域没有矿脉"
|
||||
available_lodes = [
|
||||
lode for lode in lodes
|
||||
if self.avatar.cultivation_progress.realm >= lode.realm
|
||||
]
|
||||
if len(available_lodes) == 0:
|
||||
return False, "当前区域的矿脉境界过高"
|
||||
return True, ""
|
||||
|
||||
def start(self) -> Event:
|
||||
region = self.avatar.tile.region
|
||||
return Event(self.world.month_stamp, f"{self.avatar.name} 在 {self.avatar.tile.location_name} 开始挖矿", related_avatars=[self.avatar.id])
|
||||
|
||||
# TimedAction 已统一 step 逻辑
|
||||
|
||||
async def finish(self) -> list[Event]:
|
||||
return []
|
||||
|
||||
@@ -12,7 +12,6 @@ from src.classes.avatar.core import (
|
||||
from src.classes.avatar.info_presenter import (
|
||||
get_avatar_info,
|
||||
get_avatar_structured_info,
|
||||
get_avatar_hover_info,
|
||||
get_avatar_expanded_info,
|
||||
get_other_avatar_info,
|
||||
)
|
||||
|
||||
@@ -289,10 +289,6 @@ class Avatar(
|
||||
from src.classes.avatar.info_presenter import get_avatar_structured_info
|
||||
return get_avatar_structured_info(self)
|
||||
|
||||
def get_hover_info(self) -> list[str]:
|
||||
from src.classes.avatar.info_presenter import get_avatar_hover_info
|
||||
return get_avatar_hover_info(self)
|
||||
|
||||
def get_expanded_info(
|
||||
self,
|
||||
co_region_avatars: Optional[List["Avatar"]] = None,
|
||||
|
||||
@@ -228,90 +228,6 @@ def get_avatar_structured_info(avatar: "Avatar") -> dict:
|
||||
return info
|
||||
|
||||
|
||||
def get_avatar_hover_info(avatar: "Avatar") -> list[str]:
|
||||
"""
|
||||
返回用于前端悬浮提示的多行信息。
|
||||
"""
|
||||
def add_kv(lines: list[str], key: str, value: object) -> None:
|
||||
lines.append(f"{key}: {value}")
|
||||
|
||||
def add_section(lines: list[str], title: str, body: list[str]) -> None:
|
||||
lines.append("")
|
||||
lines.append(f"{title}:")
|
||||
lines.extend(body)
|
||||
|
||||
lines: list[str] = []
|
||||
# 基础信息
|
||||
if avatar.nickname:
|
||||
add_kv(lines, "绰号", f"「{avatar.nickname.value}」")
|
||||
|
||||
add_kv(lines, "性别", avatar.gender)
|
||||
add_kv(lines, "年龄", avatar.age)
|
||||
add_kv(lines, "外貌", avatar.appearance.get_info())
|
||||
add_kv(lines, "阵营", avatar.alignment)
|
||||
add_kv(lines, "境界", str(avatar.cultivation_progress))
|
||||
add_kv(lines, "HP", avatar.hp)
|
||||
add_kv(lines, "战斗力", int(get_base_strength(avatar)))
|
||||
add_kv(lines, "宗门", avatar.get_sect_str())
|
||||
|
||||
from src.classes.root import format_root_cn
|
||||
add_kv(lines, "灵根", format_root_cn(avatar.root))
|
||||
|
||||
tech_str = avatar.technique.get_colored_info() if avatar.technique is not None else "无"
|
||||
add_kv(lines, "功法", tech_str)
|
||||
|
||||
if avatar.personas:
|
||||
persona_parts = [p.get_colored_info() for p in avatar.personas]
|
||||
add_kv(lines, "特质", ", ".join(persona_parts))
|
||||
|
||||
add_kv(lines, "灵石", str(avatar.magic_stone))
|
||||
|
||||
# 物品
|
||||
if avatar.items:
|
||||
items_lines = [f" {item.name} x{quantity}" for item, quantity in avatar.items.items()]
|
||||
add_section(lines, "物品", items_lines)
|
||||
else:
|
||||
add_kv(lines, "物品", "无")
|
||||
|
||||
# 思考与目标
|
||||
if avatar.thinking:
|
||||
add_section(lines, "正在思考", [avatar.thinking])
|
||||
if avatar.long_term_objective:
|
||||
add_section(lines, "长期目标", [avatar.long_term_objective.content])
|
||||
if avatar.short_term_objective:
|
||||
add_section(lines, "短期目标", [avatar.short_term_objective])
|
||||
|
||||
# 兵器(必有,使用颜色标记等级)
|
||||
if avatar.weapon is not None:
|
||||
weapon_text = avatar.weapon.get_colored_info()
|
||||
if avatar.weapon.desc:
|
||||
weapon_text += f"({avatar.weapon.desc})"
|
||||
add_kv(lines, "兵器", weapon_text)
|
||||
|
||||
# 辅助装备(可选,使用颜色标记等级)
|
||||
if avatar.auxiliary is not None:
|
||||
auxiliary_text = avatar.auxiliary.get_colored_info()
|
||||
if avatar.auxiliary.desc:
|
||||
auxiliary_text += f"({avatar.auxiliary.desc})"
|
||||
add_kv(lines, "辅助装备", auxiliary_text)
|
||||
else:
|
||||
add_kv(lines, "辅助装备", "无")
|
||||
|
||||
# 灵兽:仅在存在时显示
|
||||
if avatar.spirit_animal is not None:
|
||||
add_kv(lines, "灵兽", avatar.spirit_animal.get_info())
|
||||
|
||||
# 关系(从自身视角分组展示)
|
||||
from src.classes.relation import get_relations_strs
|
||||
relation_lines = get_relations_strs(avatar, max_lines=15)
|
||||
if relation_lines:
|
||||
add_section(lines, "关系", [f" {s}" for s in relation_lines])
|
||||
else:
|
||||
add_kv(lines, "关系", "无")
|
||||
|
||||
return lines
|
||||
|
||||
|
||||
def get_avatar_expanded_info(
|
||||
avatar: "Avatar",
|
||||
co_region_avatars: Optional[List["Avatar"]] = None,
|
||||
|
||||
@@ -115,14 +115,6 @@ def split_colored_segments(text: str) -> list[dict[str, str]]:
|
||||
return segments
|
||||
|
||||
|
||||
def serialize_hover_lines(lines: list[str]) -> list[list[dict[str, str]]]:
|
||||
"""将 hover 信息行转换为 segment 列表,供前端直接渲染颜色。"""
|
||||
serialized: list[list[dict[str, str]]] = []
|
||||
for line in lines:
|
||||
serialized.append(split_colored_segments(line or ""))
|
||||
return serialized
|
||||
|
||||
|
||||
# ==================== 颜色方案映射 ====================
|
||||
|
||||
# 装备等级颜色方案(普通-宝物-法宝)
|
||||
|
||||
87
src/classes/lode.py
Normal file
87
src/classes/lode.py
Normal file
@@ -0,0 +1,87 @@
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional
|
||||
|
||||
from src.utils.df import game_configs, get_str, get_int, get_list_int
|
||||
from src.classes.item import Item, items_by_id
|
||||
from src.classes.cultivation import Realm
|
||||
|
||||
@dataclass
|
||||
class Lode:
|
||||
"""
|
||||
矿脉
|
||||
"""
|
||||
id: int
|
||||
name: str
|
||||
desc: str
|
||||
realm: Realm
|
||||
item_ids: list[int] = field(default_factory=list) # 该矿脉对应的物品IDs
|
||||
|
||||
# 这些字段将在__post_init__中设置
|
||||
items: list[Item] = field(init=False, default_factory=list) # 该矿脉对应的物品实例
|
||||
|
||||
def __post_init__(self):
|
||||
"""初始化物品实例"""
|
||||
for item_id in self.item_ids:
|
||||
if item_id in items_by_id:
|
||||
self.items.append(items_by_id[item_id])
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash(self.id)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.name
|
||||
|
||||
def get_info(self) -> str:
|
||||
"""
|
||||
获取矿脉的详细信息
|
||||
"""
|
||||
info_parts = [f"【{self.name}】({self.realm.value})", self.desc]
|
||||
|
||||
if self.items:
|
||||
item_names = [item.name for item in self.items]
|
||||
info_parts.append(f"可获得矿石:{', '.join(item_names)}")
|
||||
|
||||
return " - ".join(info_parts)
|
||||
|
||||
def get_structured_info(self) -> dict:
|
||||
items_info = [item.get_structured_info() for item in self.items]
|
||||
return {
|
||||
"id": str(self.id),
|
||||
"name": self.name,
|
||||
"desc": self.desc,
|
||||
"grade": self.realm.value,
|
||||
"drops": items_info,
|
||||
"type": "lode"
|
||||
}
|
||||
|
||||
def _load_lodes() -> tuple[dict[int, Lode], dict[str, Lode]]:
|
||||
"""从配表加载lode数据"""
|
||||
lodes_by_id: dict[int, Lode] = {}
|
||||
lodes_by_name: dict[str, Lode] = {}
|
||||
|
||||
# 检查配置是否存在,避免初始化错误
|
||||
if "lode" not in game_configs:
|
||||
return {}, {}
|
||||
|
||||
lode_df = game_configs["lode"]
|
||||
for row in lode_df:
|
||||
item_ids_list = get_list_int(row, "item_ids")
|
||||
|
||||
lode = Lode(
|
||||
id=get_int(row, "id"),
|
||||
name=get_str(row, "name"),
|
||||
desc=get_str(row, "desc"),
|
||||
realm=Realm.from_id(get_int(row, "stage_id")),
|
||||
item_ids=item_ids_list
|
||||
)
|
||||
lodes_by_id[lode.id] = lode
|
||||
lodes_by_name[lode.name] = lode
|
||||
|
||||
return lodes_by_id, lodes_by_name
|
||||
|
||||
# 从配表加载lode数据
|
||||
lodes_by_id, lodes_by_name = _load_lodes()
|
||||
|
||||
# 导出所有属于矿石的物品ID,供铸造逻辑判断
|
||||
ORE_ITEM_IDS = {item_id for lode in lodes_by_id.values() for item_id in lode.item_ids}
|
||||
|
||||
@@ -111,7 +111,7 @@ class Map():
|
||||
|
||||
return {
|
||||
"修炼区域(可以修炼以增进修为)": build_regions_info(filter_regions(CultivateRegion)),
|
||||
"普通区域(可以狩猎或采集)": build_regions_info(filter_regions(NormalRegion)),
|
||||
"普通区域(可以狩猎、采集、挖矿)": build_regions_info(filter_regions(NormalRegion)),
|
||||
"城市区域(可以交易)": build_regions_info(filter_regions(CityRegion)),
|
||||
"宗门总部(宗门弟子可在此进行疗伤等操作)": build_regions_info(filter_regions(SectRegion)),
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ from src.utils.distance import chebyshev_distance
|
||||
from src.classes.essence import EssenceType, Essence
|
||||
from src.classes.animal import Animal, animals_by_id
|
||||
from src.classes.plant import Plant, plants_by_id
|
||||
from src.classes.lode import Lode, lodes_by_id
|
||||
from src.classes.sect import sects_by_name
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -64,12 +65,6 @@ class Region(ABC):
|
||||
def get_region_type(self) -> str:
|
||||
pass
|
||||
|
||||
def get_hover_info(self) -> list[str]:
|
||||
return [
|
||||
f"区域: {self.name}",
|
||||
f"描述: {self.desc}",
|
||||
]
|
||||
|
||||
@abstractmethod
|
||||
def _get_desc(self) -> str:
|
||||
"""
|
||||
@@ -109,9 +104,11 @@ class NormalRegion(Region):
|
||||
"""普通区域"""
|
||||
animal_ids: list[int] = field(default_factory=list)
|
||||
plant_ids: list[int] = field(default_factory=list)
|
||||
lode_ids: list[int] = field(default_factory=list)
|
||||
|
||||
animals: list[Animal] = field(init=False, default_factory=list)
|
||||
plants: list[Plant] = field(init=False, default_factory=list)
|
||||
lodes: list[Lode] = field(init=False, default_factory=list)
|
||||
|
||||
def __post_init__(self):
|
||||
super().__post_init__()
|
||||
@@ -121,6 +118,9 @@ class NormalRegion(Region):
|
||||
for plant_id in self.plant_ids:
|
||||
if plant_id in plants_by_id:
|
||||
self.plants.append(plants_by_id[plant_id])
|
||||
for lode_id in self.lode_ids:
|
||||
if lode_id in lodes_by_id:
|
||||
self.lodes.append(lodes_by_id[lode_id])
|
||||
|
||||
def get_region_type(self) -> str:
|
||||
return "normal"
|
||||
@@ -131,26 +131,17 @@ class NormalRegion(Region):
|
||||
info_parts.extend([a.get_info() for a in self.animals])
|
||||
if self.plants:
|
||||
info_parts.extend([p.get_info() for p in self.plants])
|
||||
return "; ".join(info_parts) if info_parts else "无特色物种"
|
||||
if self.lodes:
|
||||
info_parts.extend([l.get_info() for l in self.lodes])
|
||||
return "; ".join(info_parts) if info_parts else "无特色资源"
|
||||
|
||||
def _get_desc(self) -> str:
|
||||
species_info = self.get_species_info()
|
||||
return f"(物种分布:{species_info})"
|
||||
return f"(资源分布:{species_info})"
|
||||
|
||||
def __str__(self) -> str:
|
||||
species_info = self.get_species_info()
|
||||
return f"普通区域:{self.name} - {self.desc} | 物种分布:{species_info}"
|
||||
|
||||
def get_hover_info(self) -> list[str]:
|
||||
lines = super().get_hover_info()
|
||||
species_info = self.get_species_info()
|
||||
if species_info and species_info != "暂无特色物种":
|
||||
lines.append("物种分布:")
|
||||
for species in species_info.split("; "):
|
||||
lines.append(f" {species}")
|
||||
else:
|
||||
lines.append("物种分布: 暂无特色物种")
|
||||
return lines
|
||||
return f"普通区域:{self.name} - {self.desc} | 资源分布:{species_info}"
|
||||
|
||||
@property
|
||||
def is_huntable(self) -> bool:
|
||||
@@ -160,6 +151,10 @@ class NormalRegion(Region):
|
||||
def is_harvestable(self) -> bool:
|
||||
return len(self.plants) > 0
|
||||
|
||||
@property
|
||||
def is_mineable(self) -> bool:
|
||||
return len(self.lodes) > 0
|
||||
|
||||
def get_structured_info(self) -> dict:
|
||||
info = super().get_structured_info()
|
||||
info["type_name"] = "普通区域"
|
||||
@@ -167,6 +162,7 @@ class NormalRegion(Region):
|
||||
# Assuming animals and plants are populated in __post_init__
|
||||
info["animals"] = [a.get_structured_info() for a in self.animals] if self.animals else []
|
||||
info["plants"] = [p.get_structured_info() for p in self.plants] if self.plants else []
|
||||
info["lodes"] = [l.get_structured_info() for l in self.lodes] if self.lodes else []
|
||||
|
||||
return info
|
||||
|
||||
@@ -196,16 +192,6 @@ class CultivateRegion(Region):
|
||||
def __str__(self) -> str:
|
||||
return f"修炼区域:{self.name}({self.essence_type}行灵气:{self.essence_density})- {self.desc}"
|
||||
|
||||
def get_hover_info(self) -> list[str]:
|
||||
lines = super().get_hover_info()
|
||||
stars = "★" * self.essence_density + "☆" * (10 - self.essence_density)
|
||||
lines.append(f"主要灵气: {self.essence_type} {stars}")
|
||||
if self.host_avatar:
|
||||
lines.append(f"主人: {self.host_avatar.name}")
|
||||
else:
|
||||
lines.append("主人: 无(可占据)")
|
||||
return lines
|
||||
|
||||
def get_structured_info(self) -> dict:
|
||||
info = super().get_structured_info()
|
||||
info["type_name"] = "修炼区域"
|
||||
|
||||
@@ -28,7 +28,6 @@ from src.classes.appearance import get_appearance_by_level
|
||||
from src.classes.persona import personas_by_id
|
||||
from src.classes.cultivation import REALM_ORDER
|
||||
from src.classes.alignment import Alignment
|
||||
from src.classes.color import serialize_hover_lines
|
||||
from src.classes.event import Event
|
||||
from src.classes.celestial_phenomenon import celestial_phenomena_by_id
|
||||
from src.classes.long_term_objective import set_user_long_term_objective, clear_user_long_term_objective
|
||||
@@ -707,43 +706,6 @@ def resume_game():
|
||||
game_instance["is_paused"] = False
|
||||
return {"status": "ok", "message": "Game resumed"}
|
||||
|
||||
@app.get("/api/hover")
|
||||
def get_hover_info(
|
||||
target_type: str = Query(alias="type"),
|
||||
target_id: str = Query(alias="id")
|
||||
):
|
||||
world = game_instance.get("world")
|
||||
if world is None:
|
||||
raise HTTPException(status_code=503, detail="World not initialized")
|
||||
|
||||
target = None
|
||||
if target_type == "avatar":
|
||||
target = world.avatar_manager.get_avatar(target_id)
|
||||
elif target_type == "region":
|
||||
if world.map and hasattr(world.map, "regions"):
|
||||
regions = world.map.regions
|
||||
target = regions.get(target_id)
|
||||
if target is None:
|
||||
try:
|
||||
target = regions.get(int(target_id))
|
||||
except (ValueError, TypeError):
|
||||
target = None
|
||||
else:
|
||||
raise HTTPException(status_code=400, detail="Unsupported target type")
|
||||
|
||||
if target is None:
|
||||
raise HTTPException(status_code=404, detail="Target not found")
|
||||
if not hasattr(target, "get_hover_info"):
|
||||
raise HTTPException(status_code=422, detail="Target has no hover info")
|
||||
|
||||
lines = target.get_hover_info() or []
|
||||
return {
|
||||
"id": target_id,
|
||||
"type": target_type,
|
||||
"name": getattr(target, "name", target_id),
|
||||
"lines": serialize_hover_lines([str(line) for line in lines]),
|
||||
}
|
||||
|
||||
@app.get("/api/detail")
|
||||
def get_detail_info(
|
||||
target_type: str = Query(alias="type"),
|
||||
@@ -777,19 +739,8 @@ def get_detail_info(
|
||||
if target is None:
|
||||
raise HTTPException(status_code=404, detail="Target not found")
|
||||
|
||||
if hasattr(target, "get_structured_info"):
|
||||
info = target.get_structured_info()
|
||||
return info
|
||||
else:
|
||||
# 回退到 hover info 如果没有结构化信息
|
||||
if hasattr(target, "get_hover_info"):
|
||||
lines = target.get_hover_info() or []
|
||||
return {
|
||||
"fallback": True,
|
||||
"name": getattr(target, "name", target_id),
|
||||
"lines": serialize_hover_lines([str(line) for line in lines])
|
||||
}
|
||||
return {"error": "No info available"}
|
||||
info = target.get_structured_info()
|
||||
return info
|
||||
|
||||
class SetObjectiveRequest(BaseModel):
|
||||
avatar_id: str
|
||||
|
||||
@@ -21,4 +21,8 @@ id,name,desc,stage_id
|
||||
19,清音竹节,九曲清音竹中最精华的一节,敲击时能发出安神定志的清音,是制作音波类法宝的绝佳材料,2
|
||||
20,冰魄蛇胆,冰魄骨蛇的内丹所在,虽为极寒之物,但吞服后能大幅增强修士对寒气的亲和力,是修炼寒系功法的至宝,2
|
||||
21,星纹叶,叶面上天生带有星辰纹路的草叶,蕴含纯净的星辰之力,是炼制星辰属性丹药的主材,2
|
||||
22,赤果核,涅盘赤果的果核,即便果肉枯萎,核心仍锁有一丝不灭生机,是炼制延寿丹和疗伤圣药的关键,3
|
||||
22,赤果核,涅盘赤果的果核,即便果肉枯萎,核心仍锁有一丝不灭生机,是炼制延寿丹和疗伤圣药的关键,3
|
||||
101,玄铁,普通的黑色铁矿,质地坚硬,适合打造练气期法器,1
|
||||
102,赤精铜,通体赤红的铜矿,蕴含火灵之力,是筑基期炼器的常用材料,2
|
||||
103,秘银,银白色的稀有金属,延展性极佳且能传导灵力,适用于金丹期法宝,3
|
||||
104,庚金,锐气逼人的金色矿石,坚不可摧,乃是元婴期炼制本命法宝的顶级材料,4
|
||||
|
||||
|
7
static/game_configs/lode.csv
Normal file
7
static/game_configs/lode.csv
Normal file
@@ -0,0 +1,7 @@
|
||||
id,name,desc,stage_id,item_ids
|
||||
,,,"该矿脉对应的物品ID"
|
||||
1,玄铁矿脉,蕴含玄铁的普通矿脉,常见于浅层地表,1,101
|
||||
2,赤铜矿脉,深埋地下的赤铜矿脉,周围往往伴生火热之气,2,102
|
||||
3,秘银矿床,极其罕见的秘银富集地,银光闪烁,灵气逼人,3,103
|
||||
4,庚金矿洞,位于绝壁深处的庚金矿脉,开采极为困难,但价值连城,4,104
|
||||
|
||||
|
@@ -1,20 +1,20 @@
|
||||
id,name,desc,animal_ids,plant_ids
|
||||
ID必须以1开头,,,该区域分布的动物物种IDs,该区域分布的植物物种IDs
|
||||
101,平原地带,位于大陆中部,地势平坦,灵气平和。是初学修炼者打基础和建立宗门的理想之地。,3.0,
|
||||
102,西域流沙,位于大陆极西,茫茫大漠,黄沙漫天。此地气候干燥,日夜温差极大,是沙漠商队的必经之路。,,3.0
|
||||
103,南疆蛮荒,位于大陆西南,古木参天,藤蔓缠绕。此地森林茂密,野兽众多,是采集药材和狩猎的危险之地。,4.0,
|
||||
104,极北冰原,位于大陆极北,千里冰封,万年不化。此地严寒刺骨,风雪交加,只有最坚韧的冒险者才能在此生存。,,4.0
|
||||
105,无边碧海,位于大陆极东,浩瀚无垠,波涛汹涌。此地风浪险恶,暗礁密布,是海商和渔民的挑战之海。,5.0,
|
||||
106,天河奔流,横贯大陆东西,一江春水向东流,奔腾不息入东海。此河贯穿东西,水流湍急,是重要的交通要道。,,5.0
|
||||
107,青峰山脉,位于大陆东部,连绵起伏,直插云霄。此地山势险峻,多有奇石异洞,是探险者寻宝的热门之地。,6.0,
|
||||
108,万丈雪峰,位于大陆西北,雪峰皑皑,寒风刺骨。此地终年积雪,山路崎岖,是登山者的终极挑战。,,6.0
|
||||
109,碧野千里,位于大陆西部,芳草萋萋,一望无际。此地水草丰美,牛羊成群,是游牧民族的天然牧场。,7.0,
|
||||
110,青云林海,位于大陆中部,古树参天,绿意盎然。此地森林广袤,物产丰富,是伐木工和猎人的主要活动区域。,,7.0
|
||||
111,炎狱火山,位于大陆东北,烈焰冲天,岩浆奔流。此地火山活跃,地热丰富,是铁匠锻造的理想之地,但也极其危险。,8.0,
|
||||
112,沃土良田,位于大陆东部,土地肥沃,五谷丰登。此地土壤深厚,雨水充沛,是农民耕种的黄金宝地。,,8.0
|
||||
113,幽冥毒泽,位于大陆正南,终年被五色瘴气笼罩,毒虫遍地。凡人入之即化为白骨,唯有修习毒功者视此处为无上洞天。,9.0,
|
||||
114,十万大山,位于大陆南部,苍茫群山连绵不绝,乃是妖族祖地。山势险峻,道路难行。,10.0,
|
||||
115,紫竹幽境,位于大陆中北,紫竹成林,灵气清冽。风过林间若奏仙乐,在此静修可涤荡心魔,感悟天地自然之道。,,9.0
|
||||
116,凛霜荒原,位于大陆东北,寸草不生,冻土千尺。此地生机绝灭,却蕴含着极致的阴寒灵气,偶有万年玄冰出世。,11.0,
|
||||
117,碎星戈壁,位于大陆西南,飞沙走石,狂风如刀。传说曾有星辰陨落于此,至今仍残存着狂暴的星辰之力与天外陨铁。,,10.0
|
||||
118,蓬莱遗岛,位于极东海外,孤悬海外,云雾缭绕。岛上灵泉喷涌,奇花异草遍布,灵气浓郁远超内陆,是海外散修向往之地。,,11.0
|
||||
id,name,desc,animal_ids,plant_ids,lode_ids
|
||||
ID必须以1开头,,,该区域分布的动物物种IDs,该区域分布的植物物种IDs,该区域分布的矿脉IDs
|
||||
101,平原地带,位于大陆中部,地势平坦,灵气平和。是初学修炼者打基础和建立宗门的理想之地。,3.0,,
|
||||
102,西域流沙,位于大陆极西,茫茫大漠,黄沙漫天。此地气候干燥,日夜温差极大,是沙漠商队的必经之路。,,3.0,
|
||||
103,南疆蛮荒,位于大陆西南,古木参天,藤蔓缠绕。此地森林茂密,野兽众多,是采集药材和狩猎的危险之地。,4.0,,
|
||||
104,极北冰原,位于大陆极北,千里冰封,万年不化。此地严寒刺骨,风雪交加,只有最坚韧的冒险者才能在此生存。,,4.0,
|
||||
105,无边碧海,位于大陆极东,浩瀚无垠,波涛汹涌。此地风浪险恶,暗礁密布,是海商和渔民的挑战之海。,5.0,,
|
||||
106,天河奔流,横贯大陆东西,一江春水向东流,奔腾不息入东海。此河贯穿东西,水流湍急,是重要的交通要道。,,5.0,
|
||||
107,青峰山脉,位于大陆东部,连绵起伏,直插云霄。此地山势险峻,多有奇石异洞,是探险者寻宝的热门之地。,6.0,,1.0
|
||||
108,万丈雪峰,位于大陆西北,雪峰皑皑,寒风刺骨。此地终年积雪,山路崎岖,是登山者的终极挑战。,,6.0,
|
||||
109,碧野千里,位于大陆西部,芳草萋萋,一望无际。此地水草丰美,牛羊成群,是游牧民族的天然牧场。,7.0,,
|
||||
110,青云林海,位于大陆中部,古树参天,绿意盎然。此地森林广袤,物产丰富,是伐木工和猎人的主要活动区域。,,7.0,
|
||||
111,炎狱火山,位于大陆东北,烈焰冲天,岩浆奔流。此地火山活跃,地热丰富,是铁匠锻造的理想之地,但也极其危险。,8.0,,2.0
|
||||
112,沃土良田,位于大陆东部,土地肥沃,五谷丰登。此地土壤深厚,雨水充沛,是农民耕种的黄金宝地。,,8.0,
|
||||
113,幽冥毒泽,位于大陆正南,终年被五色瘴气笼罩,毒虫遍地。凡人入之即化为白骨,唯有修习毒功者视此处为无上洞天。,9.0,,
|
||||
114,十万大山,位于大陆南部,苍茫群山连绵不绝,乃是妖族祖地。山势险峻,道路难行。,10.0,,4.0
|
||||
115,紫竹幽境,位于大陆中北,紫竹成林,灵气清冽。风过林间若奏仙乐,在此静修可涤荡心魔,感悟天地自然之道。,,9.0,
|
||||
116,凛霜荒原,位于大陆东北,寸草不生,冻土千尺。此地生机绝灭,却蕴含着极致的阴寒灵气,偶有万年玄冰出世。,11.0,,
|
||||
117,碎星戈壁,位于大陆西南,飞沙走石,狂风如刀。传说曾有星辰陨落于此,至今仍残存着狂暴的星辰之力与天外陨铁。,,10.0,3.0
|
||||
118,蓬莱遗岛,位于极东海外,孤悬海外,云雾缭绕。岛上灵泉喷涌,奇花异草遍布,灵气浓郁远超内陆,是海外散修向往之地。,,11.0,
|
||||
|
||||
|
@@ -62,3 +62,4 @@ id,name,exclusion_names,desc,rarity,condition,effects
|
||||
60,大器晚成,,早年修行多舛,霉运连连;但若能坚持至金丹元婴,便可否极泰来,气运亨通。,SR,,"[{when: 'avatar.cultivation_progress.realm.value in [""练气"", ""筑基""]', extra_misfortune_probability: 0.005}, {when: 'avatar.cultivation_progress.realm.value in [""金丹"", ""元婴""]', extra_fortune_probability: 0.01}]"
|
||||
61,炼器师,好斗,精通炼器之道,对材料敏锐,擅长铸造法宝。你认为法宝是修行的关键,战斗并非你的专长。,R,,{extra_cast_success_rate: 0.15}
|
||||
62,情绪化,理性;淡漠,你的情绪波动很大,极易受外界事件影响而改变心情,做事也更随心所欲。,N,,
|
||||
63,矿工,怠惰,擅长勘探挖掘,对矿脉有着独特的直觉。你认为地下的宝藏才是最实在的财富。,R,,{extra_mine_items: 1}
|
||||
|
||||
|
@@ -2,7 +2,6 @@ import { httpClient } from './http';
|
||||
import type {
|
||||
InitialStateDTO,
|
||||
MapResponseDTO,
|
||||
HoverResponseDTO,
|
||||
DetailResponseDTO,
|
||||
SaveFileDTO
|
||||
} from '../types/api';
|
||||
@@ -92,11 +91,6 @@ export const gameApi = {
|
||||
|
||||
// --- Information ---
|
||||
|
||||
fetchHoverInfo(params: HoverParams) {
|
||||
const query = new URLSearchParams(Object.entries(params));
|
||||
return httpClient.get<HoverResponseDTO>(`/api/hover?${query}`);
|
||||
},
|
||||
|
||||
fetchDetailInfo(params: HoverParams) {
|
||||
const query = new URLSearchParams(Object.entries(params));
|
||||
return httpClient.get<DetailResponseDTO>(`/api/detail?${query}`);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { ref } from 'vue';
|
||||
import { gameApi } from '../api/game';
|
||||
import type { AvatarDetail, RegionDetail, SectDetail, HoverLine } from '../types/core';
|
||||
import type { AvatarDetail, RegionDetail, SectDetail } from '../types/core';
|
||||
|
||||
export type SelectionType = 'avatar' | 'region' | 'sect';
|
||||
|
||||
@@ -20,11 +20,6 @@ export const useUiStore = defineStore('ui', () => {
|
||||
const isLoadingDetail = ref(false);
|
||||
const detailError = ref<string | null>(null);
|
||||
|
||||
// --- Hover ---
|
||||
|
||||
const hoveringTarget = ref<Selection | null>(null);
|
||||
const hoverInfo = ref<HoverLine[]>([]);
|
||||
|
||||
// --- Actions ---
|
||||
|
||||
async function select(type: SelectionType, id: string) {
|
||||
@@ -78,58 +73,15 @@ export const useUiStore = defineStore('ui', () => {
|
||||
}
|
||||
}
|
||||
|
||||
// --- Hover Actions ---
|
||||
|
||||
// Simple cache for hover
|
||||
const hoverCache = new Map<string, HoverLine[]>();
|
||||
|
||||
async function setHover(type: SelectionType | null, id?: string) {
|
||||
if (!type || !id) {
|
||||
hoveringTarget.value = null;
|
||||
return;
|
||||
}
|
||||
|
||||
const key = `${type}:${id}`;
|
||||
hoveringTarget.value = { type, id };
|
||||
|
||||
// Check cache
|
||||
if (hoverCache.has(key)) {
|
||||
hoverInfo.value = hoverCache.get(key)!;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await gameApi.fetchHoverInfo({ type, id });
|
||||
// Normalize lines... (Assuming backend returns lines compatible with HoverLine)
|
||||
const lines = res.lines as HoverLine[];
|
||||
hoverCache.set(key, lines);
|
||||
|
||||
if (hoveringTarget.value?.type === type && hoveringTarget.value?.id === id) {
|
||||
hoverInfo.value = lines;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Hover fetch failed', e);
|
||||
}
|
||||
}
|
||||
|
||||
function clearHoverCache() {
|
||||
hoverCache.clear();
|
||||
}
|
||||
|
||||
return {
|
||||
selectedTarget,
|
||||
detailData,
|
||||
isLoadingDetail,
|
||||
detailError,
|
||||
|
||||
hoveringTarget,
|
||||
hoverInfo,
|
||||
|
||||
select,
|
||||
clearSelection,
|
||||
refreshDetail,
|
||||
setHover,
|
||||
clearHoverCache
|
||||
refreshDetail
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -57,10 +57,6 @@ export interface MapResponseDTO {
|
||||
};
|
||||
}
|
||||
|
||||
export interface HoverResponseDTO {
|
||||
lines: unknown; // 后端返回的可能是复杂的嵌套数组
|
||||
}
|
||||
|
||||
// 详情接口返回的结构比较动态,通常包含 entity 的所有字段
|
||||
export type DetailResponseDTO = Record<string, any>;
|
||||
|
||||
|
||||
@@ -194,12 +194,3 @@ export interface GameEvent {
|
||||
// 运行时辅助字段
|
||||
_seq?: number;
|
||||
}
|
||||
|
||||
// --- 悬浮提示 (Hover) ---
|
||||
|
||||
export type HoverSegment = {
|
||||
text: string;
|
||||
color?: string;
|
||||
};
|
||||
|
||||
export type HoverLine = HoverSegment[];
|
||||
|
||||
Reference in New Issue
Block a user