refactor relationship changes
This commit is contained in:
@@ -90,9 +90,8 @@ class Battle(InstantAction):
|
||||
# 生成战斗小故事
|
||||
target = self._get_target(avatar_name)
|
||||
start_text = self._start_event_content if hasattr(self, '_start_event_content') else result_event.content
|
||||
story = await StoryTeller.tell_story(start_text, result_event.content, self.avatar, target, prompt=self.STORY_PROMPT)
|
||||
# 战斗强制双人模式,允许改变关系
|
||||
story = await StoryTeller.tell_story(start_text, result_event.content, self.avatar, target, prompt=self.STORY_PROMPT, allow_relation_changes=True)
|
||||
story_event = Event(self.world.month_stamp, story, related_avatars=rel_ids, is_story=True)
|
||||
|
||||
return [result_event, story_event]
|
||||
|
||||
|
||||
|
||||
@@ -135,8 +135,7 @@ class Breakthrough(TimedAction):
|
||||
|
||||
# 故事参与者:本体 +(可选)相关角色
|
||||
prompt = TribulationSelector.get_story_prompt(str(calamity))
|
||||
story = await StoryTeller.tell_story(core_text, ("突破成功" if result_ok else "突破失败"), self.avatar, self._calamity_other, prompt=prompt)
|
||||
# 突破强制单人模式,不改变关系(因为没有双修/战斗那样的互动)
|
||||
story = await StoryTeller.tell_story(core_text, ("突破成功" if result_ok else "突破失败"), self.avatar, self._calamity_other, prompt=prompt, allow_relation_changes=False)
|
||||
events.append(Event(self.world.month_stamp, story, related_avatars=rel_ids, is_story=True))
|
||||
return events
|
||||
|
||||
|
||||
|
||||
@@ -478,7 +478,8 @@ async def try_trigger_fortune(avatar: Avatar) -> list[Event]:
|
||||
base_event = Event(month_at_finish, event_text, related_avatars=related_avatars, is_major=True)
|
||||
|
||||
# 生成故事事件
|
||||
story = await StoryTeller.tell_story(event_text, res_text, *actors_for_story, prompt=story_prompt)
|
||||
# 奇遇强制单人模式,不改变关系(因为关系已经在硬逻辑中处理了)
|
||||
story = await StoryTeller.tell_story(event_text, res_text, *actors_for_story, prompt=story_prompt, allow_relation_changes=False)
|
||||
story_event = Event(month_at_finish, story, related_avatars=related_avatars, is_story=True)
|
||||
|
||||
# 返回基础事件和故事事件
|
||||
@@ -488,5 +489,3 @@ async def try_trigger_fortune(avatar: Avatar) -> list[Event]:
|
||||
__all__ = [
|
||||
"try_trigger_fortune",
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -4,12 +4,9 @@ from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .mutual_action import MutualAction
|
||||
from src.classes.relation import relation_display_names, Relation
|
||||
from src.classes.relations import (
|
||||
get_possible_new_relations,
|
||||
get_possible_cancel_relations,
|
||||
set_relation,
|
||||
cancel_relation,
|
||||
process_relation_changes,
|
||||
get_relation_change_context,
|
||||
)
|
||||
from src.classes.event import Event, NULL_EVENT
|
||||
from src.utils.config import CONFIG
|
||||
@@ -52,18 +49,15 @@ class Conversation(MutualAction):
|
||||
avatar_name_2: target_avatar.get_info(detailed=True),
|
||||
}
|
||||
|
||||
# 可能的后天关系(转中文名,给模板阅读)
|
||||
# 注意:这里计算的是 target 相对于 avatar 的可能关系
|
||||
possible_new_relations = [relation_display_names[r] for r in get_possible_new_relations(self.avatar, target_avatar)]
|
||||
# 可能取消的关系
|
||||
possible_cancel_relations = [relation_display_names[r] for r in get_possible_cancel_relations(target_avatar, self.avatar)]
|
||||
# 获取关系上下文
|
||||
possible_new_relations, possible_cancel_relations = get_relation_change_context(self.avatar, target_avatar)
|
||||
|
||||
return {
|
||||
"avatar_infos": avatar_infos,
|
||||
"avatar_name_1": avatar_name_1,
|
||||
"avatar_name_2": avatar_name_2,
|
||||
"possible_new_relations": possible_new_relations,
|
||||
"possible_cancal_relations": possible_cancel_relations, # 保持模板中的拼写
|
||||
"possible_cancel_relations": possible_cancel_relations,
|
||||
}
|
||||
|
||||
def _can_start(self, target: "Avatar") -> tuple[bool, str]:
|
||||
@@ -86,8 +80,6 @@ class Conversation(MutualAction):
|
||||
Conversation 不需要反馈(FEEDBACK_ACTIONS 为空),直接生成内容。
|
||||
"""
|
||||
conversation_content = str(result.get("conversation_content", "")).strip()
|
||||
new_relation_str = str(result.get("new_relation", "")).strip()
|
||||
cancel_relation_str = str(result.get("cancal_relation", "")).strip() # 保持模板中的拼写
|
||||
|
||||
# 使用开始时间戳
|
||||
month_stamp = self._start_month_stamp if self._start_month_stamp is not None else self.world.month_stamp
|
||||
@@ -101,32 +93,8 @@ class Conversation(MutualAction):
|
||||
)
|
||||
EventHelper.push_pair(content_event, initiator=self.avatar, target=target, to_sidebar_once=True)
|
||||
|
||||
# 处理进入新关系
|
||||
if new_relation_str:
|
||||
rel = Relation.from_chinese(new_relation_str)
|
||||
if rel is not None:
|
||||
set_relation(target, self.avatar, rel)
|
||||
set_event = Event(
|
||||
month_stamp,
|
||||
f"{target.name} 与 {self.avatar.name} 的关系变为:{relation_display_names.get(rel, str(rel))}",
|
||||
related_avatars=[self.avatar.id, target.id],
|
||||
is_major=True
|
||||
)
|
||||
EventHelper.push_pair(set_event, initiator=self.avatar, target=target, to_sidebar_once=True)
|
||||
|
||||
# 处理取消关系
|
||||
if cancel_relation_str:
|
||||
rel = Relation.from_chinese(cancel_relation_str)
|
||||
if rel is not None:
|
||||
success = cancel_relation(target, self.avatar, rel)
|
||||
if success:
|
||||
cancel_event = Event(
|
||||
month_stamp,
|
||||
f"{target.name} 与 {self.avatar.name} 取消了关系:{relation_display_names.get(rel, str(rel))}",
|
||||
related_avatars=[self.avatar.id, target.id],
|
||||
is_major=True
|
||||
)
|
||||
EventHelper.push_pair(cancel_event, initiator=self.avatar, target=target, to_sidebar_once=True)
|
||||
# 处理关系变化 (调用通用逻辑)
|
||||
process_relation_changes(self.avatar, target, result, month_stamp)
|
||||
|
||||
return ActionResult(status=ActionStatus.COMPLETED, events=[])
|
||||
|
||||
|
||||
@@ -105,20 +105,15 @@ class DualCultivation(MutualAction):
|
||||
|
||||
if success:
|
||||
gain = int(self._dual_exp_gain)
|
||||
result_text = f"{self.avatar.name} 与 {target.name} 成功双修,{self.avatar.name} 获得修为经验 +{gain} 点"
|
||||
result_text = f"{self.avatar.name} 获得修为经验 +{gain} 点"
|
||||
result_event = Event(self.world.month_stamp, result_text, related_avatars=[self.avatar.id, target.id], is_major=True)
|
||||
events.append(result_event)
|
||||
|
||||
# 生成恋爱/双修小故事
|
||||
start_text = self._start_event_content or result_event.content
|
||||
story = await StoryTeller.tell_story(start_text, result_event.content, self.avatar, target, prompt=self.STORY_PROMPT)
|
||||
# 双修强制双人模式,允许改变关系
|
||||
story = await StoryTeller.tell_story(start_text, result_event.content, self.avatar, target, prompt=self.STORY_PROMPT, allow_relation_changes=True)
|
||||
story_event = Event(self.world.month_stamp, story, related_avatars=[self.avatar.id, target.id], is_story=True)
|
||||
events.append(story_event)
|
||||
else:
|
||||
result_text = f"{target.name} 拒绝了与 {self.avatar.name} 的双修"
|
||||
result_event = Event(self.world.month_stamp, result_text, related_avatars=[self.avatar.id, target.id], is_major=True)
|
||||
events.append(result_event)
|
||||
|
||||
return events
|
||||
|
||||
|
||||
|
||||
@@ -88,21 +88,14 @@ class GiftSpiritStone(MutualAction):
|
||||
return events
|
||||
|
||||
if success:
|
||||
result_text = f"{self.avatar.name} 赠送了 {self.GIFT_AMOUNT} 灵石给 {target.name}({self.avatar.name} 灵石:{self.avatar.magic_stone + self.GIFT_AMOUNT} → {self.avatar.magic_stone},{target.name} 灵石:{target.magic_stone - self.GIFT_AMOUNT} → {target.magic_stone})"
|
||||
result_text = f"{self.avatar.name} 赠送了 {self.GIFT_AMOUNT} 灵石给 {target.name}({self.avatar.
|
||||
name} 灵石:{self.avatar.magic_stone + self.GIFT_AMOUNT} → {self.avatar.magic_stone},{target.
|
||||
name} 灵石:{target.magic_stone - self.GIFT_AMOUNT} → {target.magic_stone})"
|
||||
result_event = Event(
|
||||
self.world.month_stamp,
|
||||
result_text,
|
||||
related_avatars=[self.avatar.id, target.id]
|
||||
)
|
||||
events.append(result_event)
|
||||
else:
|
||||
result_text = f"{target.name} 婉拒了 {self.avatar.name} 的灵石赠送"
|
||||
result_event = Event(
|
||||
self.world.month_stamp,
|
||||
result_text,
|
||||
related_avatars=[self.avatar.id, target.id]
|
||||
)
|
||||
events.append(result_event)
|
||||
|
||||
return events
|
||||
|
||||
|
||||
@@ -100,15 +100,7 @@ class Impart(MutualAction):
|
||||
|
||||
if success:
|
||||
gain = int(self._impart_exp_gain)
|
||||
result_text = f"{self.avatar.name} 向 {target.name} 传道,{target.name} 获得修为经验 +{gain} 点"
|
||||
result_event = Event(
|
||||
self.world.month_stamp,
|
||||
result_text,
|
||||
related_avatars=[self.avatar.id, target.id]
|
||||
)
|
||||
events.append(result_event)
|
||||
else:
|
||||
result_text = f"{target.name} 婉拒了 {self.avatar.name} 的传道"
|
||||
result_text = f"{target.name} 获得修为经验 +{gain} 点"
|
||||
result_event = Event(
|
||||
self.world.month_stamp,
|
||||
result_text,
|
||||
|
||||
@@ -5,7 +5,9 @@ from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, List
|
||||
|
||||
from src.classes.relation import Relation, INNATE_RELATIONS, get_reciprocal, is_innate
|
||||
from src.classes.relation import Relation, INNATE_RELATIONS, get_reciprocal, is_innate, relation_display_names
|
||||
from src.classes.event import Event
|
||||
from src.classes.action.event_helper import EventHelper
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from src.classes.avatar import Avatar
|
||||
@@ -124,3 +126,58 @@ def get_possible_cancel_relations(from_avatar: "Avatar", to_avatar: "Avatar") ->
|
||||
|
||||
return [existing]
|
||||
|
||||
|
||||
def get_relation_change_context(avatar1: "Avatar", avatar2: "Avatar") -> tuple[list[str], list[str]]:
|
||||
"""
|
||||
获取两角色间可能的新增关系和取消关系的中文显示列表。
|
||||
用于构建 Prompt 上下文。
|
||||
|
||||
返回:(possible_new_relations, possible_cancel_relations)
|
||||
"""
|
||||
# 计算 avatar2 相对于 avatar1 的可能关系
|
||||
new_rels = get_possible_new_relations(avatar1, avatar2)
|
||||
cancel_rels = get_possible_cancel_relations(avatar1, avatar2)
|
||||
|
||||
new_strs = [relation_display_names[r] for r in new_rels]
|
||||
cancel_strs = [relation_display_names[r] for r in cancel_rels]
|
||||
|
||||
return new_strs, cancel_strs
|
||||
|
||||
|
||||
def process_relation_changes(initiator: "Avatar", target: "Avatar", result_dict: dict, month_stamp: int) -> None:
|
||||
"""
|
||||
处理 LLM 返回的关系变更请求。
|
||||
兼容 Conversation 和 StoryTeller 的通用逻辑。
|
||||
"""
|
||||
new_relation_str = str(result_dict.get("new_relation", "")).strip()
|
||||
# 兼容模板中的拼写错误 (cancal -> cancel)
|
||||
cancel_relation_str = str(result_dict.get("cancel_relation", "")).strip()
|
||||
if not cancel_relation_str:
|
||||
cancel_relation_str = str(result_dict.get("cancal_relation", "")).strip()
|
||||
|
||||
# 处理进入新关系
|
||||
if new_relation_str:
|
||||
rel = Relation.from_chinese(new_relation_str)
|
||||
if rel is not None:
|
||||
set_relation(target, initiator, rel)
|
||||
set_event = Event(
|
||||
month_stamp,
|
||||
f"{target.name} 与 {initiator.name} 的关系变为:{relation_display_names.get(rel, str(rel))}",
|
||||
related_avatars=[initiator.id, target.id],
|
||||
is_major=True
|
||||
)
|
||||
EventHelper.push_pair(set_event, initiator=initiator, target=target, to_sidebar_once=True)
|
||||
|
||||
# 处理取消关系
|
||||
if cancel_relation_str:
|
||||
rel = Relation.from_chinese(cancel_relation_str)
|
||||
if rel is not None:
|
||||
success = cancel_relation(target, initiator, rel)
|
||||
if success:
|
||||
cancel_event = Event(
|
||||
month_stamp,
|
||||
f"{target.name} 与 {initiator.name} 取消了关系:{relation_display_names.get(rel, str(rel))}",
|
||||
related_avatars=[initiator.id, target.id],
|
||||
is_major=True
|
||||
)
|
||||
EventHelper.push_pair(cancel_event, initiator=initiator, target=target, to_sidebar_once=True)
|
||||
|
||||
@@ -8,6 +8,10 @@ if TYPE_CHECKING:
|
||||
|
||||
from src.utils.config import CONFIG
|
||||
from src.utils.llm import call_llm_with_template, LLMMode
|
||||
from src.classes.relations import (
|
||||
process_relation_changes,
|
||||
get_relation_change_context
|
||||
)
|
||||
|
||||
story_styles = [
|
||||
"平淡叙述:语句克制、少修饰、像旁观者记录。",
|
||||
@@ -31,9 +35,11 @@ story_styles = [
|
||||
class StoryTeller:
|
||||
"""
|
||||
故事生成器:基于模板与 LLM,将给定事件扩展为简短的小故事。
|
||||
同时负责处理可能的后天关系变化。
|
||||
"""
|
||||
|
||||
TEMPLATE_PATH = CONFIG.paths.templates / "story.txt"
|
||||
TEMPLATE_SINGLE_PATH = CONFIG.paths.templates / "story_single.txt"
|
||||
TEMPLATE_DUAL_PATH = CONFIG.paths.templates / "story_dual.txt"
|
||||
|
||||
@staticmethod
|
||||
def _build_avatar_infos(*actors: "Avatar") -> Dict[str, dict]:
|
||||
@@ -54,43 +60,80 @@ class StoryTeller:
|
||||
return avatar_infos
|
||||
|
||||
@staticmethod
|
||||
def _build_template_data(event: str, res: str, avatar_infos: Dict[str, dict], prompt: str) -> dict:
|
||||
def _build_template_data(event: str, res: str, avatar_infos: Dict[str, dict], prompt: str, *actors: "Avatar") -> dict:
|
||||
"""构建模板渲染所需的数据字典"""
|
||||
|
||||
# 默认空关系列表
|
||||
possible_new_relations = []
|
||||
possible_cancel_relations = []
|
||||
avatar_name_1 = ""
|
||||
avatar_name_2 = ""
|
||||
|
||||
# 如果有两个有效角色,计算可能的关系
|
||||
non_null = [a for a in actors if a is not None]
|
||||
if len(non_null) >= 2:
|
||||
# 计算 actors[1] 相对于 actors[0] 的可能关系
|
||||
possible_new_relations, possible_cancel_relations = get_relation_change_context(non_null[0], non_null[1])
|
||||
avatar_name_1 = non_null[0].name
|
||||
avatar_name_2 = non_null[1].name
|
||||
|
||||
return {
|
||||
"avatar_infos": avatar_infos,
|
||||
"avatar_name_1": avatar_name_1,
|
||||
"avatar_name_2": avatar_name_2,
|
||||
"event": event,
|
||||
"res": res,
|
||||
"style": random.choice(story_styles),
|
||||
"story_prompt": prompt,
|
||||
"possible_new_relations": possible_new_relations,
|
||||
"possible_cancel_relations": possible_cancel_relations,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _make_fallback_story(event: str, res: str, style: str) -> str:
|
||||
"""生成降级文案"""
|
||||
return f"{event}。{res}。{style}"
|
||||
# 不再显示 style,避免出戏
|
||||
return f"{event}。{res}。"
|
||||
|
||||
@staticmethod
|
||||
async def tell_story(event: str, res: str, *actors: "Avatar", prompt: str = "") -> str:
|
||||
async def tell_story(event: str, res: str, *actors: "Avatar", prompt: str = "", allow_relation_changes: bool = False) -> str:
|
||||
"""
|
||||
生成小故事(异步版本)。
|
||||
基于 `static/templates/story.txt` 模板,失败时返回降级文案。
|
||||
根据 allow_relation_changes 参数选择模板:
|
||||
- True: 使用 story_dual.txt,支持关系变化(需要至少2个角色)
|
||||
- False: 使用 story_single.txt,仅生成故事(无论角色数量)
|
||||
|
||||
Args:
|
||||
event: 事件描述
|
||||
res: 结果描述
|
||||
*actors: 参与的角色(1-2个)
|
||||
prompt: 可选的故事提示词
|
||||
allow_relation_changes: 是否允许故事导致关系变化,默认为False(单人模式)
|
||||
"""
|
||||
avatar_infos = StoryTeller._build_avatar_infos(*actors)
|
||||
infos = StoryTeller._build_template_data(event, res, avatar_infos, prompt)
|
||||
non_null = [a for a in actors if a is not None]
|
||||
|
||||
try:
|
||||
data = await call_llm_with_template(StoryTeller.TEMPLATE_PATH, infos, LLMMode.FAST)
|
||||
# 只有当允许关系变化且有至少2个角色时,才使用双人模板
|
||||
is_dual = allow_relation_changes and len(non_null) >= 2
|
||||
|
||||
template_path = StoryTeller.TEMPLATE_DUAL_PATH if is_dual else StoryTeller.TEMPLATE_SINGLE_PATH
|
||||
|
||||
avatar_infos = StoryTeller._build_avatar_infos(*actors)
|
||||
infos = StoryTeller._build_template_data(event, res, avatar_infos, prompt, *actors)
|
||||
|
||||
# 移除了 try-except 块,允许异常向上冒泡,以便 Fail Fast
|
||||
data = await call_llm_with_template(template_path, infos, LLMMode.FAST)
|
||||
story = data.get("story", "").strip()
|
||||
|
||||
# 仅在双人模式下处理关系变化
|
||||
if is_dual:
|
||||
avatar_1 = non_null[0]
|
||||
avatar_2 = non_null[1]
|
||||
# 尝试获取 month_stamp
|
||||
month_stamp = getattr(avatar_1.world, "month_stamp", 0)
|
||||
process_relation_changes(avatar_1, avatar_2, data, month_stamp)
|
||||
|
||||
if story:
|
||||
return story
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return StoryTeller._make_fallback_story(event, res, infos["style"])
|
||||
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
你是一个决策者,这是一个仙侠世界,你负责来生成两个NPC间的对话内容。
|
||||
你是一个决策者,这是一个仙侠世界,你负责来生成两个NPC间的对话内容,并决定两人是否会有关系的变化。
|
||||
|
||||
你需要进行决策的NPC的dict[AvatarName, info]为
|
||||
{avatar_infos}
|
||||
|
||||
正在进行的动作为:{avatar_name_1}和{avatar_name_2}正在对话。这个对话可能是善意的,也可能是恶意的,也可能是闲聊。内容和性质取决于NPC特质(性格、天赋等)、正邪、关系等因素。
|
||||
{avatar_name_1}和{avatar_name_2}正在对话。这个对话可能是善意的,也可能是恶意的,也可能是闲聊。内容和性质取决于NPC特质(性格、天赋等)、正邪、关系等因素。
|
||||
|
||||
两者可能进入的关系:{possible_new_relations}
|
||||
两者可能取消的关系:{possible_cancal_relations}
|
||||
两者可能取消的关系:{possible_cancel_relations}
|
||||
注意:进入/取消关系不是必须的,完全由你根据对话情况、双方性格、历史事件等判断决定。
|
||||
|
||||
注意,只返回json格式的结果。
|
||||
格式为:
|
||||
{{
|
||||
"{avatar_name_2}": {{
|
||||
"thinking": ..., // 简单思考对话的情况
|
||||
"thinking": ..., // 简单思考对话如何进行
|
||||
"conversation_content": ... // 对话双方均为第三人称视角的对话,100~150字,仙侠语言风格。可以是聊天也可以是对话概括。
|
||||
"new_relation": ... // 如果你认为可以让两者产生某种身份关系,则返回关系的中文名,否则返回空str。注意这是{avatar_name_2}相对于{avatar_name_1}的身份。
|
||||
"cancal_relation": ... // 可选,如果你认为可以让两者取消某种身份关系,则返回关系的中文名,否则返回空str。注意这是{avatar_name_2}相对于{avatar_name_1}的身份。
|
||||
"cancel_relation": ... // 可选,如果你认为可以让两者取消某种身份关系,则返回关系的中文名,否则返回空str。注意这是{avatar_name_2}相对于{avatar_name_1}的身份。
|
||||
}}
|
||||
}}
|
||||
25
static/templates/story_dual.txt
Normal file
25
static/templates/story_dual.txt
Normal file
@@ -0,0 +1,25 @@
|
||||
你是一个故事讲述者,这是一个仙侠世界,你需要把一个事件扩展为一个约200~250字的小故事,并根据事件发展和双方性格,决定两人是否会有关系的变化。
|
||||
|
||||
你需要进行决策的NPC的dict[AvatarName, info]为
|
||||
{avatar_infos}
|
||||
|
||||
两者可能进入的关系:{possible_new_relations}
|
||||
两者可能取消的关系:{possible_cancel_relations}
|
||||
注意:进入/取消关系不是必须的,完全由你根据故事情况、双方性格、历史事件等判断决定。
|
||||
|
||||
写作风格提示:{style}
|
||||
额外主题提示:{story_prompt}
|
||||
|
||||
发生的事件为:
|
||||
{event}
|
||||
结果为:
|
||||
{res}
|
||||
|
||||
注意,只返回json格式的结果,格式为:
|
||||
{{
|
||||
"thinking": ..., // 简单思考故事剧情和关系变化
|
||||
"story": "", // 第三人称的故事正文,仙侠语言风格
|
||||
"new_relation": ... // 如果你认为可以让两者产生某种身份关系,则返回关系的中文名,否则返回空str。注意这是{avatar_name_2}相对于{avatar_name_1}的身份。
|
||||
"cancel_relation": ... // 可选,如果你认为可以让两者取消某种身份关系,则返回关系的中文名,否则返回空str。注意这是{avatar_name_2}相对于{avatar_name_1}的身份。
|
||||
}}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
你是一个故事讲述者,这是一个仙侠世界,你需要把一个事件扩展为一个约200~250字的小故事。
|
||||
写作风格提示:{style}
|
||||
额外主题提示:{story_prompt}
|
||||
|
||||
你需要进行决策的NPC的dict[AvatarName, info]为
|
||||
{avatar_infos}
|
||||
|
||||
写作风格提示:{style}
|
||||
额外主题提示:{story_prompt}
|
||||
|
||||
发生的事件为:
|
||||
{event}
|
||||
结果为:
|
||||
@@ -12,5 +13,7 @@
|
||||
|
||||
注意,只返回json格式的结果,格式为:
|
||||
{{
|
||||
"story": "", // 第三人称的故事正文,仙侠语言风格
|
||||
"thinking": ..., // 简单思考故事剧情
|
||||
"story": "" // 第三人称的故事正文,仙侠语言风格
|
||||
}}
|
||||
|
||||
159
tools/img_gen/tile_prompts.py
Normal file
159
tools/img_gen/tile_prompts.py
Normal file
@@ -0,0 +1,159 @@
|
||||
# Tile Generation Prompts
|
||||
# Recommended Settings:
|
||||
# - Model: Nano Banana / Any Pixel Art Model
|
||||
# - Resolution: 1024x1024 (Recommended to resize to target size like 96x96 using "Nearest Neighbor" algorithm)
|
||||
# - Sampler: Euler a / DPM++ 2M Karras
|
||||
|
||||
# Common Prefix: Emphasize pixel style, RPG view, grid layout, white background
|
||||
BASE_PROMPT = (
|
||||
"pixel art style, 16-bit rpg game assets, top-down view, "
|
||||
"flat shading, high quality, sharp details, "
|
||||
"white background, 3x3 grid layout, sprite sheet, "
|
||||
"consistent lighting, same color palette"
|
||||
)
|
||||
|
||||
TILE_PROMPTS = {
|
||||
# ==========================================
|
||||
# Category 1: High Frequency Backgrounds
|
||||
# Strategy: Emphasize seamlessness, subtle variations, unified tone, avoid obtrusive objects
|
||||
# ==========================================
|
||||
|
||||
# Plain/Grassland
|
||||
"plain": (
|
||||
f"{BASE_PROMPT}, "
|
||||
"9 variations of green grass ground tiles, simple meadow texture, "
|
||||
"clearly different texture patterns between tiles, clean green lawn, "
|
||||
"some tiles with small stone clusters, some with sparse weeds, some with slightly drier patches, "
|
||||
"seamless pattern style, harmonious but not identical soothing green color"
|
||||
),
|
||||
|
||||
# Desert
|
||||
"desert": (
|
||||
f"{BASE_PROMPT}, "
|
||||
"9 variations of yellow sand ground tiles, desert floor texture, "
|
||||
"smooth sand with clearly different wind ripple directions and density, "
|
||||
"some tiles with small pebble clusters or tiny dried bushes, "
|
||||
"arid atmosphere, golden yellow color with slightly varied brightness, "
|
||||
"uniform texture"
|
||||
),
|
||||
|
||||
# Water/Sea
|
||||
"water": (
|
||||
f"{BASE_PROMPT}, "
|
||||
"9 variations of blue water surface tiles, lake texture, "
|
||||
"calm liquid surface with clearly different ripple shapes and wave crest directions, "
|
||||
"some tiles with gentle foam, some with brighter specular highlights, some almost flat, "
|
||||
"no islands, no rocks, clear blue color, "
|
||||
"consistent water pattern"
|
||||
),
|
||||
|
||||
# Glacier/Snow
|
||||
"glacier": (
|
||||
f"{BASE_PROMPT}, "
|
||||
"9 variations of white snow ground tiles, frozen ground texture, "
|
||||
"clean white snow with clearly different accumulation shapes, "
|
||||
"some tiles with clear shallow footprints, some with cracked ice patterns, subtle blueish tint, "
|
||||
"soft smooth surface, no rocks, seamless winter field"
|
||||
),
|
||||
|
||||
# Swamp
|
||||
"swamp": (
|
||||
f"{BASE_PROMPT}, "
|
||||
"9 variations of murky swamp ground tiles, dark purple and green mud, "
|
||||
"wet muddy ground with clearly different puddle shapes and distributions, "
|
||||
"some tiles with many bubbles, some with floating leaves, some almost only dark mud, "
|
||||
"toxic atmosphere, dark and gloomy color palette, flat texture"
|
||||
),
|
||||
|
||||
# ==========================================
|
||||
# Category 2: Obstacles & Terrain
|
||||
# Strategy: Emphasize independent objects, centered, clear outlines, slight shape variations without changing style
|
||||
# ==========================================
|
||||
|
||||
# Forest - Temperate
|
||||
"forest": (
|
||||
f"{BASE_PROMPT}, "
|
||||
"9 variations of green oak trees, individual map trees, "
|
||||
"isolated trees centered in grid cells, "
|
||||
"strong differences in tree crown shape, height and density, same green foliage color, "
|
||||
"some tiles with one big tree, some with two thinner trees, some with a lower bushy tree, "
|
||||
"compact trees, game asset style, clear outline"
|
||||
),
|
||||
|
||||
# Rainforest - Tropical
|
||||
"rainforest": (
|
||||
f"{BASE_PROMPT}, "
|
||||
"9 variations of jungle palm trees and broadleaf trees, individual trees, "
|
||||
"isolated trees centered in grid cells, "
|
||||
"tropical dark green vibrant colors, strong variation in crown size, leaf shapes and trunk height, "
|
||||
"some tiles with hanging vines, some with extra side leaves, some with double trunks, "
|
||||
"rpg map obstacle, clear outline"
|
||||
),
|
||||
|
||||
# Mountain - Rocky
|
||||
"mountain": (
|
||||
f"{BASE_PROMPT}, "
|
||||
"9 variations of grey rocky mountains, small individual mountain peaks, "
|
||||
"isolated peaks centered in grid cells, "
|
||||
"clearly different peak shapes and slopes, some tall and thin, some low and wide, some double peaks, "
|
||||
"grey stone texture with moss hints, similar overall height, clear outline"
|
||||
),
|
||||
|
||||
# Snow Mountain
|
||||
"snow_mountain": (
|
||||
f"{BASE_PROMPT}, "
|
||||
"9 variations of snow-capped mountains, individual snowy peaks, "
|
||||
"isolated peaks centered in grid cells, "
|
||||
"different amounts of snow and rock exposed, some with wide snow caps, some with more rock showing, "
|
||||
"white snow on top, grey rock at bottom, "
|
||||
"compact shape, cold atmosphere, clear outline"
|
||||
),
|
||||
|
||||
# Volcano
|
||||
"volcano": (
|
||||
f"{BASE_PROMPT}, "
|
||||
"9 variations of active volcanoes, small individual volcano peaks, "
|
||||
"isolated peaks centered in grid cells, "
|
||||
"different crater openings and lava glow intensity, some narrow and tall, some wide and flat, "
|
||||
"some tiles with heavy smoke plumes, some with side lava cracks, "
|
||||
"dark rock with magma crater on top, dangerous red and black colors, clear outline"
|
||||
),
|
||||
|
||||
# City - Icon for world map
|
||||
"city": (
|
||||
f"{BASE_PROMPT}, "
|
||||
"9 variations of small ancient chinese houses, map icon style, "
|
||||
"isolated buildings centered in grid cells, "
|
||||
"clearly different roof shapes and small courtyard layouts, some with one house, some with two connected houses, "
|
||||
"grey tiled roofs, white walls, asian architecture, "
|
||||
"compact simple structure, similar size"
|
||||
),
|
||||
|
||||
# Sect - More magnificent than cities
|
||||
"sect": (
|
||||
f"{BASE_PROMPT}, "
|
||||
"9 variations of mystical chinese fantasy pavilions, map icon style, "
|
||||
"isolated floating palaces or pagodas centered in grid cells, "
|
||||
"strong variation in roof layers and tower heights, some with tall spires, some with wide halls, some with side towers, "
|
||||
"elegant blue and gold roofs, magical aura, "
|
||||
"ethereal atmosphere, clear outline"
|
||||
),
|
||||
|
||||
# Ruins
|
||||
"ruins": (
|
||||
f"{BASE_PROMPT}, "
|
||||
"9 variations of ancient stone ruins, broken pillars and walls, "
|
||||
"isolated debris centered in grid cells, "
|
||||
"clearly different collapse shapes and arrangements, some with standing half pillars, some mostly rubble, some with broken arches, "
|
||||
"weathered grey stone, mossy and cracked, "
|
||||
"mysterious atmosphere, clear outline"
|
||||
)
|
||||
}
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("=== Generated Prompts ===")
|
||||
print(f"Base Prompt: {BASE_PROMPT}\n")
|
||||
for key, prompt in TILE_PROMPTS.items():
|
||||
print(f"--- {key.upper()} ---")
|
||||
print(prompt)
|
||||
print()
|
||||
Reference in New Issue
Block a user