市场贸易实现 bug修复

This commit is contained in:
wenhe committed 2026-09-03 14:09:44 +08:00
1 parent 1f329f8b4c
commit cae9bcb19e
25 files changed
+1300 -187

No files matched your search

@@ -0,0 +1,205 @@
# -*- coding: utf-8 -*-
"""
================================================================================
原版放置·手持原版物品右键方块表面(服务端)
================================================================================
统一处理"手持原版物品右键方块表面 → 放置对应方块"的转换(ServerItemUseOnEvent):
原版生鸡肉 → 生鸡肉(放置后);原版生鲑鱼/鳕鱼 → 死鲑鱼/死鳕鱼。
这些规则原本散落在 烧烤(BarbecueService) 和 死亡生物(DeadmobService) 里,
现在单独拆到本目录统一管理。
【事件监听在哪?】
本类【不】监听任何事件!
所有事件由 FeatureDispatcher(xiangyemod/service/FeatureDispatcher.py)统一监听:
- ServerItemUseOnEvent → OnItemUseOn → 按 itemUseOnOwner 路由到本类 handleItemUseOn
被点方块属于某个"右键方块"服务的管辖(肉钩/条箱/锅具等)时, 分发器会跳过本路径,
交给 ServerBlockUseEvent 处理(挂鱼/放入/做菜), 避免重复。
【本类怎么做】
handleItemUseOn ← 入口:分发器转发"手持物品右键方块表面"事件
├─ 匹配规则 ← 按手持物品名 O(1) 查找
├─ 算目标格 ← 被点方块表面相邻的空格
├─ checkCooldown← 连点冷却
├─ consumeItem ← 消耗1个原版物品
└─ replaceBlock ← 放置对应方块 + 提示
================================================================================
"""
import time
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 ..YuanbanData import ITEM_USE_ON_RULES
# ---- 统一分发器 ----
from ...service.FeatureDispatcher import FeatureDispatcher
# ---- 方块表面方向 → 相邻坐标偏移(0下 1上 2北 3南 4西 5东) ----
FACE_OFFSET = {
0: (0, -1, 0),
1: (0, 1, 0),
2: (0, 0, -1),
3: (0, 0, 1),
4: (-1, 0, 0),
5: (1, 0, 0),
}
class YuanbanService(BlueFoxServerSystem):
"""
原版放置·手持原版物品右键方块表面引擎
【核心数据结构】
itemUseOnRecipes = {物品名: 规则dict} # O(1) 查找
cooldown = {key: 时间戳} # 连点冷却表
"""
def __init__(self, namespace, systemName):
"""
初始化:构建放置规则字典,并注册到统一分发器。
"""
super(YuanbanService, self).__init__(namespace, systemName)
print "===== YuanbanService init ====="
# ---- 构建放置规则字典(O(1) 查找) ----
self.itemUseOnRecipes = {}
for rule in ITEM_USE_ON_RULES:
self.itemUseOnRecipes[rule["item"]] = rule
# ---- 右键冷却表 ----
self.cooldown = {}
# ---- 注册到统一分发器(只注册 itemUseOn, 不注册任何右键方块) ----
FeatureDispatcher.register(self)
# ============================================================================
# 一、给 FeatureDispatcher 的接口
# ============================================================================
def getFeatureBlocks(self):
"""本功能不管辖任何右键方块, 返回空"""
return set()
def getItemUseOnRules(self):
"""返回"手持物品右键方块表面"的规则: {物品名: 规则dict}"""
return self.itemUseOnRecipes
def handleItemUseOn(self, args):
"""
由 FeatureDispatcher 转发的"手持物品右键方块表面"事件。
在方块表面放置对应方块(生鸡肉/死鲑鱼/死鳕鱼)。
返回 True=已处理,False=当前组合无匹配规则。
"""
itemName = args.get("itemName")
# ServerItemUseOnEvent 的玩家键是 entityId,不是 playerId
playerId = args.get("playerId") or args.get("entityId")
rule = self.itemUseOnRecipes.get(itemName)
if not rule:
return False
x, y, z = args.get("x"), args.get("y"), args.get("z")
face = args.get("face", 1)
dim = comp.CreateDimension(playerId).GetEntityDimensionId()
# 计算被点方块表面相邻的目标格
off = FACE_OFFSET.get(face, (0, 1, 0))
targetPos = (x + off[0], y + off[1], z + off[2])
# 目标格必须为空(可放置)
try:
cur = comp.CreateBlockInfo(LevelId).GetBlockNew(targetPos, dim)
if cur and cur.get("name") != "minecraft:air":
return False
except Exception:
return False
# 连点冷却
key = (dim, targetPos[0], targetPos[1], targetPos[2])
if not self.checkCooldown(key, 0.5):
return True
# 消耗 1 个原版物品
itemDict = self.getCarriedItem(playerId)
if not itemDict:
return False
self.consumeItem(playerId, itemDict, rule.get("consume", 1))
# 放置对应方块
self.replaceBlock(targetPos, dim, rule["target"], playerId)
# 放置成功提示
if rule.get("tip"):
comp.CreateMsg(playerId).NotifyOneMessage(playerId, rule["tip"])
return True
# ============================================================================
# 二、工具方法:获取玩家主手物品
# ============================================================================
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):
"""
替换指定位置的方块。
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 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