新增插件指引网页
This commit is contained in:
@@ -5,6 +5,9 @@ from PWF.Convention.Runtime.Architecture import Architecture
|
||||
from PWF.Convention.Runtime.GlobalConfig import ProjectConfig
|
||||
from PWF.Convention.Runtime.Web import ToolURL
|
||||
from PWF.Convention.Runtime.String import LimitStringLength
|
||||
from fastapi.responses import HTMLResponse
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Dict, Iterable, List, Optional, Sequence, TypedDict, override, Union
|
||||
import httpx
|
||||
import re
|
||||
|
||||
@@ -13,6 +16,613 @@ MAIN_WEBHOOK_URL = logger.FindItem("main_webhook_url", "")
|
||||
logger.SaveProperties()
|
||||
|
||||
|
||||
class GuideEntry(TypedDict, total=False):
|
||||
"""单条图鉴信息。"""
|
||||
|
||||
title: str
|
||||
identifier: str
|
||||
description: str
|
||||
category: str
|
||||
metadata: Dict[str, str]
|
||||
icon: str
|
||||
badge: str
|
||||
links: Sequence[Dict[str, str]]
|
||||
tags: Sequence[str]
|
||||
details: Sequence[Union[str, Dict[str, Any]]]
|
||||
group: str
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class GuideSection:
|
||||
"""图鉴章节。"""
|
||||
|
||||
title: str
|
||||
entries: Sequence[GuideEntry] = field(default_factory=tuple)
|
||||
description: str = ""
|
||||
layout: str = "grid"
|
||||
section_id: str | None = None
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class GuidePage:
|
||||
"""完整图鉴页面。"""
|
||||
|
||||
title: str
|
||||
sections: Sequence[GuideSection] = field(default_factory=tuple)
|
||||
subtitle: str = ""
|
||||
metadata: Dict[str, str] = field(default_factory=dict)
|
||||
related_links: Dict[str, Sequence[Dict[str, str]]] = field(default_factory=dict)
|
||||
|
||||
|
||||
def render_markdown_page(page: GuidePage) -> str:
|
||||
"""保留 Markdown 渲染(备用)。"""
|
||||
|
||||
def _render_section(section: GuideSection) -> str:
|
||||
lines: List[str] = [f"## {section.title}"]
|
||||
if section.description:
|
||||
lines.append(section.description)
|
||||
if not section.entries:
|
||||
lines.append("> 暂无内容。")
|
||||
return "\n".join(lines)
|
||||
for entry in section.entries:
|
||||
title = entry.get("title", "未命名")
|
||||
identifier = entry.get("identifier")
|
||||
desc = entry.get("description", "")
|
||||
category = entry.get("category")
|
||||
metadata = entry.get("metadata", {})
|
||||
bullet = f"- **{title}**"
|
||||
if identifier:
|
||||
bullet += f"|`{identifier}`"
|
||||
if category:
|
||||
bullet += f"|{category}"
|
||||
lines.append(bullet)
|
||||
if desc:
|
||||
lines.append(f" - {desc}")
|
||||
for meta_key, meta_value in metadata.items():
|
||||
lines.append(f" - {meta_key}:{meta_value}")
|
||||
return "\n".join(lines)
|
||||
|
||||
lines: List[str] = [f"# {page.title}"]
|
||||
if page.subtitle:
|
||||
lines.append(page.subtitle)
|
||||
if page.metadata:
|
||||
lines.append("")
|
||||
for key, value in page.metadata.items():
|
||||
lines.append(f"- {key}:{value}")
|
||||
for section in page.sections:
|
||||
lines.append("")
|
||||
lines.append(_render_section(section))
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def render_html_page(page: GuidePage) -> str:
|
||||
"""渲染 Apple Store 风格的 HTML 页面。"""
|
||||
|
||||
def escape(text: Optional[str]) -> str:
|
||||
if not text:
|
||||
return ""
|
||||
return (
|
||||
text.replace("&", "&")
|
||||
.replace("<", "<")
|
||||
.replace(">", ">")
|
||||
.replace('"', """)
|
||||
)
|
||||
|
||||
def render_metadata(metadata: Dict[str, str]) -> str:
|
||||
if not metadata:
|
||||
return ""
|
||||
cards = []
|
||||
for key, value in metadata.items():
|
||||
cards.append(
|
||||
f"""
|
||||
<div class="meta-card">
|
||||
<div class="meta-key">{escape(key)}</div>
|
||||
<div class="meta-value">{escape(value)}</div>
|
||||
</div>
|
||||
"""
|
||||
)
|
||||
return f'<section class="meta-grid">{"".join(cards)}</section>'
|
||||
|
||||
def render_links(links: Optional[Sequence[Dict[str, str]]]) -> str:
|
||||
if not links:
|
||||
return ""
|
||||
items = []
|
||||
for link in links:
|
||||
href = escape(link.get("href", "#"))
|
||||
label = escape(link.get("label", "前往"))
|
||||
items.append(f'<a class="entry-link" href="{href}" target="_blank">{label}</a>')
|
||||
return "".join(items)
|
||||
|
||||
def render_tags(tags: Optional[Sequence[str]]) -> str:
|
||||
if not tags:
|
||||
return ""
|
||||
chips = "".join(f'<span class="entry-tag">{escape(tag)}</span>' for tag in tags)
|
||||
return f'<div class="entry-tags-extra">{chips}</div>'
|
||||
|
||||
def render_details(details: Optional[Sequence[Union[str, Dict[str, Any]]]]) -> str:
|
||||
if not details:
|
||||
return ""
|
||||
blocks: List[str] = []
|
||||
for detail in details:
|
||||
if isinstance(detail, str):
|
||||
blocks.append(f'<p class="entry-detail-paragraph">{escape(detail)}</p>')
|
||||
elif isinstance(detail, dict):
|
||||
kind = detail.get("type")
|
||||
if kind == "list":
|
||||
items = "".join(
|
||||
f'<li>{escape(str(item))}</li>' for item in detail.get("items", [])
|
||||
)
|
||||
blocks.append(f'<ul class="entry-detail-list">{items}</ul>')
|
||||
elif kind == "steps":
|
||||
items = "".join(
|
||||
f'<li><span class="step-index">{idx+1}</span><span>{escape(str(item))}</span></li>'
|
||||
for idx, item in enumerate(detail.get("items", []))
|
||||
)
|
||||
blocks.append(f'<ol class="entry-detail-steps">{items}</ol>')
|
||||
elif kind == "table":
|
||||
rows = []
|
||||
for row in detail.get("rows", []):
|
||||
cols = "".join(f"<td>{escape(str(col))}</td>" for col in row)
|
||||
rows.append(f"<tr>{cols}</tr>")
|
||||
head = ""
|
||||
headers = detail.get("headers")
|
||||
if headers:
|
||||
head = "".join(f"<th>{escape(str(col))}</th>" for col in headers)
|
||||
head = f"<thead><tr>{head}</tr></thead>"
|
||||
blocks.append(
|
||||
f'<table class="entry-detail-table">{head}<tbody>{"".join(rows)}</tbody></table>'
|
||||
)
|
||||
if not blocks:
|
||||
return ""
|
||||
return f'<div class="entry-details">{"".join(blocks)}</div>'
|
||||
|
||||
def render_entry(entry: GuideEntry) -> str:
|
||||
icon = escape(entry.get("icon"))
|
||||
badge = escape(entry.get("badge"))
|
||||
title = escape(entry.get("title"))
|
||||
identifier = escape(entry.get("identifier"))
|
||||
description = escape(entry.get("description"))
|
||||
category = escape(entry.get("category"))
|
||||
metadata_items = []
|
||||
for meta_key, meta_value in entry.get("metadata", {}).items():
|
||||
metadata_items.append(
|
||||
f'<li><span>{escape(meta_key)}</span><span>{escape(str(meta_value))}</span></li>'
|
||||
)
|
||||
metadata_html = ""
|
||||
if metadata_items:
|
||||
metadata_html = f'<ul class="entry-meta">{"".join(metadata_items)}</ul>'
|
||||
identifier_html = f'<code class="entry-id">{identifier}</code>' if identifier else ""
|
||||
category_html = f'<span class="entry-category">{category}</span>' if category else ""
|
||||
badge_html = f'<span class="entry-badge">{badge}</span>' if badge else ""
|
||||
icon_html = f'<div class="entry-icon">{icon}</div>' if icon else ""
|
||||
links_html = render_links(entry.get("links"))
|
||||
tags_html = render_tags(entry.get("tags"))
|
||||
details_html = render_details(entry.get("details"))
|
||||
group = escape(entry.get("group"))
|
||||
group_attr = f' data-group="{group}"' if group else ""
|
||||
return f"""
|
||||
<article class="entry-card"{group_attr}>
|
||||
{icon_html}
|
||||
<div class="entry-content">
|
||||
<header>
|
||||
<h3>{title}{badge_html}</h3>
|
||||
<div class="entry-tags">{identifier_html}{category_html}</div>
|
||||
</header>
|
||||
<p class="entry-desc">{description}</p>
|
||||
{metadata_html}
|
||||
{tags_html}
|
||||
{details_html}
|
||||
{links_html}
|
||||
</div>
|
||||
</article>
|
||||
"""
|
||||
|
||||
def render_section(section: GuideSection) -> str:
|
||||
layout_class = "entries-grid" if section.layout == "grid" else "entries-list"
|
||||
section_attr = f' id="{escape(section.section_id)}"' if section.section_id else ""
|
||||
cards = "".join(render_entry(entry) for entry in section.entries)
|
||||
description_html = (
|
||||
f'<p class="section-desc">{escape(section.description)}</p>'
|
||||
if section.description
|
||||
else ""
|
||||
)
|
||||
if not cards:
|
||||
cards = '<div class="empty-placeholder">暂无内容</div>'
|
||||
return f"""
|
||||
<section class="guide-section"{section_attr}>
|
||||
<div class="section-header">
|
||||
<h2>{escape(section.title)}</h2>
|
||||
{description_html}
|
||||
</div>
|
||||
<div class="{layout_class}">
|
||||
{cards}
|
||||
</div>
|
||||
</section>
|
||||
"""
|
||||
|
||||
def render_related(related: Dict[str, Sequence[Dict[str, str]]]) -> str:
|
||||
if not related:
|
||||
return ""
|
||||
blocks: List[str] = []
|
||||
for label, links in related.items():
|
||||
if not links:
|
||||
continue
|
||||
items = "".join(
|
||||
f'<a class="related-link" href="{escape(link.get("href", "#"))}">{escape(link.get("label", ""))}</a>'
|
||||
for link in links
|
||||
)
|
||||
blocks.append(
|
||||
f"""
|
||||
<div class="related-block">
|
||||
<div class="related-label">{escape(label)}</div>
|
||||
<div class="related-items">{items}</div>
|
||||
</div>
|
||||
"""
|
||||
)
|
||||
if not blocks:
|
||||
return ""
|
||||
return f'<section class="related-section">{"".join(blocks)}</section>'
|
||||
|
||||
sections_html = "".join(render_section(section) for section in page.sections)
|
||||
metadata_html = render_metadata(page.metadata)
|
||||
related_html = render_related(page.related_links)
|
||||
subtitle_html = f'<p class="hero-subtitle">{escape(page.subtitle)}</p>' if page.subtitle else ""
|
||||
|
||||
return f"""
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>{escape(page.title)}</title>
|
||||
<style>
|
||||
:root {{
|
||||
color-scheme: dark;
|
||||
--bg-primary: radial-gradient(120% 120% at 10% 10%, #222 0%, #050505 100%);
|
||||
--bg-card: rgba(255, 255, 255, 0.06);
|
||||
--bg-card-hover: rgba(255, 255, 255, 0.12);
|
||||
--border-soft: rgba(255, 255, 255, 0.1);
|
||||
--text-main: #f5f5f7;
|
||||
--text-sub: rgba(245, 245, 247, 0.64);
|
||||
--accent: linear-gradient(135deg, #4b7bec, #34e7e4);
|
||||
--accent-strong: linear-gradient(135deg, #ff9f1a, #ff3f34);
|
||||
}}
|
||||
* {{
|
||||
box-sizing: border-box;
|
||||
}}
|
||||
body {{
|
||||
margin: 0;
|
||||
font-family: "SF Pro Display", "SF Pro SC", "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-main);
|
||||
min-height: 100vh;
|
||||
padding: 0 0 64px;
|
||||
}}
|
||||
a {{
|
||||
color: #9fc9ff;
|
||||
text-decoration: none;
|
||||
}}
|
||||
a:hover {{
|
||||
text-decoration: underline;
|
||||
}}
|
||||
header.hero {{
|
||||
padding: 80px 24px 40px;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
}}
|
||||
header.hero::after {{
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: radial-gradient(circle at 50% -20%, rgba(255, 255, 255, 0.18), transparent 55%);
|
||||
pointer-events: none;
|
||||
z-index: -1;
|
||||
}}
|
||||
.hero-title {{
|
||||
font-size: clamp(32px, 5vw, 48px);
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
background: var(--accent);
|
||||
-webkit-background-clip: text;
|
||||
color: transparent;
|
||||
letter-spacing: 0.8px;
|
||||
}}
|
||||
.hero-subtitle {{
|
||||
margin: 16px auto 0;
|
||||
max-width: 640px;
|
||||
font-size: 18px;
|
||||
color: var(--text-sub);
|
||||
line-height: 1.6;
|
||||
}}
|
||||
main {{
|
||||
width: min(1100px, 92vw);
|
||||
margin: 0 auto;
|
||||
}}
|
||||
.meta-grid {{
|
||||
display: grid;
|
||||
gap: 20px;
|
||||
margin: 0 auto 48px;
|
||||
padding: 0 8px;
|
||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||
}}
|
||||
.related-section {{
|
||||
margin: 0 auto 48px;
|
||||
padding: 0 8px;
|
||||
display: grid;
|
||||
gap: 18px;
|
||||
}}
|
||||
.related-block {{
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}}
|
||||
.related-label {{
|
||||
font-size: 13px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
color: var(--text-sub);
|
||||
}}
|
||||
.related-items {{
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}}
|
||||
.related-link {{
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 6px 14px;
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
font-size: 13px;
|
||||
letter-spacing: 0.4px;
|
||||
transition: background 0.2s ease;
|
||||
}}
|
||||
.related-link:hover {{
|
||||
background: rgba(255, 255, 255, 0.16);
|
||||
text-decoration: none;
|
||||
}}
|
||||
.meta-card {{
|
||||
border-radius: 18px;
|
||||
padding: 22px;
|
||||
border: 1px solid var(--border-soft);
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
backdrop-filter: blur(16px);
|
||||
box-shadow: 0 12px 30px rgba(0, 0, 0, 0.35);
|
||||
}}
|
||||
.meta-key {{
|
||||
font-size: 13px;
|
||||
letter-spacing: 1px;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-sub);
|
||||
}}
|
||||
.meta-value {{
|
||||
margin-top: 6px;
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
}}
|
||||
.guide-section {{
|
||||
margin: 0 auto 56px;
|
||||
padding: 0 8px;
|
||||
}}
|
||||
.section-header {{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
margin-bottom: 28px;
|
||||
}}
|
||||
.section-header h2 {{
|
||||
margin: 0;
|
||||
font-size: clamp(26px, 3vw, 32px);
|
||||
letter-spacing: 0.5px;
|
||||
}}
|
||||
.section-desc {{
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
color: var(--text-sub);
|
||||
max-width: 640px;
|
||||
}}
|
||||
.entries-grid {{
|
||||
display: grid;
|
||||
gap: 20px;
|
||||
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
||||
}}
|
||||
.entries-list {{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 18px;
|
||||
}}
|
||||
.entry-card {{
|
||||
display: flex;
|
||||
gap: 18px;
|
||||
border-radius: 20px;
|
||||
padding: 24px;
|
||||
border: 1px solid transparent;
|
||||
background: var(--bg-card);
|
||||
transition: border 0.2s ease, background 0.2s ease, transform 0.2s ease;
|
||||
}}
|
||||
.entry-card:hover {{
|
||||
background: var(--bg-card-hover);
|
||||
border-color: rgba(255, 255, 255, 0.18);
|
||||
transform: translateY(-4px);
|
||||
}}
|
||||
.entry-icon {{
|
||||
font-size: 36px;
|
||||
}}
|
||||
.entry-content {{
|
||||
flex: 1;
|
||||
}}
|
||||
.entry-content header {{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
margin-bottom: 10px;
|
||||
}}
|
||||
.entry-content h3 {{
|
||||
margin: 0;
|
||||
font-size: 20px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}}
|
||||
.entry-badge {{
|
||||
display: inline-flex;
|
||||
padding: 4px 10px;
|
||||
border-radius: 999px;
|
||||
font-size: 12px;
|
||||
letter-spacing: 0.5px;
|
||||
background: var(--accent-strong);
|
||||
}}
|
||||
.entry-tags {{
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}}
|
||||
.entry-tags-extra {{
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 10px;
|
||||
}}
|
||||
.entry-id {{
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 999px;
|
||||
padding: 4px 10px;
|
||||
font-size: 12px;
|
||||
letter-spacing: 0.5px;
|
||||
}}
|
||||
.entry-category {{
|
||||
font-size: 12px;
|
||||
color: var(--text-sub);
|
||||
}}
|
||||
.entry-desc {{
|
||||
margin: 0 0 12px;
|
||||
color: var(--text-sub);
|
||||
line-height: 1.6;
|
||||
}}
|
||||
.entry-meta {{
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
font-size: 13px;
|
||||
color: var(--text-sub);
|
||||
}}
|
||||
.entry-meta li {{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}}
|
||||
.entry-link {{
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
margin-top: 10px;
|
||||
font-size: 14px;
|
||||
}}
|
||||
.entry-tag {{
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 4px 12px;
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
font-size: 12px;
|
||||
letter-spacing: 0.4px;
|
||||
}}
|
||||
.entry-details {{
|
||||
margin-top: 14px;
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}}
|
||||
.entry-detail-paragraph {{
|
||||
margin: 0;
|
||||
color: var(--text-sub);
|
||||
line-height: 1.6;
|
||||
}}
|
||||
.entry-detail-list, .entry-detail-steps {{
|
||||
margin: 0;
|
||||
padding-left: 20px;
|
||||
color: var(--text-sub);
|
||||
}}
|
||||
.entry-detail-steps {{
|
||||
list-style: none;
|
||||
padding-left: 0;
|
||||
}}
|
||||
.entry-detail-steps li {{
|
||||
display: grid;
|
||||
grid-template-columns: 28px 1fr;
|
||||
align-items: start;
|
||||
gap: 10px;
|
||||
margin-bottom: 6px;
|
||||
}}
|
||||
.entry-detail-steps .step-index {{
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 50%;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
font-size: 12px;
|
||||
}}
|
||||
.entry-detail-table {{
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
border-radius: 14px;
|
||||
overflow: hidden;
|
||||
}}
|
||||
.entry-detail-table th,
|
||||
.entry-detail-table td {{
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
padding: 8px 12px;
|
||||
font-size: 13px;
|
||||
text-align: left;
|
||||
}}
|
||||
.entry-detail-table th {{
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
font-weight: 600;
|
||||
}}
|
||||
.entry-detail-table tr:nth-child(even) {{
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
}}
|
||||
.empty-placeholder {{
|
||||
padding: 40px;
|
||||
text-align: center;
|
||||
border-radius: 20px;
|
||||
border: 1px dashed rgba(255, 255, 255, 0.2);
|
||||
color: var(--text-sub);
|
||||
}}
|
||||
@media (max-width: 680px) {{
|
||||
.entry-card {{
|
||||
flex-direction: column;
|
||||
}}
|
||||
.entry-content h3 {{
|
||||
font-size: 18px;
|
||||
}}
|
||||
.meta-grid {{
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}}
|
||||
}}
|
||||
@media (max-width: 480px) {{
|
||||
.meta-grid {{
|
||||
grid-template-columns: 1fr;
|
||||
}}
|
||||
}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header class="hero">
|
||||
<h1 class="hero-title">{escape(page.title)}</h1>
|
||||
{subtitle_html}
|
||||
</header>
|
||||
<main>
|
||||
{metadata_html}
|
||||
{related_html}
|
||||
{sections_html}
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
|
||||
class MessageSender:
|
||||
"""消息发送器"""
|
||||
|
||||
@@ -190,6 +800,198 @@ class BasicWPSInterface(PluginInterface):
|
||||
|
||||
|
||||
class WPSAPI(BasicWPSInterface):
|
||||
"""核心 WPS 插件基类,提供图鉴模板设施。"""
|
||||
|
||||
guide_section_labels: Dict[str, str] = {
|
||||
"commands": "指令一览",
|
||||
"items": "物品与资源",
|
||||
"recipes": "配方与合成",
|
||||
"guides": "系统指引",
|
||||
}
|
||||
|
||||
def get_guide_title(self) -> str:
|
||||
return self.__class__.__name__
|
||||
|
||||
def get_guide_subtitle(self) -> str:
|
||||
return ""
|
||||
|
||||
def get_guide_metadata(self) -> Dict[str, str]:
|
||||
return {}
|
||||
|
||||
def collect_command_entries(self) -> Sequence[GuideEntry]:
|
||||
return ()
|
||||
|
||||
def collect_item_entries(self) -> Sequence[GuideEntry]:
|
||||
return ()
|
||||
|
||||
def collect_recipe_entries(self) -> Sequence[GuideEntry]:
|
||||
return ()
|
||||
|
||||
def collect_guide_entries(self) -> Sequence[GuideEntry]:
|
||||
return ()
|
||||
|
||||
def collect_additional_sections(self) -> Sequence[GuideSection]:
|
||||
return ()
|
||||
|
||||
def collect_guide_sections(self) -> Sequence[GuideSection]:
|
||||
sections: List[GuideSection] = []
|
||||
|
||||
command_entries = tuple(self.collect_command_entries())
|
||||
if command_entries:
|
||||
sections.append(
|
||||
GuideSection(
|
||||
title=self.guide_section_labels["commands"],
|
||||
entries=command_entries,
|
||||
layout="list",
|
||||
)
|
||||
)
|
||||
|
||||
item_entries = tuple(self.collect_item_entries())
|
||||
if item_entries:
|
||||
sections.append(
|
||||
GuideSection(
|
||||
title=self.guide_section_labels["items"],
|
||||
entries=item_entries,
|
||||
)
|
||||
)
|
||||
|
||||
recipe_entries = tuple(self.collect_recipe_entries())
|
||||
if recipe_entries:
|
||||
sections.append(
|
||||
GuideSection(
|
||||
title=self.guide_section_labels["recipes"],
|
||||
entries=recipe_entries,
|
||||
)
|
||||
)
|
||||
|
||||
guide_entries = tuple(self.collect_guide_entries())
|
||||
if guide_entries:
|
||||
sections.append(
|
||||
GuideSection(
|
||||
title=self.guide_section_labels["guides"],
|
||||
entries=guide_entries,
|
||||
layout="list",
|
||||
)
|
||||
)
|
||||
|
||||
additional_sections = tuple(self.collect_additional_sections())
|
||||
if additional_sections:
|
||||
sections.extend(additional_sections)
|
||||
|
||||
return tuple(sections)
|
||||
|
||||
def build_guide_page(self) -> GuidePage:
|
||||
metadata: Dict[str, str] = {}
|
||||
for key, value in self.get_guide_metadata().items():
|
||||
metadata[key] = str(value)
|
||||
|
||||
related = self.get_related_links()
|
||||
|
||||
return GuidePage(
|
||||
title=self.get_guide_title(),
|
||||
subtitle=self.get_guide_subtitle(),
|
||||
sections=self.collect_guide_sections(),
|
||||
metadata=metadata,
|
||||
related_links=related,
|
||||
)
|
||||
|
||||
def render_guide_page(self, page: GuidePage) -> str:
|
||||
return render_html_page(page)
|
||||
|
||||
def render_guide(self) -> str:
|
||||
return self.render_guide_page(self.build_guide_page())
|
||||
|
||||
def get_guide_response(self, content: str) -> HTMLResponse:
|
||||
return HTMLResponse(content)
|
||||
|
||||
@override
|
||||
def generate_router_illustrated_guide(self):
|
||||
async def handler() -> HTMLResponse:
|
||||
return self.get_guide_response(self.render_guide())
|
||||
|
||||
return handler
|
||||
|
||||
def get_related_links(self) -> Dict[str, Sequence[Dict[str, str]]]:
|
||||
links: Dict[str, Sequence[Dict[str, str]]] = {}
|
||||
|
||||
parents = []
|
||||
for base in self.__class__.__mro__[1:]:
|
||||
if not issubclass(base, WPSAPI):
|
||||
continue
|
||||
if base.__module__.startswith("Plugins."):
|
||||
parents.append(base)
|
||||
if base is WPSAPI:
|
||||
break
|
||||
if parents:
|
||||
parents_links = [self._build_class_link(cls) for cls in reversed(parents)]
|
||||
links["父类链"] = tuple(filter(None, parents_links))
|
||||
|
||||
child_links = [self._build_class_link(child) for child in self._iter_subclasses(self.__class__)]
|
||||
child_links = [link for link in child_links if link]
|
||||
if child_links:
|
||||
links["子类"] = tuple(child_links)
|
||||
|
||||
return links
|
||||
|
||||
def _build_class_link(self, cls: type) -> Optional[Dict[str, str]]:
|
||||
if not hasattr(cls, "__module__") or not cls.__module__.startswith("Plugins."):
|
||||
return None
|
||||
path = f"/api/{cls.__name__}"
|
||||
return {
|
||||
"label": cls.__name__,
|
||||
"href": path,
|
||||
}
|
||||
|
||||
def _iter_subclasses(self, cls: type) -> List[type]:
|
||||
collected: List[type] = []
|
||||
for subclass in cls.__subclasses__():
|
||||
if not issubclass(subclass, WPSAPI):
|
||||
continue
|
||||
if not subclass.__module__.startswith("Plugins."):
|
||||
continue
|
||||
collected.append(subclass)
|
||||
collected.extend(self._iter_subclasses(subclass))
|
||||
return collected
|
||||
|
||||
def get_guide_subtitle(self) -> str:
|
||||
return "核心 Webhook 转发插件"
|
||||
|
||||
def get_guide_metadata(self) -> Dict[str, str]:
|
||||
return {
|
||||
"Webhook 状态": "已配置" if MAIN_WEBHOOK_URL else "未配置",
|
||||
}
|
||||
|
||||
def collect_command_entries(self) -> Sequence[GuideEntry]:
|
||||
return (
|
||||
{
|
||||
"title": "say",
|
||||
"identifier": "say",
|
||||
"description": "将后续消息内容以 Markdown 形式发送到主 Webhook。",
|
||||
"metadata": {"别名": "说"},
|
||||
"icon": "🗣️",
|
||||
"badge": "核心",
|
||||
},
|
||||
)
|
||||
|
||||
def collect_guide_entries(self) -> Sequence[GuideEntry]:
|
||||
return (
|
||||
{
|
||||
"title": "Webhook 绑定",
|
||||
"description": (
|
||||
"在项目配置中设置 `main_webhook_url` 后插件自动启用,"
|
||||
"所有注册的命令将调用 `send_markdown_message` 发送富文本。"
|
||||
),
|
||||
"icon": "🔗",
|
||||
},
|
||||
{
|
||||
"title": "消息格式",
|
||||
"description": (
|
||||
"默认使用 Markdown 模式发送,支持 `聊天ID` 与 `用户ID` 的 @ 提醒。"
|
||||
),
|
||||
"icon": "📝",
|
||||
},
|
||||
)
|
||||
|
||||
@override
|
||||
def is_enable_plugin(self) -> bool:
|
||||
if MAIN_WEBHOOK_URL == "":
|
||||
|
||||
@@ -13,7 +13,7 @@ from PWF.CoreModules.database import get_db, STATUS_COMPLETED
|
||||
from PWF.CoreModules.plugin_interface import DatabaseModel
|
||||
from PWF.CoreModules.flags import get_internal_debug
|
||||
|
||||
from .WPSAPI import WPSAPI
|
||||
from .WPSAPI import GuideEntry, GuideSection, WPSAPI
|
||||
from .WPSBackpackSystem import (
|
||||
BackpackItemDefinition,
|
||||
BackpackItemTier,
|
||||
@@ -73,6 +73,210 @@ class WPSAlchemyGame(WPSAPI):
|
||||
self._max_points_per_batch = MAX_POINTS_PER_BATCH
|
||||
logger.SaveProperties()
|
||||
|
||||
def get_guide_subtitle(self) -> str:
|
||||
return "积分炼金与材料合成系统"
|
||||
|
||||
def get_guide_metadata(self) -> Dict[str, str]:
|
||||
return {
|
||||
"配方数量": str(len(self._recipes)),
|
||||
"冷却时间(分钟)": str(self._cooldown_minutes),
|
||||
"单次积分上限": str(self._max_points_per_batch),
|
||||
}
|
||||
|
||||
def collect_command_entries(self) -> Sequence[GuideEntry]:
|
||||
return (
|
||||
{
|
||||
"title": "炼金",
|
||||
"identifier": "炼金",
|
||||
"description": "投入积分或三件材料,等待冷却后获取结果。",
|
||||
"metadata": {"别名": "alchemy"},
|
||||
},
|
||||
)
|
||||
|
||||
def collect_item_entries(self) -> Sequence[GuideEntry]:
|
||||
return (
|
||||
{
|
||||
"title": self.ASH_ITEM_NAME,
|
||||
"identifier": self.ASH_ITEM_ID,
|
||||
"description": "炼金失败后获得的基础材料,可再次参与配方或出售。",
|
||||
},
|
||||
{
|
||||
"title": self.SLAG_ITEM_NAME,
|
||||
"identifier": self.SLAG_ITEM_ID,
|
||||
"description": "由 `炉灰` 合成,部分园艺/商店配方会引用该物品。",
|
||||
},
|
||||
)
|
||||
|
||||
def collect_guide_entries(self) -> Sequence[GuideEntry]:
|
||||
return (
|
||||
{
|
||||
"title": "积分炼金",
|
||||
"description": (
|
||||
f"`炼金 <积分>` 消耗积分尝试炼金,单次上限 {self._max_points_per_batch},"
|
||||
"结果与运势、阶段概率表 `_PHASE_TABLE` 相关。"
|
||||
),
|
||||
"icon": "💎",
|
||||
},
|
||||
{
|
||||
"title": "材料炼金",
|
||||
"description": (
|
||||
"`炼金 <材料1> <材料2> <材料3> [次数]` 支持批量执行,配方信息可通过 `炼金配方` 查询。"
|
||||
),
|
||||
"icon": "🧪",
|
||||
},
|
||||
{
|
||||
"title": "冷却与恢复",
|
||||
"description": (
|
||||
f"默认冷却 {self._cooldown_minutes} 分钟,任务调度用于结算完成;"
|
||||
"重启后会自动恢复未结算记录。"
|
||||
),
|
||||
"icon": "⏲️",
|
||||
},
|
||||
)
|
||||
|
||||
def collect_additional_sections(self) -> Sequence[GuideSection]:
|
||||
sections = list(super().collect_additional_sections())
|
||||
sections.append(
|
||||
GuideSection(
|
||||
title="基础配方",
|
||||
entries=self._build_core_recipes(),
|
||||
layout="grid",
|
||||
section_id="alchemy-core",
|
||||
description="系统内置的基础配方,可在没有额外模块时直接使用。",
|
||||
)
|
||||
)
|
||||
|
||||
garden_recipes = self._build_garden_wine_recipes()
|
||||
if garden_recipes:
|
||||
sections.append(
|
||||
GuideSection(
|
||||
title="果酒配方",
|
||||
entries=garden_recipes,
|
||||
layout="grid",
|
||||
section_id="alchemy-garden",
|
||||
description="菜园系统提供的果酒炼金配方,使用三份果实即可酿造果酒。",
|
||||
)
|
||||
)
|
||||
|
||||
crystal_recipes = self._build_crystal_chain_recipes()
|
||||
if crystal_recipes:
|
||||
sections.append(
|
||||
GuideSection(
|
||||
title="水晶链路",
|
||||
entries=crystal_recipes,
|
||||
layout="list",
|
||||
section_id="alchemy-crystal",
|
||||
description="水晶系统的多阶段炼金链路,最终可获得黑水晶核心。",
|
||||
)
|
||||
)
|
||||
|
||||
return tuple(sections)
|
||||
|
||||
def _build_core_recipes(self) -> Sequence[GuideEntry]:
|
||||
entries: List[GuideEntry] = []
|
||||
entries.append(
|
||||
GuideEntry(
|
||||
title="炉灰 → 炉渣",
|
||||
identifier=f"{self.ASH_ITEM_ID} × 3",
|
||||
description="循环利用基础产物,将多余炉灰转化为更稀有的炉渣。",
|
||||
category="基础配方",
|
||||
metadata={
|
||||
"成功率": "100%",
|
||||
"失败产物": self.ASH_ITEM_ID,
|
||||
},
|
||||
icon="🔥",
|
||||
details=[
|
||||
{
|
||||
"type": "list",
|
||||
"items": [
|
||||
f"材料:{self.ASH_ITEM_ID} × 3",
|
||||
f"成功产物:{self.SLAG_ITEM_ID}",
|
||||
f"失败产物:{self.ASH_ITEM_ID}",
|
||||
],
|
||||
}
|
||||
],
|
||||
)
|
||||
)
|
||||
return entries
|
||||
|
||||
def _build_garden_wine_recipes(self) -> Sequence[GuideEntry]:
|
||||
try:
|
||||
from Plugins.WPSGardenSystem.garden_models import GARDEN_CROPS
|
||||
except ImportError:
|
||||
return ()
|
||||
entries: List[GuideEntry] = []
|
||||
for crop in GARDEN_CROPS.values():
|
||||
if not crop.wine_item_id:
|
||||
continue
|
||||
items = [
|
||||
f"材料:{crop.fruit_id} × 3",
|
||||
f"成功产物:{crop.wine_item_id}",
|
||||
"失败产物:garden_item_rot_fruit",
|
||||
"基础成功率:75%",
|
||||
]
|
||||
entries.append(
|
||||
GuideEntry(
|
||||
title=f"{crop.display_name}果酒",
|
||||
identifier=f"{crop.fruit_id} ×3",
|
||||
description=f"使用 {crop.display_name} 的果实炼制同名果酒。",
|
||||
category="果酒配方",
|
||||
metadata={
|
||||
"果酒稀有度": crop.wine_tier or "rare",
|
||||
},
|
||||
icon="🍷",
|
||||
tags=(crop.tier.title(),),
|
||||
details=[{"type": "list", "items": items}],
|
||||
)
|
||||
)
|
||||
return entries
|
||||
|
||||
def _build_crystal_chain_recipes(self) -> Sequence[GuideEntry]:
|
||||
try:
|
||||
from Plugins.WPSCrystalSystem.crystal_models import (
|
||||
DEFAULT_CRYSTAL_COLOR_MAP,
|
||||
DEFAULT_CRYSTAL_EXCHANGE_ENTRIES,
|
||||
)
|
||||
except ImportError:
|
||||
return ()
|
||||
entries: List[GuideEntry] = []
|
||||
for color_def in DEFAULT_CRYSTAL_COLOR_MAP.values():
|
||||
stage_details: List[str] = []
|
||||
for stage in color_def.chain_stages:
|
||||
mats = " + ".join(stage.materials)
|
||||
stage_details.append(
|
||||
f"{stage.identifier}:{mats} → {stage.result_item}(成功率 {stage.base_success_rate*100:.0f}%)"
|
||||
)
|
||||
stage_details.append(
|
||||
f"等待阶段:消耗 {', '.join(f'{k}×{v}' for k, v in color_def.wait_stage.consumed_items.items())},"
|
||||
f"耗时 {color_def.wait_stage.delay_minutes} 分钟"
|
||||
)
|
||||
fusion = color_def.final_fusion
|
||||
stage_details.append(
|
||||
f"最终融合:{', '.join(fusion.materials)} → {fusion.result_item}(成功率 {fusion.base_success_rate*100:.0f}%)"
|
||||
)
|
||||
entries.append(
|
||||
GuideEntry(
|
||||
title=color_def.display_name,
|
||||
identifier=color_def.color_key,
|
||||
description="水晶染色与融合的完整链路。",
|
||||
category="水晶链路",
|
||||
icon="💠",
|
||||
details=[{"type": "list", "items": stage_details}],
|
||||
)
|
||||
)
|
||||
for exchange in DEFAULT_CRYSTAL_EXCHANGE_ENTRIES.values():
|
||||
items = ", ".join(f"{item_id}×{qty}" for item_id, qty in exchange.required_items.items())
|
||||
entries.append(
|
||||
GuideEntry(
|
||||
title=exchange.metadata.get("display_name", exchange.identifier),
|
||||
identifier=exchange.identifier,
|
||||
description=f"兑换奖励:{exchange.reward_item}",
|
||||
category="水晶兑换",
|
||||
icon="🔁",
|
||||
details=[f"需求:{items}"],
|
||||
)
|
||||
)
|
||||
return entries
|
||||
@override
|
||||
def dependencies(self) -> List[type]:
|
||||
return [WPSAPI, WPSBackpackSystem, WPSConfigAPI, WPSFortuneSystem, WPSStoreSystem]
|
||||
@@ -858,6 +1062,31 @@ class WPSAlchemyRecipeLookup(WPSAPI):
|
||||
self._alchemy: Optional[WPSAlchemyGame] = None
|
||||
self._backpack: Optional[WPSBackpackSystem] = None
|
||||
|
||||
def get_guide_subtitle(self) -> str:
|
||||
return "查询指定物品涉及的炼金配方"
|
||||
|
||||
def collect_command_entries(self) -> Sequence[GuideEntry]:
|
||||
return (
|
||||
{
|
||||
"title": "炼金配方",
|
||||
"identifier": "炼金配方",
|
||||
"description": "展示物品作为材料、成功产物或失败产物的所有配方。",
|
||||
"metadata": {"别名": "alchemy_recipe"},
|
||||
},
|
||||
)
|
||||
|
||||
def collect_guide_entries(self) -> Sequence[GuideEntry]:
|
||||
return (
|
||||
{
|
||||
"title": "查询格式",
|
||||
"description": "`炼金配方 <物品ID>` 或 `炼金配方 <物品名称>`,忽略大小写。",
|
||||
},
|
||||
{
|
||||
"title": "输出结构",
|
||||
"description": "结果按材料/成功/失败三类分组,列出配方材料与成功率。",
|
||||
},
|
||||
)
|
||||
|
||||
def dependencies(self) -> List[type]:
|
||||
return [WPSAlchemyGame, WPSBackpackSystem]
|
||||
|
||||
|
||||
@@ -2,14 +2,14 @@ from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from typing import Dict, List, Optional, override
|
||||
from typing import Dict, List, Optional, Sequence, override
|
||||
|
||||
from PWF.Convention.Runtime.Architecture import Architecture
|
||||
from PWF.Convention.Runtime.GlobalConfig import ConsoleFrontColor, ProjectConfig
|
||||
from PWF.CoreModules.database import get_db
|
||||
from PWF.CoreModules.flags import get_internal_debug
|
||||
|
||||
from .WPSAPI import WPSAPI
|
||||
from .WPSAPI import GuideEntry, GuideSection, WPSAPI
|
||||
|
||||
logger: ProjectConfig = Architecture.Get(ProjectConfig)
|
||||
|
||||
@@ -58,6 +58,48 @@ class WPSBackpackSystem(WPSAPI):
|
||||
ITEMS_TABLE = "backpack_items"
|
||||
USER_ITEMS_TABLE = "backpack_user_items"
|
||||
|
||||
def get_guide_subtitle(self) -> str:
|
||||
return "管理物品注册、背包存储与查询的核心系统"
|
||||
|
||||
def get_guide_metadata(self) -> Dict[str, str]:
|
||||
return {
|
||||
"物品缓存数": str(len(self._item_cache)),
|
||||
"数据表": f"{self.ITEMS_TABLE}, {self.USER_ITEMS_TABLE}",
|
||||
}
|
||||
|
||||
def collect_command_entries(self) -> Sequence[GuideEntry]:
|
||||
return (
|
||||
{
|
||||
"title": "背包",
|
||||
"identifier": "背包",
|
||||
"description": "以稀有度分组展示用户当前携带物品。",
|
||||
"metadata": {"别名": "backpack"},
|
||||
},
|
||||
)
|
||||
|
||||
def collect_guide_entries(self) -> Sequence[GuideEntry]:
|
||||
tier_labels = ", ".join(tier.display_name for tier in BackpackItemTier)
|
||||
return (
|
||||
{
|
||||
"title": "物品注册",
|
||||
"description": (
|
||||
"`register_item(item_id, name, tier, description)` "
|
||||
"将物品写入背包表,重复调用会更新名称、稀有度和描述。"
|
||||
),
|
||||
},
|
||||
{
|
||||
"title": "稀有度体系",
|
||||
"description": f"支持稀有度:{tier_labels},调用 `to_markdown_label` 可渲染彩色标签。",
|
||||
},
|
||||
{
|
||||
"title": "库存操作",
|
||||
"description": (
|
||||
"`add_item` / `set_item_quantity` / `_get_user_quantity` "
|
||||
"确保用户物品数量保持非负,并自动创建记录。"
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self._item_cache: Dict[str, BackpackItemDefinition] = {}
|
||||
@@ -124,6 +166,50 @@ class WPSBackpackSystem(WPSAPI):
|
||||
description=description,
|
||||
)
|
||||
|
||||
def _iter_registered_items(self) -> Sequence[BackpackItemDefinition]:
|
||||
try:
|
||||
if not self._item_cache:
|
||||
self._warm_item_cache()
|
||||
except Exception:
|
||||
return ()
|
||||
return tuple(self._item_cache.values())
|
||||
|
||||
def collect_additional_sections(self) -> Sequence[GuideSection]:
|
||||
sections = list(super().collect_additional_sections())
|
||||
item_entries: List[GuideEntry] = []
|
||||
tier_icons = {
|
||||
BackpackItemTier.COMMON: "🪙",
|
||||
BackpackItemTier.RARE: "💠",
|
||||
BackpackItemTier.EPIC: "⚡",
|
||||
BackpackItemTier.LEGENDARY: "🌟",
|
||||
}
|
||||
for definition in self._iter_registered_items():
|
||||
item_entries.append(
|
||||
GuideEntry(
|
||||
title=definition.name,
|
||||
identifier=definition.item_id,
|
||||
description=definition.description or "(暂无描述)",
|
||||
category="背包物品",
|
||||
metadata={
|
||||
"稀有度": definition.tier.display_name,
|
||||
},
|
||||
icon=tier_icons.get(definition.tier, "🎁"),
|
||||
tags=(definition.tier.display_name,),
|
||||
group=definition.tier.display_name,
|
||||
)
|
||||
)
|
||||
if item_entries:
|
||||
sections.append(
|
||||
GuideSection(
|
||||
title="物品图鉴",
|
||||
entries=item_entries,
|
||||
layout="grid",
|
||||
section_id="backpack-items",
|
||||
description="当前已注册的背包物品列表,按稀有度分组展示。",
|
||||
)
|
||||
)
|
||||
return tuple(sections)
|
||||
|
||||
# region 对外接口
|
||||
|
||||
def register_item(
|
||||
|
||||
@@ -3,11 +3,13 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
from typing import Optional, Sequence
|
||||
|
||||
from PWF.Convention.Runtime.GlobalConfig import ConsoleFrontColor, ProjectConfig
|
||||
|
||||
from Plugins.WPSAPI import GuideEntry, GuideSection
|
||||
from .combat_plugin_base import WPSCombatBase
|
||||
from .combat_models import CombatConfig
|
||||
|
||||
|
||||
logger: ProjectConfig = ProjectConfig()
|
||||
@@ -16,6 +18,133 @@ logger: ProjectConfig = ProjectConfig()
|
||||
class WPSCombatAdventure(WPSCombatBase):
|
||||
"""冒险系统插件"""
|
||||
|
||||
def get_guide_subtitle(self) -> str:
|
||||
return "阶段式 PVE 冒险,产出装备与素材"
|
||||
|
||||
def collect_command_entries(self) -> Sequence[GuideEntry]:
|
||||
return (
|
||||
GuideEntry(
|
||||
title="冒险 开始",
|
||||
identifier="冒险 开始 [食物...]",
|
||||
description="启动第 1 阶段冒险,可额外投入食物缩短时间并提升成功率。",
|
||||
metadata={"别名": "start"},
|
||||
icon="🚀",
|
||||
tags=("阶段1", "需未受伤"),
|
||||
details=[
|
||||
{
|
||||
"type": "steps",
|
||||
"items": [
|
||||
"检查自身状态,确认未处于受伤或其他冒险中。",
|
||||
"可选:准备食物 `food_id` 列表;每个食物将被立即消耗。",
|
||||
"系统计算预计耗时、成功率并生成冒险记录。",
|
||||
"创建时钟任务,时间到后自动结算。",
|
||||
],
|
||||
},
|
||||
"结算时会根据装备强度与运势发放奖励。",
|
||||
],
|
||||
),
|
||||
GuideEntry(
|
||||
title="继续冒险",
|
||||
identifier="冒险 / 继续冒险",
|
||||
description="在已有链路下进入下一阶段或查看剩余时间。",
|
||||
metadata={"别名": "adventure / continue"},
|
||||
icon="⏱️",
|
||||
tags=("阶段推进",),
|
||||
details=[
|
||||
{
|
||||
"type": "steps",
|
||||
"items": [
|
||||
"查询当前冒险记录,若已在倒计时阶段则返回剩余时间。",
|
||||
"如冒险链完成并准备进入下一阶段,可再次投喂食物并启动。",
|
||||
"系统保持阶段编号,累计奖励与时间。",
|
||||
],
|
||||
}
|
||||
],
|
||||
),
|
||||
GuideEntry(
|
||||
title="冒险 停止",
|
||||
identifier="冒险 停止 / 放弃",
|
||||
description="放弃当前冒险链,结算状态并清空倒计时。",
|
||||
metadata={"别名": "放弃 / 停止"},
|
||||
icon="🛑",
|
||||
tags=("风险",),
|
||||
details=[
|
||||
"放弃后当前阶段奖励作废,未来可从第 1 阶段重开。",
|
||||
"若系统已开始结算,则命令会提示等待完成。",
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
def collect_guide_entries(self) -> Sequence[GuideEntry]:
|
||||
return (
|
||||
GuideEntry(
|
||||
title="冒险阶段",
|
||||
description="Adventure 链路包含多个阶段,难度逐渐提升,奖励也随之增加。",
|
||||
icon="⚙️",
|
||||
details=[
|
||||
{
|
||||
"type": "list",
|
||||
"items": [
|
||||
"阶段时间:基础 15 分钟,最高不超过 24 小时。",
|
||||
"投喂食物:每份食物提供额外时间与成功率加成。",
|
||||
"装备影响:装备强度越高,成功率越高且时间越短。",
|
||||
"运势影响:由运势系统提供当前整点的幸运值,用于修正成功率。",
|
||||
],
|
||||
}
|
||||
],
|
||||
),
|
||||
GuideEntry(
|
||||
title="奖励构成",
|
||||
description="冒险完成后会发放积分、装备、材料、纪念品等。",
|
||||
icon="🎁",
|
||||
details=[
|
||||
{
|
||||
"type": "list",
|
||||
"items": [
|
||||
"基础积分奖励随阶段提升。",
|
||||
"装备掉落根据稀有度加权抽取。",
|
||||
"部分阶段触发事件可获得药剂、种子或纪念品。",
|
||||
],
|
||||
}
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
def collect_additional_sections(self) -> Sequence[GuideSection]:
|
||||
sections = list(super().collect_additional_sections())
|
||||
adventure_config_entries: List[GuideEntry] = []
|
||||
config_labels = {
|
||||
"combat_adventure_base_time": "阶段起始时间 (分钟)",
|
||||
"combat_adventure_max_time": "时间上限 (分钟)",
|
||||
"combat_food_support_time": "单个食物提供时间 (分钟)",
|
||||
"combat_adventure_base_success_rate": "基础成功率",
|
||||
"combat_adventure_stage_penalty": "阶段成功率衰减",
|
||||
"combat_adventure_equipment_coeff": "装备强度加成系数",
|
||||
"combat_adventure_fortune_coeff": "运势加成系数",
|
||||
"combat_time_reduction_divisor": "时间缩减除数",
|
||||
}
|
||||
for key, label in config_labels.items():
|
||||
value = CombatConfig.get(key)
|
||||
adventure_config_entries.append(
|
||||
GuideEntry(
|
||||
title=label,
|
||||
identifier=key,
|
||||
description=f"{value}",
|
||||
category="配置项",
|
||||
icon="📐",
|
||||
)
|
||||
)
|
||||
sections.append(
|
||||
GuideSection(
|
||||
title="冒险参数一览",
|
||||
entries=adventure_config_entries,
|
||||
layout="grid",
|
||||
section_id="adventure-config",
|
||||
description="核心公式与系数决定冒险耗时、成功率与奖励结构。",
|
||||
)
|
||||
)
|
||||
return tuple(sections)
|
||||
|
||||
def is_enable_plugin(self) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List, Type
|
||||
from typing import Dict, List, Type, Union
|
||||
|
||||
from PWF.Convention.Runtime.Architecture import Architecture
|
||||
from PWF.Convention.Runtime.GlobalConfig import ConsoleFrontColor, ProjectConfig
|
||||
from PWF.CoreModules.plugin_interface import DatabaseModel
|
||||
|
||||
from Plugins.WPSAPI import WPSAPI
|
||||
from Plugins.WPSAPI import GuideEntry, GuideSection, WPSAPI
|
||||
from Plugins.WPSBackpackSystem import BackpackItemTier, WPSBackpackSystem
|
||||
from Plugins.WPSStoreSystem import WPSStoreSystem
|
||||
from Plugins.WPSConfigSystem import WPSConfigAPI
|
||||
@@ -46,6 +46,270 @@ class WPSCombatBase(WPSAPI):
|
||||
|
||||
_service: CombatService | None = None
|
||||
_initialized: bool = False
|
||||
|
||||
def collect_additional_sections(self) -> Sequence[GuideSection]:
|
||||
sections = list(super().collect_additional_sections())
|
||||
|
||||
equipment_entries = self._build_equipment_entries()
|
||||
if equipment_entries:
|
||||
sections.append(
|
||||
GuideSection(
|
||||
title="装备详单",
|
||||
entries=equipment_entries,
|
||||
layout="grid",
|
||||
section_id="combat-equipment",
|
||||
description="战斗系统预置的装备清单及属性效果。",
|
||||
)
|
||||
)
|
||||
|
||||
potion_entries = self._build_simple_item_entries(
|
||||
COMBAT_POTIONS,
|
||||
category="战斗药剂",
|
||||
)
|
||||
if potion_entries:
|
||||
sections.append(
|
||||
GuideSection(
|
||||
title="药剂与增益",
|
||||
entries=potion_entries,
|
||||
layout="grid",
|
||||
section_id="combat-potions",
|
||||
description="药剂品质影响价格与效果,部分由冒险掉落。",
|
||||
)
|
||||
)
|
||||
|
||||
material_entries = self._build_simple_item_entries(
|
||||
ADVENTURE_MATERIALS,
|
||||
category="冒险材料",
|
||||
)
|
||||
if material_entries:
|
||||
sections.append(
|
||||
GuideSection(
|
||||
title="冒险材料",
|
||||
entries=material_entries,
|
||||
layout="grid",
|
||||
section_id="combat-materials",
|
||||
description="材料主要来源于冒险战斗,稀有度决定获取概率。",
|
||||
)
|
||||
)
|
||||
|
||||
souvenir_entries = self._build_souvenir_entries()
|
||||
if souvenir_entries:
|
||||
sections.append(
|
||||
GuideSection(
|
||||
title="纪念品一览",
|
||||
entries=souvenir_entries,
|
||||
layout="grid",
|
||||
section_id="combat-souvenirs",
|
||||
description="纪念品可在营地或商店出售兑换积分。",
|
||||
)
|
||||
)
|
||||
|
||||
seed_entries = self._build_simple_item_entries(
|
||||
ADVENTURE_SEEDS,
|
||||
category="冒险种子",
|
||||
)
|
||||
if seed_entries:
|
||||
sections.append(
|
||||
GuideSection(
|
||||
title="冒险种子",
|
||||
entries=seed_entries,
|
||||
layout="grid",
|
||||
section_id="combat-seeds",
|
||||
description="冒险专属种子,可在菜园种植获取增益或任务物资。",
|
||||
)
|
||||
)
|
||||
|
||||
skill_entries = self._build_skill_entries()
|
||||
if skill_entries:
|
||||
sections.append(
|
||||
GuideSection(
|
||||
title="技能图鉴",
|
||||
entries=skill_entries,
|
||||
layout="list",
|
||||
section_id="combat-skills",
|
||||
description="装备附带的技能可在战斗中释放,冷却时间以回合计算。",
|
||||
)
|
||||
)
|
||||
|
||||
return tuple(sections)
|
||||
|
||||
def _format_tier(self, tier: BackpackItemTier) -> str:
|
||||
return f"{tier.display_name}"
|
||||
|
||||
def _build_equipment_entries(self) -> List[GuideEntry]:
|
||||
entries: List[GuideEntry] = []
|
||||
attr_names = {
|
||||
"HP": "生命值",
|
||||
"ATK": "攻击力",
|
||||
"DEF": "防御力",
|
||||
"SPD": "速度",
|
||||
"CRIT": "暴击率",
|
||||
"CRIT_DMG": "暴击伤害",
|
||||
}
|
||||
tier_icons = {
|
||||
BackpackItemTier.COMMON: "⚔️",
|
||||
BackpackItemTier.RARE: "🛡️",
|
||||
BackpackItemTier.EPIC: "🔥",
|
||||
BackpackItemTier.LEGENDARY: "🌟",
|
||||
}
|
||||
for eq in EQUIPMENT_REGISTRY.values():
|
||||
attr_list = []
|
||||
for key, value in eq.attributes.items():
|
||||
label = attr_names.get(key, key)
|
||||
suffix = "%" if key in ("CRIT", "CRIT_DMG") else ""
|
||||
attr_list.append(f"{label}+{value}{suffix}")
|
||||
skills = []
|
||||
for skill_id in eq.skill_ids:
|
||||
skill = SKILL_REGISTRY.get(skill_id)
|
||||
if skill:
|
||||
skills.append(f"{skill.icon} {skill.name}")
|
||||
metadata = {
|
||||
"槽位": eq.slot,
|
||||
"稀有度": self._format_tier(eq.tier),
|
||||
}
|
||||
details: List[Dict[str, Any] | str] = []
|
||||
if attr_list:
|
||||
details.append({"type": "list", "items": attr_list})
|
||||
if skills:
|
||||
details.append({"type": "list", "items": [f"技能:{info}" for info in skills]})
|
||||
if eq.description:
|
||||
details.append(eq.description)
|
||||
entries.append(
|
||||
GuideEntry(
|
||||
title=eq.name,
|
||||
identifier=eq.item_id,
|
||||
description=eq.description or "标准装备。",
|
||||
category="装备",
|
||||
metadata=metadata,
|
||||
icon=tier_icons.get(eq.tier, "⚔️"),
|
||||
tags=skills,
|
||||
details=details,
|
||||
)
|
||||
)
|
||||
return entries
|
||||
|
||||
def _build_simple_item_entries(
|
||||
self,
|
||||
registry: Dict[str, tuple],
|
||||
*,
|
||||
category: str,
|
||||
) -> List[GuideEntry]:
|
||||
entries: List[GuideEntry] = []
|
||||
icon_map = {
|
||||
"战斗药剂": "🧪",
|
||||
"冒险材料": "🪨",
|
||||
"纪念品": "🎖️",
|
||||
"冒险种子": "🌱",
|
||||
}
|
||||
for item_id, payload in registry.items():
|
||||
name, tier, *rest = payload
|
||||
description = rest[-1] if rest else ""
|
||||
metadata = {"稀有度": self._format_tier(tier)}
|
||||
if category == "战斗药剂":
|
||||
price_lookup = {
|
||||
BackpackItemTier.COMMON: 50,
|
||||
BackpackItemTier.RARE: 150,
|
||||
BackpackItemTier.EPIC: 500,
|
||||
}
|
||||
price = price_lookup.get(tier)
|
||||
if price:
|
||||
metadata["默认售价"] = f"{price} 分"
|
||||
entries.append(
|
||||
GuideEntry(
|
||||
title=name,
|
||||
identifier=item_id,
|
||||
description=description,
|
||||
category=category,
|
||||
metadata=metadata,
|
||||
icon=icon_map.get(category, "📦"),
|
||||
)
|
||||
)
|
||||
return entries
|
||||
|
||||
def _build_souvenir_entries(self) -> List[GuideEntry]:
|
||||
entries: List[GuideEntry] = []
|
||||
for item_id, (name, tier, price, desc) in ADVENTURE_SOUVENIRS.items():
|
||||
entries.append(
|
||||
GuideEntry(
|
||||
title=name,
|
||||
identifier=item_id,
|
||||
description=desc,
|
||||
category="纪念品",
|
||||
metadata={
|
||||
"稀有度": self._format_tier(tier),
|
||||
"基础售价": f"{price} 分",
|
||||
},
|
||||
icon="🎖️",
|
||||
)
|
||||
)
|
||||
return entries
|
||||
|
||||
def _build_skill_entries(self) -> List[GuideEntry]:
|
||||
entries: List[GuideEntry] = []
|
||||
for skill in SKILL_REGISTRY.values():
|
||||
details: List[Union[str, Dict[str, Any]]] = [
|
||||
{"type": "list", "items": [effect.get("description", str(effect)) for effect in skill.effects]},
|
||||
]
|
||||
if skill.cooldown:
|
||||
details.append(f"冷却:{skill.cooldown} 回合")
|
||||
entries.append(
|
||||
GuideEntry(
|
||||
title=skill.name,
|
||||
identifier=skill.skill_id,
|
||||
description=skill.description,
|
||||
category="技能",
|
||||
icon=skill.icon,
|
||||
metadata={},
|
||||
details=details,
|
||||
)
|
||||
)
|
||||
return entries
|
||||
|
||||
def get_guide_subtitle(self) -> str:
|
||||
return "冒险、战斗与装备体系的基础能力"
|
||||
|
||||
def get_guide_metadata(self) -> Dict[str, str]:
|
||||
return {
|
||||
"装备数量": str(len(EQUIPMENT_REGISTRY)),
|
||||
"药剂数量": str(len(COMBAT_POTIONS)),
|
||||
"纪念品数量": str(len(ADVENTURE_SOUVENIRS)),
|
||||
"冒险材料": str(len(ADVENTURE_MATERIALS)),
|
||||
}
|
||||
|
||||
def collect_item_entries(self) -> Sequence[GuideEntry]:
|
||||
return (
|
||||
{
|
||||
"title": "装备库",
|
||||
"description": f"{len(EQUIPMENT_REGISTRY)} 件装备,自动注册至背包并带有属性描述。",
|
||||
},
|
||||
{
|
||||
"title": "药剂与材料",
|
||||
"description": (
|
||||
f"{len(COMBAT_POTIONS)} 种药剂、{len(ADVENTURE_MATERIALS)} 种冒险素材,"
|
||||
"部分可在商店购买或冒险获得。"
|
||||
),
|
||||
},
|
||||
{
|
||||
"title": "纪念品",
|
||||
"description": f"{len(ADVENTURE_SOUVENIRS)} 种纪念品可在营地出售换取积分。",
|
||||
},
|
||||
)
|
||||
|
||||
def collect_guide_entries(self) -> Sequence[GuideEntry]:
|
||||
return (
|
||||
{
|
||||
"title": "冒险流程",
|
||||
"description": "冒险按阶段推进,使用食物缩短时间并依赖运势与装备强度影响结果。",
|
||||
},
|
||||
{
|
||||
"title": "装备体系",
|
||||
"description": "装备提供属性与技能加成,通过 `装备`/`战斗属性` 等指令查看详情。",
|
||||
},
|
||||
{
|
||||
"title": "积分与资源",
|
||||
"description": "冒险和战斗会产出积分、装备、材料等,通过商店与营地完成循环。",
|
||||
},
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def service(cls) -> CombatService:
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
from typing import Optional, Sequence
|
||||
|
||||
from PWF.Convention.Runtime.GlobalConfig import ConsoleFrontColor, ProjectConfig
|
||||
|
||||
from Plugins.WPSAPI import GuideEntry
|
||||
from .combat_plugin_base import WPSCombatBase
|
||||
|
||||
|
||||
@@ -15,6 +16,69 @@ logger: ProjectConfig = ProjectConfig()
|
||||
class WPSCombatBattle(WPSCombatBase):
|
||||
"""PVP对战插件"""
|
||||
|
||||
def get_guide_subtitle(self) -> str:
|
||||
return "玩家间回合制对战指令集"
|
||||
|
||||
def collect_command_entries(self) -> Sequence[GuideEntry]:
|
||||
return (
|
||||
GuideEntry(
|
||||
title="挑战",
|
||||
identifier="挑战 <目标用户ID>",
|
||||
description="向指定玩家发起 PVP 挑战。",
|
||||
metadata={"别名": "challenge"},
|
||||
icon="⚔️",
|
||||
details=[
|
||||
{"type": "list", "items": ["不可挑战自己。", "挑战在超时前需对方接受,否则自动失效。"]},
|
||||
],
|
||||
),
|
||||
GuideEntry(
|
||||
title="接受挑战",
|
||||
identifier="接受挑战 <挑战ID>",
|
||||
description="接受待处理的挑战并初始化战斗。",
|
||||
metadata={"别名": "accept"},
|
||||
icon="✅",
|
||||
details=[
|
||||
{"type": "steps", "items": ["输入挑战列表中的 ID。", "系统创建战斗记录并通知双方。"]},
|
||||
],
|
||||
),
|
||||
GuideEntry(
|
||||
title="拒绝挑战",
|
||||
identifier="拒绝挑战 <挑战ID>",
|
||||
description="拒绝尚未开始的挑战请求。",
|
||||
metadata={"别名": "reject"},
|
||||
icon="🚫",
|
||||
),
|
||||
GuideEntry(
|
||||
title="战斗动作",
|
||||
identifier="战斗 <战斗ID> <技能名>",
|
||||
description="在战斗中释放技能或执行普攻。",
|
||||
metadata={"别名": "battle"},
|
||||
icon="🌀",
|
||||
details=[
|
||||
{"type": "list", "items": ["技能冷却与效果可在 `技能列表` 中查看。", "战斗为回合制,按顺序执行。"]},
|
||||
],
|
||||
),
|
||||
GuideEntry(
|
||||
title="投降",
|
||||
identifier="投降 <战斗ID>",
|
||||
description="主动认输并结束当前战斗。",
|
||||
metadata={"别名": "surrender"},
|
||||
icon="🏳️",
|
||||
),
|
||||
)
|
||||
|
||||
def collect_guide_entries(self) -> Sequence[GuideEntry]:
|
||||
return (
|
||||
{
|
||||
"title": "挑战生命周期",
|
||||
"description": "挑战需在配置的超时前被接受,超时将自动失效。",
|
||||
},
|
||||
{
|
||||
"title": "战斗指令",
|
||||
"description": "战斗中按回合输入技能,系统根据属性与技能效果计算伤害。",
|
||||
},
|
||||
)
|
||||
|
||||
def is_enable_plugin(self) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
from typing import Optional, Sequence
|
||||
|
||||
from PWF.Convention.Runtime.Architecture import Architecture
|
||||
from PWF.Convention.Runtime.GlobalConfig import ConsoleFrontColor, ProjectConfig
|
||||
|
||||
from Plugins.WPSAPI import GuideEntry
|
||||
from Plugins.WPSBackpackSystem import WPSBackpackSystem
|
||||
from Plugins.WPSConfigSystem import WPSConfigAPI
|
||||
|
||||
@@ -27,6 +28,35 @@ class WPSCombatCamp(WPSCombatBase):
|
||||
meta[0].lower(): item_id for item_id, meta in self._souvenir_by_id.items()
|
||||
}
|
||||
|
||||
def get_guide_subtitle(self) -> str:
|
||||
return "营地寄售区:纪念品快速变现"
|
||||
|
||||
def collect_command_entries(self) -> Sequence[GuideEntry]:
|
||||
return (
|
||||
GuideEntry(
|
||||
title="营地 售出",
|
||||
identifier="营地 售出 <纪念品> <数量>",
|
||||
description="将纪念品出售给系统换取积分。",
|
||||
metadata={"别名": "camp"},
|
||||
icon="🏕️",
|
||||
details=[
|
||||
{"type": "steps", "items": ["输入纪念品名称或 ID。", "校验背包数量并扣除。", "根据基础售价发放积分。"]},
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
def collect_guide_entries(self) -> Sequence[GuideEntry]:
|
||||
return (
|
||||
{
|
||||
"title": "销售指令",
|
||||
"description": "`营地 售出 <纪念品> <数量>`,名称可用中文或物品ID。",
|
||||
},
|
||||
{
|
||||
"title": "积分结算",
|
||||
"description": "根据纪念品基础售价乘以数量发放积分,并扣除背包库存。",
|
||||
},
|
||||
)
|
||||
|
||||
def is_enable_plugin(self) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
from typing import Optional, Sequence
|
||||
|
||||
from PWF.Convention.Runtime.GlobalConfig import ConsoleFrontColor, ProjectConfig
|
||||
|
||||
from Plugins.WPSAPI import GuideEntry
|
||||
from .combat_plugin_base import WPSCombatBase
|
||||
|
||||
|
||||
@@ -15,6 +16,42 @@ logger: ProjectConfig = ProjectConfig()
|
||||
class WPSCombatEquipment(WPSCombatBase):
|
||||
"""装备管理插件"""
|
||||
|
||||
def get_guide_subtitle(self) -> str:
|
||||
return "穿戴与卸下战斗装备"
|
||||
|
||||
def collect_command_entries(self) -> Sequence[GuideEntry]:
|
||||
return (
|
||||
GuideEntry(
|
||||
title="装备",
|
||||
identifier="装备 [物品名|ID]",
|
||||
description="穿戴指定装备,或不带参数时查看当前装备。",
|
||||
metadata={"别名": "equip"},
|
||||
icon="🛡️",
|
||||
details=[
|
||||
{"type": "list", "items": ["名称与 ID 均可匹配装备库条目。", "若未携带该装备会提示不足。"]},
|
||||
],
|
||||
),
|
||||
GuideEntry(
|
||||
title="卸下",
|
||||
identifier="卸下 <槽位>",
|
||||
description="卸下指定槽位(weapon/helmet/armor/boots/accessory)的装备。",
|
||||
metadata={"别名": "unequip"},
|
||||
icon="🧤",
|
||||
),
|
||||
)
|
||||
|
||||
def collect_guide_entries(self) -> Sequence[GuideEntry]:
|
||||
return (
|
||||
{
|
||||
"title": "槽位说明",
|
||||
"description": "支持 weapon/helmet/armor/boots/accessory 五个槽位。",
|
||||
},
|
||||
{
|
||||
"title": "属性加成",
|
||||
"description": "装备属性直接影响战斗与冒险计算,可在 `战斗属性` 指令中查看综合加成。",
|
||||
},
|
||||
)
|
||||
|
||||
def is_enable_plugin(self) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
from typing import Optional, Sequence
|
||||
|
||||
from PWF.Convention.Runtime.GlobalConfig import ConsoleFrontColor, ProjectConfig
|
||||
from Plugins.WPSAPI import GuideEntry
|
||||
from Plugins.WPSCombatSystem.combat_models import CombatConfig
|
||||
|
||||
from .combat_plugin_base import WPSCombatBase
|
||||
|
||||
@@ -15,6 +17,32 @@ logger: ProjectConfig = ProjectConfig()
|
||||
class WPSCombatHeal(WPSCombatBase):
|
||||
"""治疗系统插件"""
|
||||
|
||||
def get_guide_subtitle(self) -> str:
|
||||
return "恢复受伤状态并重返冒险"
|
||||
|
||||
def collect_command_entries(self) -> Sequence[GuideEntry]:
|
||||
return (
|
||||
GuideEntry(
|
||||
title="治疗",
|
||||
identifier="治疗",
|
||||
description="消耗积分清除受伤状态,使角色可继续冒险或战斗。",
|
||||
metadata={"别名": "heal / 恢复"},
|
||||
icon="💊",
|
||||
details=[
|
||||
{"type": "list", "items": ["若未受伤会返回提示信息。", "扣除积分后立即清除受伤标记。"]},
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
def collect_guide_entries(self) -> Sequence[GuideEntry]:
|
||||
heal_cost = CombatConfig.get_int("combat_heal_cost")
|
||||
return (
|
||||
{
|
||||
"title": "费用计算",
|
||||
"description": f"治疗费用由配置 `combat_heal_cost` 决定,当前为 {heal_cost} 分。",
|
||||
},
|
||||
)
|
||||
|
||||
def is_enable_plugin(self) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
from typing import Optional, Sequence
|
||||
|
||||
from PWF.Convention.Runtime.Config import *
|
||||
from PWF.Convention.Runtime.GlobalConfig import ConsoleFrontColor, ProjectConfig
|
||||
from Plugins.WPSCombatSystem.combat_models import PlayerStats, EquipmentDefinition
|
||||
from Plugins.WPSAPI import GuideEntry
|
||||
from Plugins.WPSCombatSystem.combat_models import EquipmentDefinition, PlayerStats
|
||||
from .combat_plugin_base import WPSCombatBase
|
||||
|
||||
|
||||
@@ -16,6 +17,44 @@ logger: ProjectConfig = ProjectConfig()
|
||||
class WPSCombatStatus(WPSCombatBase):
|
||||
"""状态查看插件"""
|
||||
|
||||
def get_guide_subtitle(self) -> str:
|
||||
return "查看战斗属性、装备栏与技能列表"
|
||||
|
||||
def collect_command_entries(self) -> Sequence[GuideEntry]:
|
||||
return (
|
||||
GuideEntry(
|
||||
title="战斗属性",
|
||||
identifier="战斗属性",
|
||||
description="展示基础属性、装备强度与技能概览。",
|
||||
metadata={"别名": "combat"},
|
||||
icon="📊",
|
||||
),
|
||||
GuideEntry(
|
||||
title="装备栏",
|
||||
identifier="装备栏",
|
||||
description="仅查看当前装备与各槽位详情。",
|
||||
icon="🎽",
|
||||
),
|
||||
GuideEntry(
|
||||
title="技能列表",
|
||||
identifier="技能列表",
|
||||
description="罗列当前可用技能、描述与冷却时间。",
|
||||
icon="📜",
|
||||
),
|
||||
)
|
||||
|
||||
def collect_guide_entries(self) -> Sequence[GuideEntry]:
|
||||
return (
|
||||
{
|
||||
"title": "属性来源",
|
||||
"description": "数值由基础配置、装备加成与技能被动共同组成。",
|
||||
},
|
||||
{
|
||||
"title": "技能冷却",
|
||||
"description": "输出中会标注技能冷却回合,用于 PVP 战斗操作参考。",
|
||||
},
|
||||
)
|
||||
|
||||
def is_enable_plugin(self) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
@@ -14,6 +14,41 @@ CHECKIN_POINTS = logger.FindItem("checkin_points", 100)
|
||||
logger.SaveProperties()
|
||||
|
||||
class WPSConfigAPI(WPSAPI):
|
||||
def get_guide_subtitle(self) -> str:
|
||||
return "用户基础资料与积分配置接口"
|
||||
|
||||
def get_guide_metadata(self) -> Dict[str, str]:
|
||||
return {
|
||||
"数据表": "user_info",
|
||||
"每日签到积分": str(CHECKIN_POINTS),
|
||||
}
|
||||
|
||||
def collect_command_entries(self) -> Sequence[GuideEntry]:
|
||||
return (
|
||||
{
|
||||
"title": "config",
|
||||
"identifier": "config",
|
||||
"description": "配置与查询用户昵称、URL、积分等信息。",
|
||||
"metadata": {"别名": "cfg"},
|
||||
},
|
||||
)
|
||||
|
||||
def collect_guide_entries(self) -> Sequence[GuideEntry]:
|
||||
return (
|
||||
{
|
||||
"title": "设置指令",
|
||||
"description": "`config set user.name <昵称>` / `config set user.url <URL>`",
|
||||
},
|
||||
{
|
||||
"title": "查询指令",
|
||||
"description": "`config get user.name|user.url|user.point` 返回当前资料或积分。",
|
||||
},
|
||||
{
|
||||
"title": "数据校验",
|
||||
"description": "内部自动确保用户记录存在,并限制昵称长度与 URL 前缀。",
|
||||
},
|
||||
)
|
||||
|
||||
@override
|
||||
def dependencies(self) -> List[Type]:
|
||||
return [WPSAPI]
|
||||
@@ -196,6 +231,37 @@ class WPSConfigAPI(WPSAPI):
|
||||
|
||||
|
||||
class WPSCheckinAPI(WPSAPI):
|
||||
def get_guide_subtitle(self) -> str:
|
||||
return "每日签到并发放积分的快捷指令"
|
||||
|
||||
def get_guide_metadata(self) -> Dict[str, str]:
|
||||
return {
|
||||
"数据表": "daily_checkin",
|
||||
"签到积分": str(CHECKIN_POINTS),
|
||||
}
|
||||
|
||||
def collect_command_entries(self) -> Sequence[GuideEntry]:
|
||||
return (
|
||||
{
|
||||
"title": "checkin",
|
||||
"identifier": "checkin",
|
||||
"description": "执行签到流程,发放积分并反馈今日进度。",
|
||||
"metadata": {"别名": "签到 / 积分"},
|
||||
},
|
||||
)
|
||||
|
||||
def collect_guide_entries(self) -> Sequence[GuideEntry]:
|
||||
return (
|
||||
{
|
||||
"title": "签到逻辑",
|
||||
"description": "同一日多次调用仅第一次成功,并记录在 `daily_checkin` 表内。",
|
||||
},
|
||||
{
|
||||
"title": "积分结算",
|
||||
"description": "成功签到将通过 `WPSConfigAPI.adjust_user_points` 增加积分。",
|
||||
},
|
||||
)
|
||||
|
||||
@override
|
||||
def dependencies(self) -> List[Type]:
|
||||
return [WPSAPI]
|
||||
|
||||
@@ -53,6 +53,54 @@ class WPSCrystalSystem(WPSAPI):
|
||||
key.lower(): value for key, value in DEFAULT_CRYSTAL_EXCHANGE_ENTRIES.items()
|
||||
}
|
||||
|
||||
def get_guide_subtitle(self) -> str:
|
||||
return "水晶养成、颜色淬炼与兑换拓展系统"
|
||||
|
||||
def get_guide_metadata(self) -> Dict[str, str]:
|
||||
return {
|
||||
"颜色数量": str(len(self._colors)),
|
||||
"水晶物品": str(len(self._items)),
|
||||
"兑换项目": str(len(self._exchange_entries)),
|
||||
}
|
||||
|
||||
def collect_command_entries(self) -> Sequence[GuideEntry]:
|
||||
return (
|
||||
{
|
||||
"title": "水晶",
|
||||
"identifier": "水晶",
|
||||
"description": "查看系统配置或执行变色与兑换等操作。",
|
||||
"metadata": {"别名": "crystal"},
|
||||
},
|
||||
)
|
||||
|
||||
def collect_item_entries(self) -> Sequence[GuideEntry]:
|
||||
return (
|
||||
{
|
||||
"title": "基础水晶物品",
|
||||
"description": f"{len(self._items)} 个水晶组件,可由背包/商店系统持有与交易。",
|
||||
},
|
||||
{
|
||||
"title": "颜色链路",
|
||||
"description": f"{len(self._colors)} 条变色链(包含等待阶段与最终融合)。",
|
||||
},
|
||||
)
|
||||
|
||||
def collect_guide_entries(self) -> Sequence[GuideEntry]:
|
||||
return (
|
||||
{
|
||||
"title": "变色流程",
|
||||
"description": "`水晶 变色 <颜色>` 进入等待流程,完成后获得对应水晶部件。",
|
||||
},
|
||||
{
|
||||
"title": "兑换指令",
|
||||
"description": "`水晶 兑换 <ID>` 消耗配置好的材料换取奖励物品。",
|
||||
},
|
||||
{
|
||||
"title": "菜园扩展",
|
||||
"description": "系统会向菜园注册水晶树作物,使果实与水晶体系互相联动。",
|
||||
},
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# Plugin lifecycle
|
||||
# ------------------------------------------------------------------ #
|
||||
|
||||
@@ -32,6 +32,35 @@ _FORTUNE_STAGE_TABLE: List[Tuple[float, str]] = [
|
||||
class WPSFortuneSystem(WPSAPI):
|
||||
"""基于整点哈希的运势系统,可供其他模块复用"""
|
||||
|
||||
def get_guide_subtitle(self) -> str:
|
||||
return "提供整点运势值及阶段信息的公共组件"
|
||||
|
||||
def collect_command_entries(self) -> Sequence[GuideEntry]:
|
||||
return (
|
||||
{
|
||||
"title": "fortune",
|
||||
"identifier": "fortune",
|
||||
"description": "查询当前整点的运势值与阶段文本。",
|
||||
"metadata": {"别名": "运势"},
|
||||
},
|
||||
)
|
||||
|
||||
def collect_guide_entries(self) -> Sequence[GuideEntry]:
|
||||
return (
|
||||
{
|
||||
"title": "运势算法",
|
||||
"description": "基于用户ID与整点时间的 SHA-256 哈希映射为 [-0.9999, 0.9999] 区间。",
|
||||
},
|
||||
{
|
||||
"title": "阶段划分",
|
||||
"description": "通过 `_FORTUNE_STAGE_TABLE` 匹配阶段标签,供冒险等系统引用。",
|
||||
},
|
||||
{
|
||||
"title": "复用接口",
|
||||
"description": "`get_fortune_value / get_fortune_info` 可被其他插件同步或异步调用。",
|
||||
},
|
||||
)
|
||||
|
||||
@override
|
||||
def dependencies(self) -> List[Type]:
|
||||
return [WPSAPI]
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from typing import List, Optional, Type
|
||||
from typing import Any, Dict, List, Optional, Sequence, Type, Union
|
||||
|
||||
from PWF.Convention.Runtime.Architecture import Architecture
|
||||
from PWF.Convention.Runtime.GlobalConfig import ConsoleFrontColor, ProjectConfig
|
||||
from PWF.CoreModules.plugin_interface import DatabaseModel
|
||||
|
||||
from Plugins.WPSAPI import WPSAPI
|
||||
from Plugins.WPSAPI import GuideEntry, GuideSection, WPSAPI
|
||||
from Plugins.WPSBackpackSystem import (
|
||||
BackpackItemTier,
|
||||
WPSBackpackSystem,
|
||||
@@ -33,6 +33,166 @@ class WPSGardenBase(WPSAPI):
|
||||
_service: GardenService | None = None
|
||||
_initialized: bool = False
|
||||
|
||||
def get_guide_subtitle(self) -> str:
|
||||
return "菜园作物种植与联动系统的核心服务"
|
||||
|
||||
def get_guide_metadata(self) -> Dict[str, str]:
|
||||
service = self.service()
|
||||
config = service.config
|
||||
return {
|
||||
"作物数量": str(len(GARDEN_CROPS)),
|
||||
"最大地块": str(config.max_plots),
|
||||
"出售倍率": str(config.sale_multiplier),
|
||||
}
|
||||
|
||||
def collect_item_entries(self) -> Sequence[GuideEntry]:
|
||||
tier_counter: Dict[str, int] = {}
|
||||
wine_counter: int = 0
|
||||
for crop in GARDEN_CROPS.values():
|
||||
tier_counter[crop.tier] = tier_counter.get(crop.tier, 0) + 1
|
||||
if crop.wine_item_id:
|
||||
wine_counter += 1
|
||||
entries: List[GuideEntry] = []
|
||||
for tier, count in sorted(tier_counter.items()):
|
||||
entries.append(
|
||||
{
|
||||
"title": f"{tier.title()} 作物",
|
||||
"description": f"{count} 种作物,可收获果实与额外奖励。",
|
||||
}
|
||||
)
|
||||
entries.append(
|
||||
{
|
||||
"title": "果酒配方",
|
||||
"description": f"{wine_counter} 种作物支持果酒配方,并与战斗系统的果酒增益联动。",
|
||||
}
|
||||
)
|
||||
if GARDEN_MISC_ITEMS:
|
||||
entries.append(
|
||||
{
|
||||
"title": "杂项素材",
|
||||
"description": f"{len(GARDEN_MISC_ITEMS)} 种额外素材,可用于任务或商店出售。",
|
||||
}
|
||||
)
|
||||
return tuple(entries)
|
||||
|
||||
def collect_guide_entries(self) -> Sequence[GuideEntry]:
|
||||
service = self.service()
|
||||
return (
|
||||
{
|
||||
"title": "成长流程",
|
||||
"description": (
|
||||
"种植后根据作物 `growth_minutes` 决定成熟时间,系统会在成熟时通过时钟任务提醒。"
|
||||
),
|
||||
},
|
||||
{
|
||||
"title": "收获收益",
|
||||
"description": (
|
||||
"收获基础产量由 `base_yield` 决定,额外奖励受运势与 `extra_reward` 配置影响。"
|
||||
),
|
||||
},
|
||||
{
|
||||
"title": "果实售出",
|
||||
"description": (
|
||||
f"通过 `菜园 售出` 指令以 {service.config.sale_multiplier} 倍种子价格出售果实并获取积分。"
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
def collect_additional_sections(self) -> Sequence[GuideSection]:
|
||||
sections = list(super().collect_additional_sections())
|
||||
|
||||
crop_entries: List[GuideEntry] = []
|
||||
tier_icon = {
|
||||
"common": "🌱",
|
||||
"rare": "🌳",
|
||||
"epic": "🍷",
|
||||
"legendary": "🏵️",
|
||||
}
|
||||
for crop in GARDEN_CROPS.values():
|
||||
reward_desc = ""
|
||||
if crop.extra_reward.kind == "points":
|
||||
payload = crop.extra_reward.payload
|
||||
reward_desc = (
|
||||
f"额外积分 {payload.get('min', 0)}~{payload.get('max', 0)},"
|
||||
f"触发率 {crop.extra_reward.base_rate*100:.0f}%"
|
||||
)
|
||||
elif crop.extra_reward.kind == "item":
|
||||
payload = crop.extra_reward.payload
|
||||
reward_desc = (
|
||||
f"额外物品 `{crop.extra_item_id}` 数量 {payload.get('min', 0)}~{payload.get('max', 0)},"
|
||||
f"触发率 {crop.extra_reward.base_rate*100:.0f}%"
|
||||
)
|
||||
details: List[Union[str, Dict[str, Any]]] = [
|
||||
{
|
||||
"type": "list",
|
||||
"items": [
|
||||
f"生长时间:{crop.growth_minutes} 分钟",
|
||||
f"基础产量:{crop.base_yield} 个果实",
|
||||
reward_desc or "无额外奖励",
|
||||
f"种子售价:{crop.seed_price} 分",
|
||||
],
|
||||
}
|
||||
]
|
||||
if crop.wine_item_id:
|
||||
details.append(
|
||||
{
|
||||
"type": "list",
|
||||
"items": [
|
||||
f"果酒:{crop.wine_item_id}(稀有度 {crop.wine_tier or 'rare'})",
|
||||
"炼金配方:三份果实 + 炼金坩埚 → 果酒。",
|
||||
],
|
||||
}
|
||||
)
|
||||
crop_entries.append(
|
||||
GuideEntry(
|
||||
title=crop.display_name,
|
||||
identifier=crop.seed_id,
|
||||
description=f"果实 ID:{crop.fruit_id}",
|
||||
category="作物",
|
||||
metadata={
|
||||
"稀有度": crop.tier,
|
||||
"果实ID": crop.fruit_id,
|
||||
},
|
||||
icon=tier_icon.get(crop.tier.lower(), "🌿"),
|
||||
tags=(crop.tier.title(),),
|
||||
details=details,
|
||||
)
|
||||
)
|
||||
if crop_entries:
|
||||
sections.append(
|
||||
GuideSection(
|
||||
title="作物图鉴",
|
||||
entries=crop_entries,
|
||||
layout="grid",
|
||||
section_id="garden-crops",
|
||||
description="每种作物的成长周期、产出与额外奖励说明。",
|
||||
)
|
||||
)
|
||||
|
||||
if GARDEN_MISC_ITEMS:
|
||||
misc_entries: List[GuideEntry] = []
|
||||
for item_id, meta in GARDEN_MISC_ITEMS.items():
|
||||
misc_entries.append(
|
||||
GuideEntry(
|
||||
title=meta.get("name", item_id),
|
||||
identifier=item_id,
|
||||
description=meta.get("description", ""),
|
||||
category="杂项素材",
|
||||
icon="🧺",
|
||||
)
|
||||
)
|
||||
sections.append(
|
||||
GuideSection(
|
||||
title="杂项素材",
|
||||
entries=misc_entries,
|
||||
layout="grid",
|
||||
section_id="garden-misc",
|
||||
description="园艺相关的任务或合成所需的特殊素材。",
|
||||
)
|
||||
)
|
||||
|
||||
return tuple(sections)
|
||||
|
||||
@classmethod
|
||||
def service(cls) -> GardenService:
|
||||
if cls._service is None:
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
from typing import Optional, Sequence
|
||||
|
||||
from PWF.Convention.Runtime.Architecture import Architecture
|
||||
|
||||
from Plugins.WPSAPI import GuideEntry
|
||||
from Plugins.WPSBackpackSystem import WPSBackpackSystem
|
||||
from Plugins.WPSConfigSystem import WPSConfigAPI
|
||||
from Plugins.WPSFortuneSystem import WPSFortuneSystem
|
||||
@@ -14,6 +15,42 @@ from .garden_plugin_base import WPSGardenBase
|
||||
|
||||
|
||||
class WPSGardenHarvest(WPSGardenBase):
|
||||
def get_guide_subtitle(self) -> str:
|
||||
return "收获成熟作物并处理额外奖励"
|
||||
|
||||
def collect_command_entries(self) -> Sequence[GuideEntry]:
|
||||
return (
|
||||
GuideEntry(
|
||||
title="收获",
|
||||
identifier="收获 <地块序号>",
|
||||
description="从成熟地块采摘果实并发放额外奖励。",
|
||||
metadata={"别名": "harvest"},
|
||||
icon="🧺",
|
||||
details=[
|
||||
{
|
||||
"type": "steps",
|
||||
"items": [
|
||||
"输入正整数地块序号。",
|
||||
"系统校验成熟状态,计算基础果实数量。",
|
||||
"发放额外奖励:积分或额外物品会自动结算。",
|
||||
],
|
||||
}
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
def collect_guide_entries(self) -> Sequence[GuideEntry]:
|
||||
return (
|
||||
{
|
||||
"title": "指令格式",
|
||||
"description": "`收获 <地块序号>`,序号需为正整数。",
|
||||
},
|
||||
{
|
||||
"title": "收益构成",
|
||||
"description": "基础果实直接入背包,额外奖励可能为积分或额外物品。",
|
||||
},
|
||||
)
|
||||
|
||||
def wake_up(self) -> None:
|
||||
super().wake_up()
|
||||
self.register_plugin("harvest")
|
||||
|
||||
@@ -2,16 +2,54 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
from typing import Optional, Sequence
|
||||
|
||||
from PWF.Convention.Runtime.Architecture import Architecture
|
||||
|
||||
from Plugins.WPSAPI import GuideEntry
|
||||
from Plugins.WPSBackpackSystem import WPSBackpackSystem
|
||||
|
||||
from .garden_plugin_base import WPSGardenBase
|
||||
|
||||
|
||||
class WPSGardenPlant(WPSGardenBase):
|
||||
def get_guide_subtitle(self) -> str:
|
||||
return "种植作物并分配地块"
|
||||
|
||||
def collect_command_entries(self) -> Sequence[GuideEntry]:
|
||||
return (
|
||||
GuideEntry(
|
||||
title="种植",
|
||||
identifier="种植 <种子> [地块序号]",
|
||||
description="在指定地块种下一颗种子,默认选取下一个空地。",
|
||||
metadata={"别名": "plant"},
|
||||
icon="🌱",
|
||||
details=[
|
||||
{
|
||||
"type": "steps",
|
||||
"items": [
|
||||
"确认背包中持有对应种子。",
|
||||
"可选:指定地块序号(正整数),否则使用下一个空地。",
|
||||
"系统校验库存并写入菜园数据库,生成成熟计时。",
|
||||
],
|
||||
},
|
||||
"种植成功后立即扣除种子数量并返回成熟时间。",
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
def collect_guide_entries(self) -> Sequence[GuideEntry]:
|
||||
return (
|
||||
{
|
||||
"title": "指令格式",
|
||||
"description": "`种植 <种子ID|名称> [地块序号]`,默认选择下一个空地。",
|
||||
},
|
||||
{
|
||||
"title": "库存校验",
|
||||
"description": "会检查背包种子数量,不足时返回提示而不消耗资源。",
|
||||
},
|
||||
)
|
||||
|
||||
def wake_up(self) -> None:
|
||||
super().wake_up()
|
||||
self.register_plugin("plant")
|
||||
|
||||
@@ -2,12 +2,44 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
from typing import Optional, Sequence
|
||||
|
||||
from Plugins.WPSAPI import GuideEntry
|
||||
from .garden_plugin_base import WPSGardenBase
|
||||
|
||||
|
||||
class WPSGardenRemove(WPSGardenBase):
|
||||
def get_guide_subtitle(self) -> str:
|
||||
return "清理地块以重新种植"
|
||||
|
||||
def collect_command_entries(self) -> Sequence[GuideEntry]:
|
||||
return (
|
||||
GuideEntry(
|
||||
title="铲除",
|
||||
identifier="铲除 <地块序号>",
|
||||
description="清空指定地块,移除作物与成熟计时。",
|
||||
metadata={"别名": "remove"},
|
||||
icon="🧹",
|
||||
details=[
|
||||
{
|
||||
"type": "list",
|
||||
"items": [
|
||||
"常用于处理枯萎或不再需要的作物。",
|
||||
"操作不可逆,被铲除的作物不会返还种子。",
|
||||
],
|
||||
}
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
def collect_guide_entries(self) -> Sequence[GuideEntry]:
|
||||
return (
|
||||
{
|
||||
"title": "使用场景",
|
||||
"description": "用于清理枯萎或不再需要的作物,释放地块。",
|
||||
},
|
||||
)
|
||||
|
||||
def wake_up(self) -> None:
|
||||
super().wake_up()
|
||||
self.register_plugin("remove")
|
||||
|
||||
@@ -2,17 +2,55 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
from typing import Optional, Sequence
|
||||
|
||||
from PWF.Convention.Runtime.Architecture import Architecture
|
||||
|
||||
from PWF.CoreModules.database import get_db
|
||||
from Plugins.WPSAPI import GuideEntry
|
||||
from Plugins.WPSBackpackSystem import WPSBackpackSystem
|
||||
|
||||
from .garden_plugin_base import WPSGardenBase
|
||||
|
||||
|
||||
class WPSGardenSteal(WPSGardenBase):
|
||||
def get_guide_subtitle(self) -> str:
|
||||
return "偷取其他用户成熟果实的互动指令"
|
||||
|
||||
def collect_command_entries(self) -> Sequence[GuideEntry]:
|
||||
return (
|
||||
GuideEntry(
|
||||
title="偷取",
|
||||
identifier="偷取 <用户> <地块序号>",
|
||||
description="从其他用户的成熟作物中偷取果实。",
|
||||
metadata={"别名": "steal"},
|
||||
icon="🕵️",
|
||||
details=[
|
||||
{
|
||||
"type": "steps",
|
||||
"items": [
|
||||
"输入目标用户 ID 或昵称,以及成熟地块序号。",
|
||||
"系统校验目标用户菜园与成熟状态。",
|
||||
"成功后获得 1 个果实并通知对方被偷记录。",
|
||||
],
|
||||
},
|
||||
"同一地块可多次被不同用户偷取,超限后将提示果实不足。",
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
def collect_guide_entries(self) -> Sequence[GuideEntry]:
|
||||
return (
|
||||
{
|
||||
"title": "指令格式",
|
||||
"description": "`偷取 <用户ID|昵称> <地块序号>`,不可针对自己。",
|
||||
},
|
||||
{
|
||||
"title": "通知机制",
|
||||
"description": "成功偷取后会向目标用户推送警报消息,包含剩余果实数量。",
|
||||
},
|
||||
)
|
||||
|
||||
def wake_up(self) -> None:
|
||||
super().wake_up()
|
||||
self.register_plugin("steal")
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
from typing import Optional, Sequence
|
||||
|
||||
from PWF.Convention.Runtime.Architecture import Architecture
|
||||
|
||||
from Plugins.WPSAPI import GuideEntry
|
||||
from Plugins.WPSBackpackSystem import WPSBackpackSystem
|
||||
from Plugins.WPSConfigSystem import WPSConfigAPI
|
||||
|
||||
@@ -13,6 +14,58 @@ from .garden_plugin_base import WPSGardenBase
|
||||
|
||||
|
||||
class WPSGardenView(WPSGardenBase):
|
||||
def get_guide_subtitle(self) -> str:
|
||||
return "查看菜园概览与果实售出"
|
||||
|
||||
def collect_command_entries(self) -> Sequence[GuideEntry]:
|
||||
return (
|
||||
GuideEntry(
|
||||
title="菜园 概览",
|
||||
identifier="菜园",
|
||||
description="显示当前所有地块状态与剩余果实。",
|
||||
metadata={"别名": "garden"},
|
||||
icon="🗺️",
|
||||
details=[
|
||||
{
|
||||
"type": "list",
|
||||
"items": [
|
||||
"列出地块序号、作物名称、成熟时间与被偷情况。",
|
||||
"空地块会提示剩余可用数量。",
|
||||
],
|
||||
}
|
||||
],
|
||||
),
|
||||
GuideEntry(
|
||||
title="菜园 售出",
|
||||
identifier="菜园 售出 <果实> <数量>",
|
||||
description="售出成熟果实并换取积分。",
|
||||
metadata={"别名": "garden sell"},
|
||||
icon="💰",
|
||||
details=[
|
||||
{
|
||||
"type": "steps",
|
||||
"items": [
|
||||
"检查背包持有的果实数量。",
|
||||
"输入欲出售的果实 ID/名称与数量。",
|
||||
"系统按配置的售价乘以数量发放积分,同时扣除背包库存。",
|
||||
],
|
||||
}
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
def collect_guide_entries(self) -> Sequence[GuideEntry]:
|
||||
return (
|
||||
{
|
||||
"title": "概览视图",
|
||||
"description": "默认输出地块编号、成熟状态、剩余果实与被偷记录。",
|
||||
},
|
||||
{
|
||||
"title": "果实售出",
|
||||
"description": "`菜园 售出 <果实> <数量>`,自动结算积分并扣除背包库存。",
|
||||
},
|
||||
)
|
||||
|
||||
def wake_up(self) -> None:
|
||||
super().wake_up()
|
||||
self.register_plugin("garden")
|
||||
|
||||
@@ -83,6 +83,42 @@ class WPSStoreSystem(WPSAPI):
|
||||
logger.SaveProperties()
|
||||
self._permanent_mode_ids: set[str] = set()
|
||||
|
||||
def get_guide_subtitle(self) -> str:
|
||||
return "系统商品与玩家寄售的统一商店"
|
||||
|
||||
def get_guide_metadata(self) -> Dict[str, str]:
|
||||
return {
|
||||
"已注册模式": str(len(self._mode_registry)),
|
||||
"永久模式": str(len(self._permanent_mode_ids)),
|
||||
"数据表": f"{self.SYSTEM_TABLE}, {self.PLAYER_TABLE}",
|
||||
}
|
||||
|
||||
def collect_command_entries(self) -> Sequence[GuideEntry]:
|
||||
return (
|
||||
{
|
||||
"title": "store",
|
||||
"identifier": "store",
|
||||
"description": "查看系统刷新的商品列表,包含系统和玩家寄售。",
|
||||
"metadata": {"别名": "商店"},
|
||||
},
|
||||
)
|
||||
|
||||
def collect_guide_entries(self) -> Sequence[GuideEntry]:
|
||||
return (
|
||||
{
|
||||
"title": "模式注册",
|
||||
"description": "`register_mode/register_permanent_mode` 将背包物品以指定库存与价格投放至系统表。",
|
||||
},
|
||||
{
|
||||
"title": "刷新机制",
|
||||
"description": "每小时根据 `store_hourly_count` 配置刷新系统库存,同时同步永久模式。",
|
||||
},
|
||||
{
|
||||
"title": "玩家寄售",
|
||||
"description": "`sell_item` 将玩家物品挂售至寄售表,`purchase_item` 支持购买系统或玩家商品。",
|
||||
},
|
||||
)
|
||||
|
||||
@override
|
||||
def dependencies(self) -> List[type]:
|
||||
return [WPSAPI, WPSConfigAPI, WPSBackpackSystem]
|
||||
@@ -960,6 +996,31 @@ class WPSStoreSystem(WPSAPI):
|
||||
|
||||
|
||||
class WPSStoreBuyCommand(WPSAPI):
|
||||
def get_guide_subtitle(self) -> str:
|
||||
return "购买商店及玩家寄售物品"
|
||||
|
||||
def collect_command_entries(self) -> Sequence[GuideEntry]:
|
||||
return (
|
||||
{
|
||||
"title": "buy",
|
||||
"identifier": "buy",
|
||||
"description": "购买系统或玩家寄售商品,数量需为正整数。",
|
||||
"metadata": {"别名": "购买"},
|
||||
},
|
||||
)
|
||||
|
||||
def collect_guide_entries(self) -> Sequence[GuideEntry]:
|
||||
return (
|
||||
{
|
||||
"title": "参数格式",
|
||||
"description": "`购买 <物品名称或ID> <数量>`,内部支持模糊匹配模式名称。",
|
||||
},
|
||||
{
|
||||
"title": "权限校验",
|
||||
"description": "调用 `purchase_item` 时校验库存、积分并自动扣除商品库存。",
|
||||
},
|
||||
)
|
||||
|
||||
@override
|
||||
def dependencies(self) -> list[type]:
|
||||
return [WPSStoreSystem]
|
||||
@@ -1016,6 +1077,31 @@ class WPSStoreBuyCommand(WPSAPI):
|
||||
|
||||
|
||||
class WPSStoreSellCommand(WPSAPI):
|
||||
def get_guide_subtitle(self) -> str:
|
||||
return "挂售物品至商店寄售区"
|
||||
|
||||
def collect_command_entries(self) -> Sequence[GuideEntry]:
|
||||
return (
|
||||
{
|
||||
"title": "sell",
|
||||
"identifier": "sell",
|
||||
"description": "将背包物品以指定数量和单价挂售。",
|
||||
"metadata": {"别名": "出售"},
|
||||
},
|
||||
)
|
||||
|
||||
def collect_guide_entries(self) -> Sequence[GuideEntry]:
|
||||
return (
|
||||
{
|
||||
"title": "参数格式",
|
||||
"description": "`出售 <物品名称或ID> <数量> <单价>`。",
|
||||
},
|
||||
{
|
||||
"title": "寄售生命周期",
|
||||
"description": "寄售记录写入玩家表,状态变更后定期清理无效记录。",
|
||||
},
|
||||
)
|
||||
|
||||
@override
|
||||
def dependencies(self) -> list[type]:
|
||||
return [WPSStoreSystem]
|
||||
|
||||
Reference in New Issue
Block a user