add emoji to frontend
This commit is contained in:
@@ -69,6 +69,8 @@ class Action(ABC):
|
||||
"""
|
||||
return str(self.__class__.__name__)
|
||||
|
||||
EMOJI: str = ""
|
||||
|
||||
def get_save_data(self) -> dict:
|
||||
"""获取需要存档的运行时数据"""
|
||||
return {}
|
||||
|
||||
@@ -19,6 +19,7 @@ if TYPE_CHECKING:
|
||||
@cooldown_action
|
||||
class Assassinate(InstantAction):
|
||||
ACTION_NAME = "暗杀"
|
||||
EMOJI = "🗡️"
|
||||
DESC = "暗杀目标,失败则变为战斗"
|
||||
DOABLES_REQUIREMENTS = "无限制;需要冷却"
|
||||
PARAMS = {"avatar_name": "AvatarName"}
|
||||
|
||||
@@ -12,6 +12,7 @@ from src.classes.kill_and_grab import kill_and_grab
|
||||
|
||||
class Attack(InstantAction):
|
||||
ACTION_NAME = "发起战斗"
|
||||
EMOJI = "⚔️"
|
||||
DESC = "攻击目标,进行对战"
|
||||
DOABLES_REQUIREMENTS = "无限制"
|
||||
PARAMS = {"avatar_name": "AvatarName"}
|
||||
|
||||
@@ -26,6 +26,7 @@ class Breakthrough(TimedAction):
|
||||
"""
|
||||
|
||||
ACTION_NAME = "突破"
|
||||
EMOJI = "⚡"
|
||||
DESC = "尝试突破境界(成功增加寿元上限,失败折损寿元上限;境界越高,成功率越低。)"
|
||||
DOABLES_REQUIREMENTS = "角色处于瓶颈时;不能连续执行"
|
||||
PARAMS = {}
|
||||
|
||||
@@ -24,6 +24,7 @@ class Catch(TimedAction):
|
||||
"""
|
||||
|
||||
ACTION_NAME = "御兽"
|
||||
EMOJI = "🕸️"
|
||||
DESC = "尝试驯服一只灵兽,成为自身灵兽。只能有一只灵兽,但是可以高级替换低级。"
|
||||
DOABLES_REQUIREMENTS = "仅百兽宗;在有动物的普通区域;目标动物境界不高于角色"
|
||||
PARAMS = {}
|
||||
|
||||
@@ -12,6 +12,7 @@ class Cultivate(TimedAction):
|
||||
"""
|
||||
|
||||
ACTION_NAME = "修炼"
|
||||
EMOJI = "🧘"
|
||||
DESC = "修炼,增进修为。在修炼区域(洞府)且灵气匹配时效果最佳,否则效果很差。"
|
||||
DOABLES_REQUIREMENTS = "角色未到瓶颈;若在洞府区域,则该洞府需无主或归自己所有。"
|
||||
PARAMS = {}
|
||||
|
||||
@@ -11,6 +11,7 @@ class DevourMortals(TimedAction):
|
||||
"""
|
||||
|
||||
ACTION_NAME = "吞噬凡人"
|
||||
EMOJI = "🩸"
|
||||
DESC = "吞噬凡人,较多增加战力"
|
||||
DOABLES_REQUIREMENTS = "持有万魂幡"
|
||||
PARAMS = {}
|
||||
|
||||
@@ -15,6 +15,7 @@ class Escape(InstantAction):
|
||||
"""
|
||||
|
||||
ACTION_NAME = "逃离"
|
||||
EMOJI = "💨"
|
||||
DESC = "逃离对方(基于成功率判定)"
|
||||
DOABLES_REQUIREMENTS = "无限制"
|
||||
PARAMS = {"avatar_name": "AvatarName"}
|
||||
|
||||
@@ -13,6 +13,7 @@ class Harvest(TimedAction):
|
||||
"""
|
||||
|
||||
ACTION_NAME = "采集"
|
||||
EMOJI = "🌾"
|
||||
DESC = "在当前区域采集植物,获取植物材料"
|
||||
DOABLES_REQUIREMENTS = "在有植物的普通区域,且avatar的境界必须大于等于植物的境界"
|
||||
PARAMS = {}
|
||||
|
||||
@@ -13,6 +13,7 @@ class HelpMortals(TimedAction):
|
||||
"""
|
||||
|
||||
ACTION_NAME = "帮助凡人"
|
||||
EMOJI = "🤝"
|
||||
DESC = "在城镇帮助凡人,消耗少量灵石"
|
||||
DOABLES_REQUIREMENTS = "仅限城市区域,且角色阵营为‘正’,并且灵石足够"
|
||||
PARAMS = {}
|
||||
|
||||
@@ -13,6 +13,7 @@ class Hunt(TimedAction):
|
||||
"""
|
||||
|
||||
ACTION_NAME = "狩猎"
|
||||
EMOJI = "🏹"
|
||||
DESC = "在当前区域狩猎动物,获取动物材料"
|
||||
DOABLES_REQUIREMENTS = "在有动物的普通区域,且avatar的境界必须大于等于动物的境界"
|
||||
PARAMS = {}
|
||||
|
||||
@@ -10,6 +10,7 @@ class Move(DefineAction, ChunkActionMixin):
|
||||
"""
|
||||
|
||||
ACTION_NAME = "移动"
|
||||
EMOJI = "🏃"
|
||||
DESC = "移动到某个相对位置"
|
||||
PARAMS = {"delta_x": "int", "delta_y": "int"}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ class NurtureWeapon(TimedAction):
|
||||
"""
|
||||
|
||||
ACTION_NAME = "温养兵器"
|
||||
EMOJI = "✨"
|
||||
DESC = "温养兵器,增加兵器熟练度"
|
||||
DOABLES_REQUIREMENTS = "无限制"
|
||||
PARAMS = {}
|
||||
|
||||
@@ -10,6 +10,7 @@ class Play(TimedAction):
|
||||
"""
|
||||
|
||||
ACTION_NAME = "消遣"
|
||||
EMOJI = "🪁"
|
||||
DESC = "消遣,放松身心"
|
||||
DOABLES_REQUIREMENTS = "无限制"
|
||||
PARAMS = {}
|
||||
|
||||
@@ -13,6 +13,7 @@ class PlunderMortals(TimedAction):
|
||||
"""
|
||||
|
||||
ACTION_NAME = "搜刮凡人"
|
||||
EMOJI = "💀"
|
||||
DESC = "在城镇搜刮凡人,获取少量灵石"
|
||||
DOABLES_REQUIREMENTS = "仅限城市区域,且角色阵营为‘邪’"
|
||||
PARAMS = {}
|
||||
|
||||
@@ -12,6 +12,7 @@ class SelfHeal(TimedAction):
|
||||
"""
|
||||
|
||||
ACTION_NAME = "疗伤"
|
||||
EMOJI = "💚"
|
||||
DESC = "在宗门总部静养疗伤,回满HP"
|
||||
DOABLES_REQUIREMENTS = "自己是宗门弟子,且位于本宗门总部区域,且当前HP未满"
|
||||
PARAMS = {}
|
||||
|
||||
@@ -14,6 +14,7 @@ class SellItems(InstantAction):
|
||||
"""
|
||||
|
||||
ACTION_NAME = "出售物品"
|
||||
EMOJI = "💰"
|
||||
DESC = "在城镇出售持有的某类物品的全部"
|
||||
DOABLES_REQUIREMENTS = "在城镇且背包非空"
|
||||
PARAMS = {"item_name": "str"}
|
||||
|
||||
@@ -111,6 +111,7 @@ def get_avatar_structured_info(avatar: "Avatar") -> dict:
|
||||
"nickname_reason": avatar.nickname.reason if avatar.nickname else None,
|
||||
"is_dead": avatar.is_dead,
|
||||
"death_info": avatar.death_info,
|
||||
"action_state": f"正在{getattr(avatar.current_action.action, 'ACTION_NAME', '思考')}" if hasattr(avatar, "current_action") and avatar.current_action and getattr(avatar.current_action, "action", None) else "思考"
|
||||
}
|
||||
|
||||
# 1. 特质 (Personas)
|
||||
|
||||
@@ -13,6 +13,7 @@ class MutualAttack(MutualAction):
|
||||
"""攻击另一个NPC"""
|
||||
|
||||
ACTION_NAME = "攻击"
|
||||
EMOJI = "⚔️"
|
||||
DESC = "对目标进行攻击。"
|
||||
DOABLES_REQUIREMENTS = "目标在交互范围内;不能连续执行"
|
||||
PARAMS = {"target_avatar": "AvatarName"}
|
||||
|
||||
@@ -26,6 +26,7 @@ class Conversation(MutualAction):
|
||||
"""
|
||||
|
||||
ACTION_NAME = "交谈"
|
||||
EMOJI = "🗣️"
|
||||
DESC = "与对方进行一段交流对话"
|
||||
DOABLES_REQUIREMENTS = "目标在交互范围内"
|
||||
PARAMS = {"target_avatar": "AvatarName"}
|
||||
|
||||
@@ -13,6 +13,7 @@ class DriveAway(MutualAction):
|
||||
"""驱赶:试图让对方离开当前区域。"""
|
||||
|
||||
ACTION_NAME = "驱赶"
|
||||
EMOJI = "😤"
|
||||
DESC = "以武力威慑对方离开此地。"
|
||||
DOABLES_REQUIREMENTS = "目标在交互范围内;不能连续执行"
|
||||
PARAMS = {"target_avatar": "AvatarName"}
|
||||
|
||||
@@ -26,6 +26,7 @@ class DualCultivation(MutualAction):
|
||||
"""
|
||||
|
||||
ACTION_NAME = "双修"
|
||||
EMOJI = "💕"
|
||||
DESC = "以情入道的双修之术,仅合欢宗弟子可发起,对象可接受或拒绝"
|
||||
DOABLES_REQUIREMENTS = "发起者为合欢宗;目标在交互范围内;不能连续执行"
|
||||
PARAMS = {"target_avatar": "AvatarName"}
|
||||
|
||||
@@ -21,6 +21,7 @@ class GiftSpiritStone(MutualAction):
|
||||
"""
|
||||
|
||||
ACTION_NAME = "赠送灵石"
|
||||
EMOJI = "🎁"
|
||||
DESC = "向对方赠送灵石,一次赠送100灵石"
|
||||
DOABLES_REQUIREMENTS = "发起者至少有100灵石;目标在交互范围内"
|
||||
PARAMS = {"target_avatar": "AvatarName"}
|
||||
|
||||
@@ -25,6 +25,7 @@ class Impart(MutualAction):
|
||||
"""
|
||||
|
||||
ACTION_NAME = "传道"
|
||||
EMOJI = "📖"
|
||||
DESC = "师傅向徒弟传授修炼经验,徒弟可获得大量修为"
|
||||
DOABLES_REQUIREMENTS = "发起者是目标的师傅;师傅等级 > 徒弟等级 + 20;目标在交互范围内;不能连续执行"
|
||||
PARAMS = {"target_avatar": "AvatarName"}
|
||||
|
||||
@@ -31,6 +31,7 @@ class MutualAction(DefineAction, LLMAction, ActualActionMixin, TargetingMixin):
|
||||
"""
|
||||
|
||||
ACTION_NAME: str = "MutualAction"
|
||||
EMOJI: str = "💬"
|
||||
DESC: str = ""
|
||||
DOABLES_REQUIREMENTS: str = "交互范围内可互动"
|
||||
PARAMS: dict = {"target_avatar": "Avatar"}
|
||||
|
||||
@@ -28,6 +28,7 @@ class Occupy(MutualAction):
|
||||
对方拒绝则进入战斗,进攻方胜利则洞府易主。
|
||||
"""
|
||||
ACTION_NAME = "抢夺洞府"
|
||||
EMOJI = "🚩"
|
||||
DESC = "占据或抢夺洞府"
|
||||
PARAMS = {"region_name": "str"}
|
||||
FEEDBACK_ACTIONS = ["Yield", "Reject"]
|
||||
|
||||
@@ -21,6 +21,7 @@ class Spar(MutualAction):
|
||||
切磋动作:双方切磋,不造成伤害,增加武器熟练度。
|
||||
"""
|
||||
ACTION_NAME = "切磋"
|
||||
EMOJI = "🤺"
|
||||
DESC = "与目标切磋武艺,点到为止(大幅增加武器熟练度,不造成伤害)"
|
||||
DOABLES_REQUIREMENTS = "交互范围内可互动;不能连续执行"
|
||||
FEEDBACK_ACTIONS = ["Accept", "Reject"]
|
||||
|
||||
@@ -19,6 +19,7 @@ class Talk(MutualAction):
|
||||
"""
|
||||
|
||||
ACTION_NAME = "攀谈"
|
||||
EMOJI = "👋"
|
||||
DESC = "向对方发起攀谈"
|
||||
DOABLES_REQUIREMENTS = "目标在交互范围内"
|
||||
PARAMS = {"target_avatar": "AvatarName"}
|
||||
|
||||
@@ -99,6 +99,21 @@ def resolve_avatar_pic_id(avatar) -> int:
|
||||
gender_val = getattr(getattr(avatar, "gender", None), "value", "male")
|
||||
return get_avatar_pic_id(str(getattr(avatar, "id", "")), gender_val or "male")
|
||||
|
||||
def resolve_avatar_action_emoji(avatar) -> str:
|
||||
"""获取角色当前动作的 Emoji"""
|
||||
if not avatar:
|
||||
return ""
|
||||
curr = getattr(avatar, "current_action", None)
|
||||
if not curr:
|
||||
return ""
|
||||
|
||||
# ActionInstance.action -> Action 实例
|
||||
act_instance = getattr(curr, "action", None)
|
||||
if not act_instance:
|
||||
return ""
|
||||
|
||||
return getattr(act_instance, "EMOJI", "")
|
||||
|
||||
# 触发配置重载的标记 (technique.csv updated)
|
||||
|
||||
# 简易的命令行参数检查 (不使用 argparse 以避免冲突和时序问题)
|
||||
@@ -380,7 +395,8 @@ async def game_loop():
|
||||
"y": int(getattr(a, "pos_y", 0)),
|
||||
"gender": a.gender.value,
|
||||
"pic_id": resolve_avatar_pic_id(a),
|
||||
"action": getattr(a, "current_action", {}).get("name", "发呆") if hasattr(a, "current_action") and a.current_action else "发呆",
|
||||
"action": getattr(a, "current_action", {}).get("name", "思考") if hasattr(a, "current_action") and a.current_action else "思考",
|
||||
"action_emoji": resolve_avatar_action_emoji(a),
|
||||
"is_dead": False
|
||||
})
|
||||
|
||||
@@ -409,7 +425,8 @@ async def game_loop():
|
||||
avatar_updates.append({
|
||||
"id": str(a.id),
|
||||
"x": int(getattr(a, "pos_x", 0)),
|
||||
"y": int(getattr(a, "pos_y", 0))
|
||||
"y": int(getattr(a, "pos_y", 0)),
|
||||
"action_emoji": resolve_avatar_action_emoji(a)
|
||||
})
|
||||
count += 1
|
||||
|
||||
@@ -597,6 +614,7 @@ def get_state():
|
||||
"x": ax,
|
||||
"y": ay,
|
||||
"action": str(aaction),
|
||||
"action_emoji": resolve_avatar_action_emoji(a),
|
||||
"gender": str(a.gender.value),
|
||||
"pic_id": resolve_avatar_pic_id(a)
|
||||
})
|
||||
|
||||
@@ -55,8 +55,15 @@ useSharedTicker((delta) => {
|
||||
} else {
|
||||
currentY.value = destY
|
||||
}
|
||||
|
||||
// Emoji bobbing animation
|
||||
emojiTime += delta * 0.05
|
||||
emojiBob.value = Math.sin(emojiTime) * 5
|
||||
})
|
||||
|
||||
let emojiTime = 0
|
||||
const emojiBob = ref(0)
|
||||
|
||||
function getTexture() {
|
||||
const gender = (props.avatar.gender || 'male').toLowerCase()
|
||||
let pid = props.avatar.pic_id
|
||||
@@ -117,6 +124,57 @@ function handlePointerTap() {
|
||||
name: props.avatar.name
|
||||
})
|
||||
}
|
||||
|
||||
const emojiStyle = {
|
||||
fontFamily: '"Segoe UI Emoji", "Apple Color Emoji", "Noto Color Emoji", sans-serif',
|
||||
fontSize: 70,
|
||||
align: 'center',
|
||||
} as any
|
||||
|
||||
const drawEmojiBg = (g: Graphics) => {
|
||||
g.clear()
|
||||
|
||||
const w = 80
|
||||
const h = 80
|
||||
const r = 16
|
||||
const halfW = w / 2
|
||||
const halfH = h / 2
|
||||
|
||||
// 1. Draw all fills first (to cover background)
|
||||
g.beginPath()
|
||||
g.roundRect(-halfW, -halfH, w, h, r)
|
||||
g.fill({ color: 0xffffff, alpha: 1.0 })
|
||||
|
||||
// Tail fill
|
||||
g.beginPath()
|
||||
g.moveTo(-halfW + 10, halfH) // Start at bottom-left area of body
|
||||
g.lineTo(-halfW - 10, halfH + 20) // Point pointing down-left
|
||||
g.lineTo(-halfW, halfH - 10) // Back to left edge of body
|
||||
g.closePath()
|
||||
g.fill({ color: 0xffffff, alpha: 1.0 })
|
||||
|
||||
// 2. Draw Strokes (Outlines)
|
||||
// We draw the bubble body stroke
|
||||
g.roundRect(-halfW, -halfH, w, h, r)
|
||||
g.stroke({ width: 3, color: 0x000000, alpha: 1.0 })
|
||||
|
||||
// We draw the tail stroke
|
||||
g.beginPath()
|
||||
g.moveTo(-halfW + 10, halfH)
|
||||
g.lineTo(-halfW - 10, halfH + 20)
|
||||
g.lineTo(-halfW, halfH - 10)
|
||||
g.stroke({ width: 3, color: 0x000000, alpha: 1.0 })
|
||||
|
||||
// 3. Clean up the intersection with a white patch
|
||||
// We fill a small polygon over the line where tail meets body
|
||||
g.beginPath()
|
||||
g.moveTo(-halfW + 8, halfH - 2) // Inside body, near bottom
|
||||
g.lineTo(-halfW - 2, halfH - 12) // Inside body, near left
|
||||
g.lineTo(-halfW - 8, halfH + 16) // Towards tail tip (but not all the way)
|
||||
g.lineTo(-halfW + 8, halfH + 2) // Towards tail base
|
||||
g.closePath()
|
||||
g.fill({ color: 0xffffff, alpha: 1.0 })
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -141,6 +199,22 @@ function handlePointerTap() {
|
||||
@render="drawFallback"
|
||||
/>
|
||||
|
||||
<!-- Emoji Bubble -->
|
||||
<container
|
||||
v-if="avatar.action_emoji"
|
||||
:x="tileSize * 0.6"
|
||||
:y="(getTexture() ? -tileSize * 3.5 : -tileSize * 1.2) + emojiBob"
|
||||
:z-index="100"
|
||||
>
|
||||
<graphics @render="drawEmojiBg" />
|
||||
<text
|
||||
:text="avatar.action_emoji"
|
||||
:style="emojiStyle"
|
||||
:anchor="0.5"
|
||||
:scale="1.0"
|
||||
/>
|
||||
</container>
|
||||
|
||||
<text
|
||||
:text="avatar.name"
|
||||
:style="nameStyle"
|
||||
|
||||
@@ -76,6 +76,11 @@ async function handleClearObjective() {
|
||||
</div>
|
||||
|
||||
<div class="content-scroll">
|
||||
<!-- Action State Banner -->
|
||||
<div v-if="!data.is_dead && data.action_state" class="action-banner">
|
||||
{{ data.action_state }}
|
||||
</div>
|
||||
|
||||
<!-- Stats Grid -->
|
||||
<div class="stats-grid">
|
||||
<StatItem label="境界" :value="data.realm" :sub-value="data.level" />
|
||||
@@ -226,6 +231,17 @@ async function handleClearObjective() {
|
||||
border: 1px solid #7a2a2a;
|
||||
}
|
||||
|
||||
.action-banner {
|
||||
background: rgba(23, 125, 220, 0.15);
|
||||
color: #aaddff;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
font-size: 13px;
|
||||
margin-bottom: 8px;
|
||||
border: 1px solid rgba(23, 125, 220, 0.3);
|
||||
}
|
||||
|
||||
.content-scroll {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
|
||||
@@ -36,6 +36,7 @@ export interface Item extends EffectEntity {
|
||||
|
||||
export interface AvatarSummary extends EntityBase, Coordinates {
|
||||
action?: string;
|
||||
action_emoji?: string;
|
||||
gender?: string;
|
||||
pic_id?: number;
|
||||
is_dead?: boolean;
|
||||
@@ -49,6 +50,7 @@ export interface AvatarDetail extends EntityBase {
|
||||
nickname?: string;
|
||||
appearance: string; // 外貌描述
|
||||
is_dead?: boolean;
|
||||
action_state?: string; // 当前正在进行的动作描述
|
||||
death_info?: {
|
||||
time: number;
|
||||
reason: string;
|
||||
|
||||
Reference in New Issue
Block a user