diff --git a/src/classes/action/battle.py b/src/classes/action/battle.py index a59374e..9af7067 100644 --- a/src/classes/action/battle.py +++ b/src/classes/action/battle.py @@ -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] - - diff --git a/src/classes/action/breakthrough.py b/src/classes/action/breakthrough.py index c85e3a1..b796f38 100644 --- a/src/classes/action/breakthrough.py +++ b/src/classes/action/breakthrough.py @@ -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 - - diff --git a/src/classes/fortune.py b/src/classes/fortune.py index 0b40ff8..b8f62c0 100644 --- a/src/classes/fortune.py +++ b/src/classes/fortune.py @@ -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", ] - - diff --git a/src/classes/mutual_action/conversation.py b/src/classes/mutual_action/conversation.py index 1f9a044..2a2c257 100644 --- a/src/classes/mutual_action/conversation.py +++ b/src/classes/mutual_action/conversation.py @@ -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=[]) diff --git a/src/classes/mutual_action/dual_cultivation.py b/src/classes/mutual_action/dual_cultivation.py index 142d91b..b5161bf 100644 --- a/src/classes/mutual_action/dual_cultivation.py +++ b/src/classes/mutual_action/dual_cultivation.py @@ -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 - - diff --git a/src/classes/mutual_action/gift_spirit_stone.py b/src/classes/mutual_action/gift_spirit_stone.py index 8dc203b..a8e42ff 100644 --- a/src/classes/mutual_action/gift_spirit_stone.py +++ b/src/classes/mutual_action/gift_spirit_stone.py @@ -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 diff --git a/src/classes/mutual_action/impart.py b/src/classes/mutual_action/impart.py index fedbe34..59bb2c9 100644 --- a/src/classes/mutual_action/impart.py +++ b/src/classes/mutual_action/impart.py @@ -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, diff --git a/src/classes/relations.py b/src/classes/relations.py index cc3a336..4325534 100644 --- a/src/classes/relations.py +++ b/src/classes/relations.py @@ -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) diff --git a/src/classes/story_teller.py b/src/classes/story_teller.py index 8dffc13..f9ae5d5 100644 --- a/src/classes/story_teller.py +++ b/src/classes/story_teller.py @@ -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,45 +60,82 @@ 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) - story = data.get("story", "").strip() - if story: - return story - except Exception: - pass + # 只有当允许关系变化且有至少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 return StoryTeller._make_fallback_story(event, res, infos["style"]) -__all__ = ["StoryTeller"] \ No newline at end of file +__all__ = ["StoryTeller"] diff --git a/static/templates/conversation.txt b/static/templates/conversation.txt index 3b15b77..5646c3f 100644 --- a/static/templates/conversation.txt +++ b/static/templates/conversation.txt @@ -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}的身份。 }} -}} \ No newline at end of file +}} diff --git a/static/templates/story_dual.txt b/static/templates/story_dual.txt new file mode 100644 index 0000000..ec8208d --- /dev/null +++ b/static/templates/story_dual.txt @@ -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}的身份。 +}} + diff --git a/static/templates/story.txt b/static/templates/story_single.txt similarity index 75% rename from static/templates/story.txt rename to static/templates/story_single.txt index 785f5d7..49303d5 100644 --- a/static/templates/story.txt +++ b/static/templates/story_single.txt @@ -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": "", // 第三人称的故事正文,仙侠语言风格 -}} \ No newline at end of file + "thinking": ..., // 简单思考故事剧情 + "story": "" // 第三人称的故事正文,仙侠语言风格 +}} + diff --git a/tools/img_gen/tile_prompts.py b/tools/img_gen/tile_prompts.py new file mode 100644 index 0000000..8817f77 --- /dev/null +++ b/tools/img_gen/tile_prompts.py @@ -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()