dreamland / ui /explorer_tab.py
panda-lsy
refactor: pure text UI, fix slot switching, remove all SVG icons
4dad7ad
Raw
History Blame Contribute Delete
6.74 kB
"""Explorer Tab — location exploration, NPC encounters, quest tracking, actions."""
import random
import gradio as gr
from core.world import WorldGraph
from core.player import load_player, save_player
from core.model_manager import ModelManager
from ui.i18n import t
def _get_location_choices() -> list:
"""Get list of location names for the dropdown."""
world = WorldGraph()
return [loc.name for loc in world.get_entities_by_type("location")]
def _render_location(location_name: str) -> str:
if not location_name:
return f"""
<div style="text-align:center;padding:60px 20px;color:#555;">
<div style="margin-bottom:16px;"></div>
<p>{t('exp_empty')}</p>
</div>
"""
world = WorldGraph()
player = load_player()
locations = world.get_entities_by_type("location")
location = next((l for l in locations if l.name == location_name), None)
if not location:
return f"<p>{t('exp_not_found')}</p>"
player.current_location_id = location.id
save_player(player)
all_npcs = world.get_entities_by_type("npc")
npcs_here = [n for n in all_npcs if n.location_id == location.id]
all_quests = world.get_entities_by_type("quest")
quests_here = [q for q in all_quests if q.location_id == location.id and not player.is_quest_completed(q.id)]
neighbors = world.get_neighbors(location.id, "connects_to")
connected = [n.name for n in neighbors if n.name != location_name]
html = f"""
<div style="max-width:650px;margin:0 auto;">
<div style="text-align:center;margin-bottom:20px;padding:20px;background:linear-gradient(180deg,rgba(74,158,255,0.08),transparent);border-radius:12px;">
<div style="font-family:Georgia,serif;font-size:1.6rem;color:#4a9eff;margin-bottom:6px;">
{location.name}
</div>
<div style="color:#aaa;font-size:0.9rem;line-height:1.7;max-width:500px;margin:0 auto;">
{location.description}
</div>
</div>
"""
if npcs_here:
html += f'<div style="font-family:Georgia,serif;color:#50c878;font-size:1rem;margin:16px 0 10px;">{t("exp_encounter")}</div>'
for npc in npcs_here:
dialog = npc.dialog or "..."
html += f"""
<div style="background:#16162a;border:1px solid #2a2a4a;border-radius:10px;padding:14px 18px;margin-bottom:10px;border-left:3px solid #50c878;">
<div style="color:#50c878;font-weight:600;margin-bottom:4px;">{npc.name}</div>
<div style="color:#a0d0a0;font-style:italic;font-size:0.9rem;">"{dialog}"</div>
</div>
"""
if quests_here:
html += f'<div style="font-family:Georgia,serif;color:#ffd700;font-size:1rem;margin:16px 0 10px;">{t("exp_quests")}</div>'
for q in quests_here:
html += f"""
<div style="background:#1a1a10;border:1px solid #3a3a2a;border-radius:10px;padding:14px 18px;margin-bottom:10px;border-left:3px solid #ffd700;">
<div style="color:#ffd700;font-weight:600;margin-bottom:4px;">{q.name}</div>
<div style="color:#aaa;font-size:0.85rem;line-height:1.5;">{q.description[:100]}...</div>
</div>
"""
if connected:
html += f'<div style="font-family:Georgia,serif;color:#b8a9ff;font-size:1rem;margin:16px 0 10px;">{t("exp_travel")}</div>'
html += '<div style="display:flex;flex-wrap:wrap;gap:8px;">'
for loc_name in connected:
html += f"""
<div style="padding:6px 14px;background:#16162a;border:1px solid #2a2a4a;border-radius:8px;color:#4a9eff;font-size:0.85rem;">
{loc_name}
</div>
"""
html += "</div>"
html += "</div>"
return html
def explore_location(location_name: str) -> tuple:
if not location_name:
return t("exp_select_first"), ""
world = WorldGraph()
player = load_player()
manager = ModelManager()
locations = world.get_entities_by_type("location")
location = next((l for l in locations if l.name == location_name), None)
if not location:
return t("exp_not_exist"), ""
outcomes = [t("exp_outcome_1"), t("exp_outcome_2"), t("exp_outcome_3"), t("exp_outcome_4"), t("exp_outcome_5")]
outcome = outcomes[0] # Default, LLM overrides
prompt = f"你在{location.name}探索。{location.description[:100]}\n\n请描述一个有趣的发现(物品或线索),50字以内。用中文回答。"
gen = manager.generate_fn
discovery = gen(prompt) or outcome
level_msg = player.add_xp(15)
item_found = None
if random.random() > 0.5:
item_names = [t("item_key"), t("item_crystal"), t("item_scroll"), t("item_potion"), t("item_dust"), t("item_shard")]
item_name = random.choice(item_names)
player.add_item(item_name, discovery[:30], "misc")
item_found = item_name
save_player(player)
result = f"{t('exp_discovery')}: {discovery}"
if item_found:
result += f"\n{t('exp_item_found')}: {item_found} (+15 XP)"
if level_msg:
result += f"\n{level_msg}"
return result, _render_location(location_name)
def rest_action(location_name: str) -> tuple:
player = load_player()
player.heal(hp=30, mp=20)
save_player(player)
return f"{t('exp_rest_result')} (HP:{player.hp}/{player.max_hp}, MP:{player.mp}/{player.max_mp})", _render_location(location_name)
def create_explorer_tab() -> tuple:
"""Create the Explorer tab."""
with gr.Row():
location_dropdown = gr.Dropdown(
label=t("exp_select_label"),
choices=_get_location_choices(),
interactive=True,
)
location_display = gr.HTML(
value=f"""
<div style="text-align:center;padding:60px 20px;color:#555;">
<div style="margin-bottom:16px;"></div>
<p>{t('exp_empty')}</p>
</div>
"""
)
with gr.Row():
explore_btn = gr.Button(t("exp_explore"), variant="primary")
rest_btn = gr.Button(t("exp_rest"), variant="secondary")
action_result = gr.HTML("")
# Location change -> render
location_dropdown.change(
fn=_render_location,
inputs=[location_dropdown],
outputs=[location_display],
)
# Explore
explore_btn.click(
fn=explore_location,
inputs=[location_dropdown],
outputs=[action_result, location_display],
)
# Rest
rest_btn.click(
fn=rest_action,
inputs=[location_dropdown],
outputs=[action_result, location_display],
)
return location_dropdown, location_display, action_result