搬运了原版食材 死亡生物 手办 功能实现 小bug 1
This commit is contained in:
1 parent
4f032234c0
commit
213c74cb55
12 files changed
+1304
No files matched your search
@@ -0,0 +1,61 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
================================================================================
|
||||
手办分类转换规则数据
|
||||
================================================================================
|
||||
只处理"手办"(xy_shouban) 分类下的转换:
|
||||
1) 盲盒开盒: 空手右键 盲盒(未开, jewelrybox_0) → 盲盒(开过, jewelrybox_1)
|
||||
+ 随机开出 1 个手办方块(掉落物形式)
|
||||
2) 猫/狐狸手办循环: 空手右键 逐级切换
|
||||
cat_0 → cat_1 → cat_2 → cat_3 → cat_0, fox 同理
|
||||
其余赞助者手办(figurines ~ figurines_5)为静态手办, 无转换。
|
||||
|
||||
【盲盒开出范围】
|
||||
只开"转换链第一个"的猫/狐狸手办(cat_0 / fox_0) + 赞助者手办, 共 8 种均等概率。
|
||||
cat_1~3、fox_1~3 无法从盲盒开出, 只能通过循环转换逐级获得。
|
||||
================================================================================
|
||||
"""
|
||||
|
||||
# ============================================================
|
||||
# 一、盲盒
|
||||
# ============================================================
|
||||
BLIND_BOX_SOURCE = "rtdd:jewelrybox_0" # 盲盒(未开)
|
||||
BLIND_BOX_TARGET = "rtdd:jewelrybox_1" # 盲盒(开过)
|
||||
|
||||
# 盲盒开出内容: 只开链首的猫/狐狸 + 赞助者手办(均等概率)
|
||||
BLIND_BOX_POOL = [
|
||||
"rtdd:cat_0",
|
||||
"rtdd:fox_0",
|
||||
"rtdd:figurines",
|
||||
"rtdd:figurines_1",
|
||||
"rtdd:figurines_2",
|
||||
"rtdd:figurines_3",
|
||||
"rtdd:figurines_4",
|
||||
"rtdd:figurines_5",
|
||||
]
|
||||
|
||||
# 开出内容的显示名(用于开盒提示)
|
||||
FIGURINE_NAMES = {
|
||||
"rtdd:cat_0": "猫手办",
|
||||
"rtdd:fox_0": "狐狸手办",
|
||||
"rtdd:figurines": "赞助者手办_1",
|
||||
"rtdd:figurines_1": "赞助者手办_2",
|
||||
"rtdd:figurines_2": "赞助者手办_3",
|
||||
"rtdd:figurines_3": "赞助者手办_4",
|
||||
"rtdd:figurines_4": "赞助者手办_5",
|
||||
"rtdd:figurines_5": "赞助者手办_6",
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# 二、猫/狐狸手办循环转换链(空手右键逐级切换, 末尾回到起点)
|
||||
# ============================================================
|
||||
CYCLE_CHAINS = {
|
||||
"rtdd:cat_0": "rtdd:cat_1",
|
||||
"rtdd:cat_1": "rtdd:cat_2",
|
||||
"rtdd:cat_2": "rtdd:cat_3",
|
||||
"rtdd:cat_3": "rtdd:cat_0",
|
||||
"rtdd:fox_0": "rtdd:fox_1",
|
||||
"rtdd:fox_1": "rtdd:fox_2",
|
||||
"rtdd:fox_2": "rtdd:fox_3",
|
||||
"rtdd:fox_3": "rtdd:fox_0",
|
||||
}
|
||||
Whitespace-only changes.
+203
@@ -0,0 +1,203 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
================================================================================
|
||||
手办·右键转换引擎(服务端)
|
||||
================================================================================
|
||||
只处理"手办"(xy_shouban) 分类下的【右键切换型】转换:
|
||||
盲盒开盒、猫/狐狸手办循环切换。
|
||||
全部为即时右键转换, 无计时, 因此只有一个服务类, 不需要拆分 TimerService。
|
||||
|
||||
【事件监听在哪?】
|
||||
本类【不】监听任何事件!
|
||||
所有事件由 FeatureDispatcher(xiangyemod/service/FeatureDispatcher.py)统一监听:
|
||||
- ServerBlockUseEvent → OnBlockUse → 按 blockOwner 路由到本类 handleBlockUse
|
||||
本类只实现被分发器调用的接口方法, 不关心事件来源。
|
||||
|
||||
【本类怎么做(自上而下的方法系列)】
|
||||
handleBlockUse ← 入口:分发器转发右键方块事件
|
||||
├─ 空手确认 ← 盲盒/循环都要求空手
|
||||
├─ checkCooldown← 连点冷却(防刷手办)
|
||||
├─ openBlindBox ← 盲盒: 换为"开过"盒 + 随机掉落 1 个手办 + 提示
|
||||
└─ replaceBlock ← 猫/狐狸: 逐级切到链上下一个形态
|
||||
================================================================================
|
||||
"""
|
||||
import time
|
||||
import random
|
||||
import mod.server.extraServerApi as serverApi
|
||||
|
||||
# ---- 网易API基础对象 ----
|
||||
ServerSystem = serverApi.GetServerSystemCls() # 服务端系统基类
|
||||
comp = serverApi.GetEngineCompFactory() # 组件工厂
|
||||
|
||||
# ---- 继承 BlueFox 框架的系统基类(提供 CreateEngineItemEntity 等方法) ----
|
||||
from ....BlueFoxTools.SpecialFunciton.BlueFoxSystem import BlueFoxServerSystem
|
||||
from ....BlueFoxTools.ToolsConfig import LevelId
|
||||
|
||||
# ---- 只导入手办自己的数据库 ----
|
||||
from ..ShoubanData import (
|
||||
BLIND_BOX_SOURCE, BLIND_BOX_TARGET,
|
||||
BLIND_BOX_POOL, FIGURINE_NAMES, CYCLE_CHAINS,
|
||||
)
|
||||
|
||||
# ---- 统一分发器(本类把自己注册进去, 让分发器把右键事件转发过来) ----
|
||||
from ...service.FeatureDispatcher import FeatureDispatcher
|
||||
|
||||
|
||||
class ShoubanService(BlueFoxServerSystem):
|
||||
"""
|
||||
手办·右键转换引擎
|
||||
|
||||
【核心数据结构】
|
||||
shoubanBlockSet = {方块名, ...} # 管辖方块(盲盒 + 猫/狐狸链上所有方块)
|
||||
cooldown = {key: 时间戳} # 连点冷却表
|
||||
"""
|
||||
|
||||
def __init__(self, namespace, systemName):
|
||||
"""
|
||||
初始化:构建管辖方块集合, 并注册到统一分发器。
|
||||
"""
|
||||
super(ShoubanService, self).__init__(namespace, systemName)
|
||||
print "===== ShoubanService init ====="
|
||||
|
||||
# ---- 管辖方块: 盲盒(未开) + 猫/狐狸循环链上所有方块 ----
|
||||
self.shoubanBlockSet = set([BLIND_BOX_SOURCE]) | set(CYCLE_CHAINS.keys())
|
||||
|
||||
# ---- 右键冷却表 ----
|
||||
self.cooldown = {}
|
||||
|
||||
# ---- 注册到统一分发器 ----
|
||||
FeatureDispatcher.register(self)
|
||||
|
||||
# ============================================================================
|
||||
# 一、给 FeatureDispatcher 的接口
|
||||
# ============================================================================
|
||||
|
||||
def getFeatureBlocks(self):
|
||||
"""返回本功能管辖的所有方块名集合(右键路由用)"""
|
||||
return self.shoubanBlockSet
|
||||
|
||||
def getItemUseOnRules(self):
|
||||
"""返回"手持物品右键方块表面"的规则(手办分类没有, 返回空)"""
|
||||
return {}
|
||||
|
||||
def handleBlockUse(self, args):
|
||||
"""
|
||||
由 FeatureDispatcher 转发的右键方块入口。
|
||||
只有 blockName 在本功能的方块集合里才会被调用。
|
||||
|
||||
盲盒开盒 / 手办循环均要求【空手】右键:
|
||||
- 盲盒: 空手右键 → 换为"开过"盒 + 随机掉落 1 个手办
|
||||
- 猫/狐狸: 空手右键 → 逐级切到链上下一个形态
|
||||
|
||||
返回 True = 本功能处理了这个右键(分发器取消默认行为)
|
||||
返回 False = 当前组合没有匹配规则(分发器不拦截)
|
||||
"""
|
||||
blockName = args.get("blockName")
|
||||
playerId = args.get("playerId")
|
||||
x, y, z = args.get("x"), args.get("y"), args.get("z")
|
||||
pos = (x, y, z)
|
||||
dim = comp.CreateDimension(playerId).GetEntityDimensionId()
|
||||
key = (dim, x, y, z)
|
||||
|
||||
# ---- 空手确认(盲盒/循环都要求空手) ----
|
||||
itemDict = self.getCarriedItem(playerId)
|
||||
itemName = itemDict.get("newItemName") if itemDict else None
|
||||
if itemName and itemName != "minecraft:air":
|
||||
return False
|
||||
|
||||
# ---- 连点冷却 ----
|
||||
if not self.checkCooldown(key, 0.5):
|
||||
return True
|
||||
|
||||
# ---- 盲盒开盒 ----
|
||||
if blockName == BLIND_BOX_SOURCE:
|
||||
self.openBlindBox(playerId, pos, dim)
|
||||
return True
|
||||
|
||||
# ---- 猫/狐狸手办循环切换 ----
|
||||
if blockName in CYCLE_CHAINS:
|
||||
self.replaceBlock(pos, dim, CYCLE_CHAINS[blockName], playerId)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
# ============================================================================
|
||||
# 二、盲盒开盒
|
||||
# ============================================================================
|
||||
|
||||
def openBlindBox(self, playerId, pos, dim):
|
||||
"""
|
||||
盲盒开盒:换为"开过"盒, 随机掉落 1 个手办方块, 提示开出内容。
|
||||
"""
|
||||
# 随机开出一个手办
|
||||
fig = random.choice(BLIND_BOX_POOL)
|
||||
# 盒子变为"开过"
|
||||
self.replaceBlock(pos, dim, BLIND_BOX_TARGET, playerId)
|
||||
# 掉落手办
|
||||
self.dropItems(dim, pos, [{"name": fig, "count": 1}])
|
||||
# 提示开出内容
|
||||
name = FIGURINE_NAMES.get(fig, "手办")
|
||||
comp.CreateMsg(playerId).NotifyOneMessage(playerId, "§a盲盒开出了" + name + "!")
|
||||
|
||||
# ============================================================================
|
||||
# 三、工具方法:获取玩家主手物品
|
||||
# ============================================================================
|
||||
|
||||
def getCarriedItem(self, playerId):
|
||||
itemComp = comp.CreateItem(playerId)
|
||||
return itemComp.GetPlayerItem(
|
||||
serverApi.GetMinecraftEnum().ItemPosType.CARRIED, 0)
|
||||
|
||||
# ============================================================================
|
||||
# 四、工具方法:替换方块
|
||||
# ============================================================================
|
||||
|
||||
def replaceBlock(self, pos, dim, newBlockName, playerId=None):
|
||||
"""
|
||||
替换指定位置的方块。
|
||||
playerId 可为 None, 用 LevelId 创建组件。
|
||||
"""
|
||||
creator = playerId if playerId is not None else LevelId
|
||||
aux = 0
|
||||
try:
|
||||
oldBlock = comp.CreateBlockInfo(creator).GetBlockNew(pos, dim)
|
||||
aux = oldBlock.get("aux", 0)
|
||||
except Exception:
|
||||
aux = 0
|
||||
comp.CreateBlockInfo(creator).SetBlockNew(
|
||||
pos,
|
||||
blockDict={"name": newBlockName, "aux": aux},
|
||||
oldBlockHandling=0,
|
||||
dimensionId=dim)
|
||||
|
||||
# ============================================================================
|
||||
# 五、工具方法:以掉落物形式生成物品(开出/取下等)
|
||||
# ============================================================================
|
||||
|
||||
def dropItems(self, dim, pos, dropList):
|
||||
"""
|
||||
dropList 格式:[{"name": "xxx", "count": N}, ...]
|
||||
在方块上方生成掉落物实体, 让它自然掉到地上。
|
||||
"""
|
||||
for d in dropList:
|
||||
itemDict = {
|
||||
"itemName": d["name"],
|
||||
"count": d.get("count", 1),
|
||||
"auxValue": d.get("aux", 0),
|
||||
}
|
||||
self.CreateEngineItemEntity(itemDict, dim, (pos[0], pos[1] + 0.5, pos[2]))
|
||||
|
||||
# ============================================================================
|
||||
# 六、工具方法:冷却检查
|
||||
# ============================================================================
|
||||
|
||||
def checkCooldown(self, key, cd=0.5):
|
||||
"""
|
||||
右键冷却:同一方块 cd 秒内只响应一次, 防止连点刷手办/刷提示。
|
||||
"""
|
||||
now = time.time()
|
||||
last = self.cooldown.get(key, 0)
|
||||
if now - last < cd:
|
||||
return False
|
||||
self.cooldown[key] = now
|
||||
return True
|
||||
Whitespace-only changes.
+460
@@ -0,0 +1,460 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
================================================================================
|
||||
死亡生物方块转换规则数据
|
||||
================================================================================
|
||||
所有"死亡生物"(xy_deadmob) 分类下"右键方块→消耗物品→替换方块/掉落物品"的转换规则
|
||||
都在这里。只被 DeadmobService / DeadmobTimerService 读取,FeatureDispatcher 通过
|
||||
方块名集合路由到对应服务,不会去查其他功能的数据库。
|
||||
|
||||
【流程】
|
||||
1) 挂肉钩挂放: 手持 死牛/死猪/死羊/原版鲑鱼/原版鳕鱼 右键 空钩(meathook)
|
||||
→ 挂上对应尸体(消耗1个)。鱼可挂两条。
|
||||
2) 取下退回: 切肉开始前, 空手右键 整只/整头 → 空钩 + 掉落回尸体(不可逆前可退)
|
||||
3) 挂肉钩切肉: 手持 菜刀(cleaver) 右键 逐级切割(不可逆), 以掉落物形式获得对应部位肉
|
||||
- 切肉每刀掉落数量: 常规肉 2~3 个; 四肢固定 4 个; 牛头骨固定 1 个
|
||||
- 牛: 去头去皮掉牛头骨1+原版牛肉4~5 → 去四肢掉牛腿肉4 → 去肉掉牛肋2~3+牛里脊2~3+原版牛肉4~5 → 空钩掉碎肉2~3+原版牛肉4~5
|
||||
- 猪: 去皮掉五花肉2~3+原版生猪排4~5 → 去四肢掉猪蹄4 → 去肉掉猪肋排2~3+原版生猪排4~5 → 空钩掉碎肉2~3+原版生猪排4~5
|
||||
- 羊: 去皮掉羊小排2~3+原版生羊肉4~5 → 去四肢掉羊腿4 → 去肉掉羊里脊2~3+原版生羊肉4~5 → 空钩掉碎肉2~3+原版生羊肉4~5
|
||||
4) 鱼晾干(计时): 鲑鱼/鳕鱼挂上后自动计时约300秒晾干(挂肉钩自动开始, autoStart=True)
|
||||
- 鲑鱼: 挂一条→干一条; 挂两条→干两条
|
||||
- 鳕鱼: 挂两条→干两条
|
||||
5) 取鱼干: 空手右键 干鱼钩 → 以掉落物形式获得 鱼干(thedriedfish)
|
||||
6) 死鱼/死鱿鱼切块(手持菜刀, 掉落物):
|
||||
- 死鲑鱼: 共四刀, 每刀掉1份生鱼片(sashimi), 共4份 → 方块消失
|
||||
- 死鳕鱼: 共三刀, 每刀掉1份生鱼片(sashimi), 共3份 → 方块消失
|
||||
- 死鱿鱼: 一刀切须, 掉5~6个鱿鱼须(rawsquidwhiskersr) → 变为 死鱿鱼(切掉鱿鱼须)
|
||||
|
||||
【尸体生成机制】(玩家杀死对应生物生成尸体) 已在txt标注"暂时不搞", 本次不实现。
|
||||
|
||||
字段说明(与 AnimalData/BarbecueData 一致):
|
||||
source: 被右键的方块ID(必填)
|
||||
item: 玩家手里必须拿的物品, None=空手
|
||||
target: 替换成什么方块, "minecraft:air"=消失
|
||||
consume: 消耗物品数量, 0=不消耗(默认1)
|
||||
dropItem: 转换时以掉落物形式生成的物品列表(优先于 giveItem)
|
||||
格式: [{"name": "xxx", "count": N}, ...]
|
||||
count 支持范围写法 (min,max), 服务端随机取整(如 (2,3)=2~3)
|
||||
type: "switch"=右键切换, "timer"=计时自动转换
|
||||
time: 计时型转换需要多少秒
|
||||
tip: 转换成功时给玩家的提示(绿色§a开头)
|
||||
doneTip: 计时到点完成时给启动者的提示
|
||||
autoStart: 放置时是否自动开始计时, 默认True(鱼挂上自动晾干)
|
||||
================================================================================
|
||||
"""
|
||||
|
||||
DEADMOB_SWITCHES = [
|
||||
|
||||
# ============================================================
|
||||
# 一、挂肉钩 - 挂放尸体(手持尸体右键空钩, 消耗1个)
|
||||
# ============================================================
|
||||
{
|
||||
"source": "rtdd:meathook",
|
||||
"item": "rtdd:dead_cow",
|
||||
"target": "rtdd:cow_0",
|
||||
"consume": 1,
|
||||
"type": "switch",
|
||||
"tip": "§a已把整只牛挂上肉钩",
|
||||
"giveItem": None,
|
||||
},
|
||||
{
|
||||
"source": "rtdd:meathook",
|
||||
"item": "rtdd:dead_pig",
|
||||
"target": "rtdd:pig_0",
|
||||
"consume": 1,
|
||||
"type": "switch",
|
||||
"tip": "§a已把整只猪挂上肉钩",
|
||||
"giveItem": None,
|
||||
},
|
||||
{
|
||||
"source": "rtdd:meathook",
|
||||
"item": "rtdd:dead_sheep",
|
||||
"target": "rtdd:sheep",
|
||||
"consume": 1,
|
||||
"type": "switch",
|
||||
"tip": "§a已把整只羊挂上肉钩",
|
||||
"giveItem": None,
|
||||
},
|
||||
# 挂鲑鱼(原版salmon): 空钩→一条→两条
|
||||
{
|
||||
"source": "rtdd:meathook",
|
||||
"item": "minecraft:salmon",
|
||||
"target": "rtdd:hang_meat_hooks_1",
|
||||
"consume": 1,
|
||||
"type": "switch",
|
||||
"tip": "§a已挂上一条鲑鱼,开始晾晒",
|
||||
"giveItem": None,
|
||||
},
|
||||
{
|
||||
"source": "rtdd:hang_meat_hooks_1",
|
||||
"item": "minecraft:salmon",
|
||||
"target": "rtdd:hang_meat_hooks_2",
|
||||
"consume": 1,
|
||||
"type": "switch",
|
||||
"tip": "§a已再挂上一条鲑鱼",
|
||||
"giveItem": None,
|
||||
},
|
||||
# 挂鳕鱼(原版cod): 空钩→一条→两条
|
||||
{
|
||||
"source": "rtdd:meathook",
|
||||
"item": "minecraft:cod",
|
||||
"target": "rtdd:meathookcod_1",
|
||||
"consume": 1,
|
||||
"type": "switch",
|
||||
"tip": "§a已挂上一条鳕鱼,开始晾晒",
|
||||
"giveItem": None,
|
||||
},
|
||||
{
|
||||
"source": "rtdd:meathookcod_1",
|
||||
"item": "minecraft:cod",
|
||||
"target": "rtdd:meathookcod_2",
|
||||
"consume": 1,
|
||||
"type": "switch",
|
||||
"tip": "§a已再挂上一条鳕鱼",
|
||||
"giveItem": None,
|
||||
},
|
||||
|
||||
# ============================================================
|
||||
# 二、取下退回(未切割前, 空手右键 整只/整头 → 空钩 + 掉落回尸体)
|
||||
# ============================================================
|
||||
{
|
||||
"source": "rtdd:cow_0",
|
||||
"item": None,
|
||||
"target": "rtdd:meathook",
|
||||
"consume": 0,
|
||||
"type": "switch",
|
||||
"dropItem": [{"name": "rtdd:dead_cow", "count": 1}],
|
||||
"tip": "§a取下了整只牛(尚未切割)",
|
||||
"giveItem": None,
|
||||
},
|
||||
{
|
||||
"source": "rtdd:pig_0",
|
||||
"item": None,
|
||||
"target": "rtdd:meathook",
|
||||
"consume": 0,
|
||||
"type": "switch",
|
||||
"dropItem": [{"name": "rtdd:dead_pig", "count": 1}],
|
||||
"tip": "§a取下了整只猪(尚未切割)",
|
||||
"giveItem": None,
|
||||
},
|
||||
{
|
||||
"source": "rtdd:sheep",
|
||||
"item": None,
|
||||
"target": "rtdd:meathook",
|
||||
"consume": 0,
|
||||
"type": "switch",
|
||||
"dropItem": [{"name": "rtdd:dead_sheep", "count": 1}],
|
||||
"tip": "§a取下了整只羊(尚未切割)",
|
||||
"giveItem": None,
|
||||
},
|
||||
|
||||
# ============================================================
|
||||
# 三、挂肉钩切肉(手持 菜刀cleaver, 掉落物, 数量随机 2~3 / 四肢固定4)
|
||||
# ============================================================
|
||||
# ---- 牛(不可逆) ----
|
||||
{
|
||||
"source": "rtdd:cow_0",
|
||||
"item": "rtdd:cleaver",
|
||||
"target": "rtdd:cow_1",
|
||||
"consume": 0,
|
||||
"type": "switch",
|
||||
"dropItem": [
|
||||
{"name": "rtdd:cow_skull", "count": 1},
|
||||
{"name": "minecraft:beef", "count": (4, 5)},
|
||||
],
|
||||
"tip": "§a砍下了牛头",
|
||||
"giveItem": None,
|
||||
},
|
||||
{
|
||||
"source": "rtdd:cow_1",
|
||||
"item": "rtdd:cleaver",
|
||||
"target": "rtdd:cow_2",
|
||||
"consume": 0,
|
||||
"type": "switch",
|
||||
"dropItem": [{"name": "rtdd:legcow", "count": 4}],
|
||||
"tip": "§a卸下了四肢牛腿肉",
|
||||
"giveItem": None,
|
||||
},
|
||||
{
|
||||
"source": "rtdd:cow_2",
|
||||
"item": "rtdd:cleaver",
|
||||
"target": "rtdd:cow_3",
|
||||
"consume": 0,
|
||||
"type": "switch",
|
||||
"dropItem": [
|
||||
{"name": "rtdd:costillavacaes", "count": (2, 3)},
|
||||
{"name": "rtdd:beef_tenderloin", "count": (2, 3)},
|
||||
{"name": "minecraft:beef", "count": (4, 5)},
|
||||
],
|
||||
"tip": "§a剔出了牛肋和牛里脊",
|
||||
"giveItem": None,
|
||||
},
|
||||
{
|
||||
"source": "rtdd:cow_3",
|
||||
"item": "rtdd:cleaver",
|
||||
"target": "rtdd:meathook",
|
||||
"consume": 0,
|
||||
"type": "switch",
|
||||
"dropItem": [
|
||||
{"name": "rtdd:crushedmeat", "count": (2, 3)},
|
||||
{"name": "minecraft:beef", "count": (4, 5)},
|
||||
],
|
||||
"tip": "§a刮下碎肉,肉钩已清空",
|
||||
"giveItem": None,
|
||||
},
|
||||
# ---- 猪(不可逆) ----
|
||||
{
|
||||
"source": "rtdd:pig_0",
|
||||
"item": "rtdd:cleaver",
|
||||
"target": "rtdd:pig_1",
|
||||
"consume": 0,
|
||||
"type": "switch",
|
||||
"dropItem": [
|
||||
{"name": "rtdd:porkloins", "count": (2, 3)},
|
||||
{"name": "minecraft:porkchop", "count": (4, 5)},
|
||||
],
|
||||
"tip": "§a已给猪去皮",
|
||||
"giveItem": None,
|
||||
},
|
||||
{
|
||||
"source": "rtdd:pig_1",
|
||||
"item": "rtdd:cleaver",
|
||||
"target": "rtdd:pig_2",
|
||||
"consume": 0,
|
||||
"type": "switch",
|
||||
"dropItem": [{"name": "rtdd:ham", "count": 4}],
|
||||
"tip": "§a卸下了四肢猪蹄",
|
||||
"giveItem": None,
|
||||
},
|
||||
{
|
||||
"source": "rtdd:pig_2",
|
||||
"item": "rtdd:cleaver",
|
||||
"target": "rtdd:pig_3",
|
||||
"consume": 0,
|
||||
"type": "switch",
|
||||
"dropItem": [
|
||||
{"name": "rtdd:porkrib", "count": (2, 3)},
|
||||
{"name": "minecraft:porkchop", "count": (4, 5)},
|
||||
],
|
||||
"tip": "§a剔出了猪肋排",
|
||||
"giveItem": None,
|
||||
},
|
||||
{
|
||||
"source": "rtdd:pig_3",
|
||||
"item": "rtdd:cleaver",
|
||||
"target": "rtdd:meathook",
|
||||
"consume": 0,
|
||||
"type": "switch",
|
||||
"dropItem": [
|
||||
{"name": "rtdd:crushedmeat", "count": (2, 3)},
|
||||
{"name": "minecraft:porkchop", "count": (4, 5)},
|
||||
],
|
||||
"tip": "§a刮下碎肉,肉钩已清空",
|
||||
"giveItem": None,
|
||||
},
|
||||
# ---- 羊(不可逆) ----
|
||||
{
|
||||
"source": "rtdd:sheep",
|
||||
"item": "rtdd:cleaver",
|
||||
"target": "rtdd:sheep_1",
|
||||
"consume": 0,
|
||||
"type": "switch",
|
||||
"dropItem": [
|
||||
{"name": "rtdd:sheepracks", "count": (2, 3)},
|
||||
{"name": "minecraft:mutton", "count": (4, 5)},
|
||||
],
|
||||
"tip": "§a已给羊去皮",
|
||||
"giveItem": None,
|
||||
},
|
||||
{
|
||||
"source": "rtdd:sheep_1",
|
||||
"item": "rtdd:cleaver",
|
||||
"target": "rtdd:sheep_2",
|
||||
"consume": 0,
|
||||
"type": "switch",
|
||||
"dropItem": [{"name": "rtdd:legsleep", "count": 4}],
|
||||
"tip": "§a卸下了四肢羊腿",
|
||||
"giveItem": None,
|
||||
},
|
||||
{
|
||||
"source": "rtdd:sheep_2",
|
||||
"item": "rtdd:cleaver",
|
||||
"target": "rtdd:sheep_3",
|
||||
"consume": 0,
|
||||
"type": "switch",
|
||||
"dropItem": [
|
||||
{"name": "rtdd:sheeploins", "count": (2, 3)},
|
||||
{"name": "minecraft:mutton", "count": (4, 5)},
|
||||
],
|
||||
"tip": "§a剔出了羊里脊",
|
||||
"giveItem": None,
|
||||
},
|
||||
{
|
||||
"source": "rtdd:sheep_3",
|
||||
"item": "rtdd:cleaver",
|
||||
"target": "rtdd:meathook",
|
||||
"consume": 0,
|
||||
"type": "switch",
|
||||
"dropItem": [
|
||||
{"name": "rtdd:crushedmeat", "count": (2, 3)},
|
||||
{"name": "minecraft:mutton", "count": (4, 5)},
|
||||
],
|
||||
"tip": "§a刮下碎肉,肉钩已清空",
|
||||
"giveItem": None,
|
||||
},
|
||||
|
||||
# ============================================================
|
||||
# 四、取鱼干(空手右键 干鱼钩 → 掉落 鱼干)
|
||||
# ============================================================
|
||||
{
|
||||
"source": "rtdd:hang_meat_hooks_3",
|
||||
"item": None,
|
||||
"target": "rtdd:meathook",
|
||||
"consume": 0,
|
||||
"type": "switch",
|
||||
"dropItem": [{"name": "rtdd:thedriedfish", "count": 1}],
|
||||
"tip": "§a取下了1份鱼干",
|
||||
"giveItem": None,
|
||||
},
|
||||
{
|
||||
"source": "rtdd:hang_meat_hooks_4",
|
||||
"item": None,
|
||||
"target": "rtdd:hang_meat_hooks_3",
|
||||
"consume": 0,
|
||||
"type": "switch",
|
||||
"dropItem": [{"name": "rtdd:thedriedfish", "count": 1}],
|
||||
"tip": "§a取下了1份鱼干,还剩一条",
|
||||
"giveItem": None,
|
||||
},
|
||||
{
|
||||
"source": "rtdd:meathookcoddry",
|
||||
"item": None,
|
||||
"target": "rtdd:meathook",
|
||||
"consume": 0,
|
||||
"type": "switch",
|
||||
"dropItem": [{"name": "rtdd:thedriedfish", "count": 1}],
|
||||
"tip": "§a取下了1份鱼干",
|
||||
"giveItem": None,
|
||||
},
|
||||
|
||||
# ============================================================
|
||||
# 五、死鱼/死鱿鱼切块(手持 菜刀cleaver, 掉落物)
|
||||
# ============================================================
|
||||
# 死鲑鱼: 四刀, 每刀1份生鱼片
|
||||
{
|
||||
"source": "rtdd:killfish_0",
|
||||
"item": "rtdd:cleaver",
|
||||
"target": "rtdd:killfish_1",
|
||||
"consume": 0,
|
||||
"type": "switch",
|
||||
"dropItem": [{"name": "rtdd:sashimi", "count": 1}],
|
||||
"tip": "§a切下1份生鱼片",
|
||||
"giveItem": None,
|
||||
},
|
||||
{
|
||||
"source": "rtdd:killfish_1",
|
||||
"item": "rtdd:cleaver",
|
||||
"target": "rtdd:killfish_2",
|
||||
"consume": 0,
|
||||
"type": "switch",
|
||||
"dropItem": [{"name": "rtdd:sashimi", "count": 1}],
|
||||
"tip": "§a切下1份生鱼片",
|
||||
"giveItem": None,
|
||||
},
|
||||
{
|
||||
"source": "rtdd:killfish_2",
|
||||
"item": "rtdd:cleaver",
|
||||
"target": "rtdd:killfish_3",
|
||||
"consume": 0,
|
||||
"type": "switch",
|
||||
"dropItem": [{"name": "rtdd:sashimi", "count": 1}],
|
||||
"tip": "§a切下1份生鱼片",
|
||||
"giveItem": None,
|
||||
},
|
||||
{
|
||||
"source": "rtdd:killfish_3",
|
||||
"item": "rtdd:cleaver",
|
||||
"target": "minecraft:air",
|
||||
"consume": 0,
|
||||
"type": "switch",
|
||||
"dropItem": [{"name": "rtdd:sashimi", "count": 1}],
|
||||
"tip": "§a切完最后一份生鱼片",
|
||||
"giveItem": None,
|
||||
},
|
||||
# 死鳕鱼: 三刀, 每刀1份生鱼片
|
||||
{
|
||||
"source": "rtdd:killcod_0",
|
||||
"item": "rtdd:cleaver",
|
||||
"target": "rtdd:killcod_1",
|
||||
"consume": 0,
|
||||
"type": "switch",
|
||||
"dropItem": [{"name": "rtdd:sashimi", "count": 1}],
|
||||
"tip": "§a切下1份生鱼片",
|
||||
"giveItem": None,
|
||||
},
|
||||
{
|
||||
"source": "rtdd:killcod_1",
|
||||
"item": "rtdd:cleaver",
|
||||
"target": "rtdd:killcod_2",
|
||||
"consume": 0,
|
||||
"type": "switch",
|
||||
"dropItem": [{"name": "rtdd:sashimi", "count": 1}],
|
||||
"tip": "§a切下1份生鱼片",
|
||||
"giveItem": None,
|
||||
},
|
||||
{
|
||||
"source": "rtdd:killcod_2",
|
||||
"item": "rtdd:cleaver",
|
||||
"target": "minecraft:air",
|
||||
"consume": 0,
|
||||
"type": "switch",
|
||||
"dropItem": [{"name": "rtdd:sashimi", "count": 1}],
|
||||
"tip": "§a切完最后一份生鱼片",
|
||||
"giveItem": None,
|
||||
},
|
||||
# 死鱿鱼: 一刀切须
|
||||
{
|
||||
"source": "rtdd:deadsquida",
|
||||
"item": "rtdd:cleaver",
|
||||
"target": "rtdd:dead_squid_1",
|
||||
"consume": 0,
|
||||
"type": "switch",
|
||||
"dropItem": [{"name": "rtdd:rawsquidwhiskersr", "count": (5, 6)}],
|
||||
"tip": "§a切下了鱿鱼须",
|
||||
"giveItem": None,
|
||||
},
|
||||
|
||||
# ============================================================
|
||||
# 六、鱼晾干(计时自动转换, 挂上即自动开始 autoStart=True)
|
||||
# ============================================================
|
||||
{
|
||||
"source": "rtdd:hang_meat_hooks_1",
|
||||
"item": None,
|
||||
"target": "rtdd:hang_meat_hooks_3",
|
||||
"consume": 0,
|
||||
"type": "timer",
|
||||
"time": 300,
|
||||
"doneTip": "§e鲑鱼晾干了!",
|
||||
"giveItem": None,
|
||||
},
|
||||
{
|
||||
"source": "rtdd:hang_meat_hooks_2",
|
||||
"item": None,
|
||||
"target": "rtdd:hang_meat_hooks_4",
|
||||
"consume": 0,
|
||||
"type": "timer",
|
||||
"time": 300,
|
||||
"doneTip": "§e鲑鱼晾干了!",
|
||||
"giveItem": None,
|
||||
},
|
||||
{
|
||||
"source": "rtdd:meathookcod_2",
|
||||
"item": None,
|
||||
"target": "rtdd:meathookcoddry",
|
||||
"consume": 0,
|
||||
"type": "timer",
|
||||
"time": 300,
|
||||
"doneTip": "§e鳕鱼晾干了!",
|
||||
"giveItem": None,
|
||||
},
|
||||
]
|
||||
Whitespace-only changes.
+270
@@ -0,0 +1,270 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
================================================================================
|
||||
死亡生物·右键转换引擎(服务端)
|
||||
================================================================================
|
||||
只处理"死亡生物"(xy_deadmob) 分类下的【右键切换型】转换:
|
||||
挂肉钩挂放尸体、取下退回、菜刀切肉(牛/猪/羊)、取鱼干、死鱼/死鱿鱼切块。
|
||||
|
||||
【与计时转换解耦】
|
||||
同分类的"鱼晾干"(计时自动转换)已拆到独立类:DeadmobTimerService。
|
||||
本类只负责右键事件,两者通过 FeatureDispatcher 协作:
|
||||
- 挂上鱼后(变成计时源方块)由 DeadmobTimerService 的 tick 自动开始晾干
|
||||
(autoStart=True,无需右键激活,因此本类不需要转发 startTimer)
|
||||
- 计时到点转换完全由 DeadmobTimerService 负责
|
||||
|
||||
【事件监听在哪?】
|
||||
本类【不】监听任何事件!
|
||||
所有事件由 FeatureDispatcher(xiangyemod/service/FeatureDispatcher.py)统一监听:
|
||||
- ServerBlockUseEvent → OnBlockUse → 按 blockOwner 路由到本类 handleBlockUse
|
||||
本类只实现被分发器调用的接口方法,不关心事件来源。
|
||||
|
||||
【本类怎么做(自上而下的方法系列)】
|
||||
handleBlockUse ← 入口:分发器转发右键方块事件
|
||||
├─ findRecipe ← 匹配 (方块名, 物品名) 规则;空手规则需确认真正空手
|
||||
├─ checkCooldown ← 连点冷却(防刷提示)
|
||||
└─ handleSwitch ← 切换型规则具体执行(挂放/取回/切肉/取鱼干/切块)
|
||||
├─ consumeItem ← 消耗物品(挂放尸体消耗1个)
|
||||
├─ replaceBlock ← 替换方块
|
||||
└─ dropItems ← 掉落物形式给物品(切肉/取回/取鱼干),支持随机数量
|
||||
================================================================================
|
||||
"""
|
||||
import time
|
||||
import random
|
||||
import mod.server.extraServerApi as serverApi
|
||||
|
||||
# ---- 网易API基础对象 ----
|
||||
ServerSystem = serverApi.GetServerSystemCls() # 服务端系统基类
|
||||
comp = serverApi.GetEngineCompFactory() # 组件工厂
|
||||
|
||||
# ---- 继承 BlueFox 框架的系统基类(提供 CreateEngineItemEntity 等方法) ----
|
||||
from ....BlueFoxTools.SpecialFunciton.BlueFoxSystem import BlueFoxServerSystem
|
||||
from ....BlueFoxTools.ToolsConfig import LevelId
|
||||
|
||||
# ---- 只导入死亡生物自己的数据库 ----
|
||||
from ..DeadmobData import DEADMOB_SWITCHES
|
||||
|
||||
# ---- 统一分发器 ----
|
||||
from ...service.FeatureDispatcher import FeatureDispatcher
|
||||
|
||||
|
||||
class DeadmobService(BlueFoxServerSystem):
|
||||
"""
|
||||
死亡生物·右键转换引擎
|
||||
|
||||
【核心数据结构】
|
||||
deadmobRecipes = {(方块名, 物品名): 规则dict} # O(1) 查找,只收 switch 规则
|
||||
deadmobBlockSet = {方块名, ...} # O(1) 注册用(所有右键源方块)
|
||||
cooldown = {key: 时间戳} # 连点冷却表
|
||||
"""
|
||||
|
||||
def __init__(self, namespace, systemName):
|
||||
"""
|
||||
初始化:构建规则字典,并注册到统一分发器(让分发器把右键事件转发过来)。
|
||||
"""
|
||||
super(DeadmobService, self).__init__(namespace, systemName)
|
||||
print "===== DeadmobService init ====="
|
||||
|
||||
# ---- 构建右键规则字典(O(1) 查找),只收 switch 规则 ----
|
||||
# 计时规则(鱼晾干)由 DeadmobTimerService 处理,本类不接管
|
||||
self.deadmobRecipes = {}
|
||||
self.deadmobBlockSet = set()
|
||||
for sw in DEADMOB_SWITCHES:
|
||||
if sw.get("type") == "timer":
|
||||
continue
|
||||
key = (sw["source"], sw.get("item"))
|
||||
self.deadmobRecipes[key] = sw
|
||||
self.deadmobBlockSet.add(sw["source"])
|
||||
|
||||
# ---- 右键冷却表 ----
|
||||
self.cooldown = {}
|
||||
|
||||
# ---- 注册到统一分发器 ----
|
||||
FeatureDispatcher.register(self)
|
||||
|
||||
# ============================================================================
|
||||
# 一、给 FeatureDispatcher 的接口
|
||||
# ============================================================================
|
||||
|
||||
def getFeatureBlocks(self):
|
||||
"""返回本功能管辖的所有方块名集合(右键路由用)"""
|
||||
return self.deadmobBlockSet
|
||||
|
||||
def getItemUseOnRules(self):
|
||||
"""
|
||||
返回"手持物品右键方块表面"的规则(当前返回空)。
|
||||
挂鱼走 ServerBlockUseEvent(右键方块本身)即可,不需要 ItemUseOn:
|
||||
日志验证手持原版鲑鱼/鳕鱼右键肉钩会正常触发 ServerBlockUseEvent 并转换,
|
||||
若同时注册 ItemUseOn 会重复转换(多消耗一条鱼)且 ServerItemUseOnEvent 的
|
||||
玩家键是 entityId 不是 playerId,易出错,故不再使用该路径。
|
||||
"""
|
||||
return {}
|
||||
|
||||
def handleBlockUse(self, args):
|
||||
"""
|
||||
由 FeatureDispatcher 转发的右键方块入口。
|
||||
只有 blockName 在本功能的方块集合里才会被调用。
|
||||
|
||||
返回 True = 本功能处理了这个右键(分发器会取消默认行为)
|
||||
返回 False = 当前组合没有匹配规则(分发器不拦截,交还默认行为)
|
||||
"""
|
||||
blockName = args.get("blockName")
|
||||
playerId = args.get("playerId")
|
||||
x, y, z = args.get("x"), args.get("y"), args.get("z")
|
||||
pos = (x, y, z)
|
||||
dim = comp.CreateDimension(playerId).GetEntityDimensionId()
|
||||
key = (dim, x, y, z)
|
||||
|
||||
itemDict = self.getCarriedItem(playerId)
|
||||
|
||||
recipe = self.findRecipe(blockName, itemDict)
|
||||
print "[Deadmob-Use]", blockName, "hand:", (itemDict.get("newItemName") if itemDict else None), "hit:", (recipe.get("source") if recipe else "NONE")
|
||||
if not recipe:
|
||||
return False
|
||||
|
||||
# 冷却检查(防止连点刷掉落物/刷提示)
|
||||
if not self.checkCooldown(key, recipe.get("switchCd", 0.5)):
|
||||
return True
|
||||
|
||||
# 切换型规则:就地执行
|
||||
self.handleSwitch(recipe, playerId, pos, dim, itemDict)
|
||||
return True
|
||||
|
||||
# ============================================================================
|
||||
# 二、规则匹配(O(1) 字典查找)
|
||||
# ============================================================================
|
||||
|
||||
def findRecipe(self, blockName, itemDict):
|
||||
"""
|
||||
在死亡生物字典中查找匹配规则:
|
||||
1. 先精确匹配 (方块名, 物品名) —— 手持菜刀切肉、手持尸体挂放、手持原版鱼挂放
|
||||
2. 没找到再匹配 (方块名, None) —— 空手触发的规则(取下退回/取鱼干)
|
||||
注意:同一个源方块可能同时有"手持菜刀"和"空手"两类规则(如 cow_0 切肉/取回),
|
||||
因此 fallback 到空手规则前必须确认玩家【确实空手】,避免手持其他物品误触发。
|
||||
"""
|
||||
itemName = itemDict.get("newItemName") if itemDict else None
|
||||
recipe = self.deadmobRecipes.get((blockName, itemName))
|
||||
if recipe:
|
||||
return recipe
|
||||
# 空手规则:只有确认空手(无物品或手持空气)才 fallback
|
||||
if not itemName or itemName == "minecraft:air":
|
||||
return self.deadmobRecipes.get((blockName, None))
|
||||
return None
|
||||
|
||||
# ============================================================================
|
||||
# 三、切换型规则处理
|
||||
# ============================================================================
|
||||
|
||||
def handleSwitch(self, recipe, playerId, pos, dim, itemDict):
|
||||
"""
|
||||
处理"右键直接转换"类规则(统一流程:掉落物 → 消耗 → 替换 → 提示)。
|
||||
- 切肉(手持菜刀, consume=0): 掉落物 + 替换, 不消耗
|
||||
- 挂放(手持尸体/原版鱼, consume=1): 无掉落物, 消耗 + 替换
|
||||
- 取回/取鱼干(空手, consume=0): 掉落物 + 替换
|
||||
"""
|
||||
# ---- 掉落物优先(切肉/取回/取鱼干都生成掉落物) ----
|
||||
if recipe.get("dropItem"):
|
||||
self.dropItems(dim, pos, recipe["dropItem"])
|
||||
|
||||
# ---- 消耗物品(挂放尸体/挂鱼) ----
|
||||
if recipe.get("item") is not None and recipe.get("consume", 1) > 0:
|
||||
self.consumeItem(playerId, itemDict, recipe.get("consume", 1))
|
||||
|
||||
# ---- 替换方块 ----
|
||||
if recipe.get("target"):
|
||||
self.replaceBlock(pos, dim, recipe["target"], playerId)
|
||||
|
||||
# ---- 转换成功提示 ----
|
||||
if recipe.get("tip"):
|
||||
comp.CreateMsg(playerId).NotifyOneMessage(playerId, recipe["tip"])
|
||||
|
||||
# ============================================================================
|
||||
# 四、工具方法:获取玩家主手物品
|
||||
# ============================================================================
|
||||
|
||||
def getCarriedItem(self, playerId):
|
||||
itemComp = comp.CreateItem(playerId)
|
||||
return itemComp.GetPlayerItem(
|
||||
serverApi.GetMinecraftEnum().ItemPosType.CARRIED, 0)
|
||||
|
||||
# ============================================================================
|
||||
# 五、工具方法:消耗物品
|
||||
# ============================================================================
|
||||
|
||||
def consumeItem(self, playerId, itemDict, amount=1):
|
||||
if itemDict is None:
|
||||
return
|
||||
count = itemDict.get("count", 0)
|
||||
if count <= amount:
|
||||
comp.CreateItem(playerId).SetEntityItem(
|
||||
serverApi.GetMinecraftEnum().ItemPosType.CARRIED,
|
||||
{"itemName": "minecraft:air", "count": 0, "auxValue": 0})
|
||||
else:
|
||||
itemDict["count"] = count - amount
|
||||
comp.CreateItem(playerId).SetEntityItem(
|
||||
serverApi.GetMinecraftEnum().ItemPosType.CARRIED,
|
||||
{"itemName": itemDict["newItemName"],
|
||||
"count": itemDict["count"],
|
||||
"auxValue": itemDict.get("newAuxValue", 0)})
|
||||
|
||||
# ============================================================================
|
||||
# 六、工具方法:替换方块
|
||||
# ============================================================================
|
||||
|
||||
def replaceBlock(self, pos, dim, newBlockName, playerId=None):
|
||||
"""
|
||||
替换指定位置的方块。
|
||||
newBlockName="minecraft:air" 表示清除该方块。
|
||||
playerId 可为 None,用 LevelId 创建组件。
|
||||
"""
|
||||
creator = playerId if playerId is not None else LevelId
|
||||
# 清空方块时必须用 aux=0:空气没有朝向变体,
|
||||
# 若继承死鱼(face_directional)的朝向 aux,SetBlockNew 无法把方块清掉
|
||||
aux = 0
|
||||
if newBlockName != "minecraft:air":
|
||||
try:
|
||||
oldBlock = comp.CreateBlockInfo(creator).GetBlockNew(pos, dim)
|
||||
aux = oldBlock.get("aux", 0)
|
||||
except Exception:
|
||||
aux = 0
|
||||
comp.CreateBlockInfo(creator).SetBlockNew(
|
||||
pos,
|
||||
blockDict={"name": newBlockName, "aux": aux},
|
||||
oldBlockHandling=0,
|
||||
dimensionId=dim)
|
||||
|
||||
# ============================================================================
|
||||
# 七、工具方法:以掉落物形式生成物品(切肉/取下/取鱼干)
|
||||
# ============================================================================
|
||||
|
||||
def dropItems(self, dim, pos, dropList):
|
||||
"""
|
||||
dropList 格式:[{"name": "xxx", "count": N 或 (min,max)}, ...]
|
||||
count 为 tuple/list 时表示范围,随机取整(如 (4,5)=4~5、(2,3)=2~3)。
|
||||
在方块上方生成掉落物实体,让它自然掉到地上。
|
||||
"""
|
||||
for d in dropList:
|
||||
cnt = d.get("count", 1)
|
||||
if isinstance(cnt, (tuple, list)):
|
||||
cnt = random.randint(cnt[0], cnt[1])
|
||||
itemDict = {
|
||||
"itemName": d["name"],
|
||||
"count": cnt,
|
||||
"auxValue": d.get("aux", 0),
|
||||
}
|
||||
self.CreateEngineItemEntity(itemDict, dim, (pos[0], pos[1] + 0.5, pos[2]))
|
||||
|
||||
# ============================================================================
|
||||
# 八、工具方法:冷却检查
|
||||
# ============================================================================
|
||||
|
||||
def checkCooldown(self, key, cd=0.5):
|
||||
"""
|
||||
右键冷却:同一方块 cd 秒内只响应一次,防止连点刷掉落物/刷提示。
|
||||
"""
|
||||
now = time.time()
|
||||
last = self.cooldown.get(key, 0)
|
||||
if now - last < cd:
|
||||
return False
|
||||
self.cooldown[key] = now
|
||||
return True
|
||||
+256
@@ -0,0 +1,256 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
================================================================================
|
||||
死亡生物·计时转换引擎(服务端)
|
||||
================================================================================
|
||||
只处理"死亡生物"(xy_deadmob) 分类下的【计时自动转换】:
|
||||
鲑鱼/鳕鱼挂上肉钩后自动晾干(约300秒)→ 变为干鱼钩。
|
||||
|
||||
【与右键转换解耦】
|
||||
同分类的"右键切换"(挂放/切肉/取回/取鱼干/死鱼切块)在 DeadmobService。
|
||||
本类只负责计时,两者通过 FeatureDispatcher 协作:
|
||||
- 挂上鱼后,方块变成计时源(如 hang_meat_hooks_1),它的 tick 由 FeatureDispatcher
|
||||
按 timerOwner 路由到本类的 handleBlockEntityTick
|
||||
- autoStart=True:挂上即自动开始晾干,无需右键激活,因此 DeadmobService
|
||||
不需要转发 startTimer(本类 startTimer 仅为接口完整性保留,不会被调用)
|
||||
|
||||
【晾干规则】
|
||||
- autoStart=True:计时源方块放置/替换后,第一次 tick 自动补写计时数据
|
||||
- 晾干不需要任何环境条件(无 below 检查)
|
||||
- 完成后清除计时数据,若启动者在线则发完成提示(自动晾干通常无启动者)
|
||||
|
||||
【计时怎么做到"退出游戏继续计时"】
|
||||
不做倒计时,只记"开始时刻":
|
||||
- 首次 tick 把 开始时间戳(rtdd_tstart)+总时长(rtdd_tdur) 写进方块实体数据
|
||||
- 每帧 tick 判断 当前时间-开始时刻 >= 总时长 → 到点转换
|
||||
- 时间戳是绝对时间(time.time()),服务器重启/玩家离线真实时间仍在流逝,
|
||||
回来后 tick 直接算差值,剩余时间自然准确
|
||||
|
||||
【事件监听在哪?】
|
||||
本类【不】监听任何事件!
|
||||
ServerBlockEntityTickEvent 由 FeatureDispatcher 统一监听,再按 timerOwner
|
||||
路由到本类的 handleBlockEntityTick。
|
||||
|
||||
【本类怎么做(自上而下的方法系列)】
|
||||
handleBlockEntityTick ← 入口:分发器转发 tick 事件
|
||||
└─ _tickProcess ← 判断是否到点 / autoStart=True 自动补写计时
|
||||
└─ _finishTimer ← 到点执行转换(确认方块仍是计时源,否则清数据)
|
||||
└─ _notifyIfOnline ← 完成提示(启动者在线才发)
|
||||
_writeTimerData ← 写 开始时刻+时长 到方块实体
|
||||
_clearTimerData ← 清理方块上的计时数据
|
||||
================================================================================
|
||||
"""
|
||||
import time
|
||||
import mod.server.extraServerApi as serverApi
|
||||
|
||||
# ---- 网易API基础对象 ----
|
||||
ServerSystem = serverApi.GetServerSystemCls() # 服务端系统基类
|
||||
comp = serverApi.GetEngineCompFactory() # 组件工厂
|
||||
|
||||
# ---- 继承 BlueFox 框架的系统基类 ----
|
||||
from ....BlueFoxTools.SpecialFunciton.BlueFoxSystem import BlueFoxServerSystem
|
||||
from ....BlueFoxTools.ToolsConfig import LevelId
|
||||
|
||||
# ---- 方块实体数据组件(计时持久化:把开始时间戳+时长写进方块,重启后继续计时) ----
|
||||
blockEntityDataComp = comp.CreateBlockEntityData(LevelId)
|
||||
|
||||
# ---- 只导入死亡生物自己的数据库 ----
|
||||
from ..DeadmobData import DEADMOB_SWITCHES
|
||||
|
||||
# ---- 统一分发器(本类把自己注册进计时路由表,让分发器把 tick 事件转发过来) ----
|
||||
from ...service.FeatureDispatcher import FeatureDispatcher
|
||||
|
||||
|
||||
class DeadmobTimerService(BlueFoxServerSystem):
|
||||
"""
|
||||
死亡生物·计时转换引擎(鱼晾干)
|
||||
|
||||
【核心数据结构】
|
||||
deadmobTimers = {计时源方块名: 规则dict} # 只收 type=="timer" 的规则
|
||||
"""
|
||||
|
||||
def __init__(self, namespace, systemName):
|
||||
"""
|
||||
初始化:收集计时型规则,并注册到统一分发器的计时路由表。
|
||||
"""
|
||||
super(DeadmobTimerService, self).__init__(namespace, systemName)
|
||||
print "===== DeadmobTimerService init ====="
|
||||
|
||||
# ---- 只收集计时型规则(source → 规则) ----
|
||||
self.deadmobTimers = {}
|
||||
for sw in DEADMOB_SWITCHES:
|
||||
if sw.get("type") == "timer":
|
||||
self.deadmobTimers[sw["source"]] = sw
|
||||
|
||||
# ---- 注册到分发器的计时路由表(tick 事件按方块名路由到这里) ----
|
||||
FeatureDispatcher.registerTimer(self)
|
||||
|
||||
# ============================================================================
|
||||
# 一、给 FeatureDispatcher 的接口
|
||||
# ============================================================================
|
||||
|
||||
def getTimerBlocks(self):
|
||||
"""
|
||||
返回本功能的所有"计时源方块"集合。
|
||||
FeatureDispatcher 靠它构建 方块名→本实例 的计时路由表(timerOwner)。
|
||||
"""
|
||||
return set(self.deadmobTimers.keys())
|
||||
|
||||
def handleBlockEntityTick(self, args):
|
||||
"""
|
||||
由 FeatureDispatcher 转发的计时方块 tick 入口。
|
||||
只有 blockName 在计时路由表里才会被调用。
|
||||
"""
|
||||
dim = args.get("dimension")
|
||||
pos = (args.get("posX"), args.get("posY"), args.get("posZ"))
|
||||
blockName = args.get("blockName", "")
|
||||
self._tickProcess(dim, pos, blockName)
|
||||
|
||||
def startTimer(self, dim, pos, blockName, playerId=None):
|
||||
"""
|
||||
接口完整性保留。鱼晾干为 autoStart=True(挂上自动开始),
|
||||
本功能右键服务不会转发 startTimer,此方法通常不会被调用。
|
||||
"""
|
||||
recipe = self.deadmobTimers.get(blockName)
|
||||
if not recipe:
|
||||
return
|
||||
self._writeTimerData(dim, pos, recipe, playerId)
|
||||
|
||||
# ============================================================================
|
||||
# 二、tick 检查与到点转换
|
||||
# ============================================================================
|
||||
|
||||
def _tickProcess(self, dim, pos, blockName):
|
||||
"""
|
||||
tick 主逻辑:
|
||||
1) 按事件自带的当前方块名判断是否计时源
|
||||
2) 是计时源但没有计时数据 → autoStart=True 自动补写计时(挂上即自动晾干)
|
||||
3) 有计时数据 → 到点执行转换(重启后时间戳照常生效)
|
||||
方块被拆掉后 tick 停止、数据随方块消失,不会在原位置继续计时转换。
|
||||
注意:GetBlockEntityData 返回的对象不支持 .get(),这里统一用 [] 读取。
|
||||
"""
|
||||
try:
|
||||
recipe = self.deadmobTimers.get(blockName)
|
||||
if not recipe:
|
||||
return # 当前方块不是计时源(tick 只对计时源方块开启)
|
||||
|
||||
blockData = blockEntityDataComp.GetBlockEntityData(dim, pos)
|
||||
if blockData is None:
|
||||
return
|
||||
|
||||
# 读取计时标识;没有或与当前方块不符
|
||||
try:
|
||||
tid = blockData["rtdd_tid"]
|
||||
except Exception:
|
||||
tid = ""
|
||||
if tid != blockName:
|
||||
# 鱼晾干 autoStart=True:挂上后自动补写计时
|
||||
if recipe.get("autoStart", True):
|
||||
self._writeTimerData(dim, pos, recipe, None)
|
||||
print "[Deadmob-Tick-Start]", blockName, pos
|
||||
return
|
||||
|
||||
tstart = blockData["rtdd_tstart"]
|
||||
tdur = blockData["rtdd_tdur"]
|
||||
|
||||
if time.time() - tstart >= tdur:
|
||||
self._finishTimer(dim, pos, recipe, blockData)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _finishTimer(self, dim, pos, recipe, data):
|
||||
"""
|
||||
计时到点:确认方块仍是计时源,才执行转换(晾干无环境条件检查)。
|
||||
- 方块已被玩家改变 → 清掉过期计时
|
||||
- 转换后清除计时数据,若启动者在线发完成提示
|
||||
"""
|
||||
try:
|
||||
cur = comp.CreateBlockInfo(LevelId).GetBlockNew(pos, dim)
|
||||
if cur and cur.get("name") != recipe["source"]:
|
||||
# 方块已被玩家改变 → 清掉过期计时
|
||||
self._clearTimerData(dim, pos)
|
||||
return
|
||||
# 替换方块
|
||||
self.replaceBlock(pos, dim, recipe["target"], None)
|
||||
print "[Deadmob-Tick-Finish]", recipe["source"], "->", recipe["target"], pos
|
||||
nextRecipe = self.deadmobTimers.get(recipe["target"])
|
||||
if nextRecipe:
|
||||
# 下一级计时(换新的开始时间戳)
|
||||
self._writeTimerData(dim, pos, nextRecipe, data["rtdd_tp"])
|
||||
else:
|
||||
# 整条链结束:清数据 + 完成提示
|
||||
self._clearTimerData(dim, pos)
|
||||
doneTip = recipe.get("doneTip")
|
||||
if doneTip:
|
||||
self._notifyIfOnline(data["rtdd_tp"], doneTip)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# ============================================================================
|
||||
# 三、计时数据读写(存在方块实体数据里,重启不丢)
|
||||
# ============================================================================
|
||||
|
||||
def _writeTimerData(self, dim, pos, recipe, playerId=None):
|
||||
"""把 计时标识+开始时刻+时长+启动者 写进方块实体数据"""
|
||||
try:
|
||||
blockData = blockEntityDataComp.GetBlockEntityData(dim, pos)
|
||||
if blockData is None:
|
||||
return
|
||||
now = time.time()
|
||||
blockData["rtdd_tid"] = recipe["source"]
|
||||
blockData["rtdd_tstart"] = now
|
||||
blockData["rtdd_tdur"] = recipe.get("time", 300)
|
||||
blockData["rtdd_tp"] = playerId if playerId else ""
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _clearTimerData(self, dim, pos):
|
||||
"""清除方块上的计时数据"""
|
||||
try:
|
||||
blockData = blockEntityDataComp.GetBlockEntityData(dim, pos)
|
||||
if blockData is None:
|
||||
return
|
||||
blockData["rtdd_tid"] = ""
|
||||
blockData["rtdd_tstart"] = 0
|
||||
blockData["rtdd_tdur"] = 0
|
||||
blockData["rtdd_tp"] = ""
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# ============================================================================
|
||||
# 四、工具方法:完成提示
|
||||
# ============================================================================
|
||||
|
||||
def _notifyIfOnline(self, playerId, tip):
|
||||
"""若启动者在线,发送完成提示(离线则跳过)"""
|
||||
if not tip or not playerId:
|
||||
return
|
||||
try:
|
||||
ids = comp.CreateGame(LevelId).GetAllPlayerIds()
|
||||
if playerId in ids:
|
||||
comp.CreateMsg(playerId).NotifyOneMessage(playerId, tip)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# ============================================================================
|
||||
# 五、工具方法:替换方块
|
||||
# ============================================================================
|
||||
|
||||
def replaceBlock(self, pos, dim, newBlockName, playerId=None):
|
||||
"""
|
||||
替换指定位置的方块。
|
||||
newBlockName="minecraft:air" 表示清除该方块。
|
||||
playerId 可为 None(计时回调时没有玩家),用 LevelId 创建组件。
|
||||
"""
|
||||
creator = playerId if playerId is not None else LevelId
|
||||
aux = 0
|
||||
try:
|
||||
oldBlock = comp.CreateBlockInfo(creator).GetBlockNew(pos, dim)
|
||||
aux = oldBlock.get("aux", 0)
|
||||
except Exception:
|
||||
aux = 0
|
||||
comp.CreateBlockInfo(creator).SetBlockNew(
|
||||
pos,
|
||||
blockDict={"name": newBlockName, "aux": aux},
|
||||
oldBlockHandling=0,
|
||||
dimensionId=dim)
|
||||
Whitespace-only changes.
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"format_version": "1.10",
|
||||
"minecraft:item": {
|
||||
"description": {
|
||||
"identifier": "rtdd:grape",
|
||||
"category": "xy_ingredients"
|
||||
},
|
||||
"components": {
|
||||
"minecraft:max_stack_size": 64,
|
||||
"minecraft:use_animation": "eat",
|
||||
"minecraft:use_duration": 1.6,
|
||||
"minecraft:food": {
|
||||
"nutrition": 2,
|
||||
"saturation_modifier": "low"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"format_version": "1.10",
|
||||
"minecraft:item": {
|
||||
"description": {
|
||||
"identifier": "rtdd:orange",
|
||||
"category": "xy_ingredients"
|
||||
},
|
||||
"components": {
|
||||
"minecraft:max_stack_size": 64,
|
||||
"minecraft:use_animation": "eat",
|
||||
"minecraft:use_duration": 1.6,
|
||||
"minecraft:food": {
|
||||
"nutrition": 2,
|
||||
"saturation_modifier": "low"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"format_version": "1.10",
|
||||
"minecraft:item": {
|
||||
"description": {
|
||||
"identifier": "rtdd:peach",
|
||||
"category": "xy_ingredients"
|
||||
},
|
||||
"components": {
|
||||
"minecraft:max_stack_size": 64,
|
||||
"minecraft:use_animation": "eat",
|
||||
"minecraft:use_duration": 1.6,
|
||||
"minecraft:food": {
|
||||
"nutrition": 2,
|
||||
"saturation_modifier": "low"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user