死亡 生成 黄豆获取1

This commit is contained in:
wenhe committed 2026-09-24 12:53:16 +08:00
1 parent faa3a22631
commit 104603fd64
6 files changed
+654 -2

No files matched your search

@@ -44,6 +44,10 @@ from ...BlueFoxTools.SpecialFunciton.BlueFoxSystem import BlueFoxServerSystem
from ...BlueFoxTools.ToolsConfig import LevelId
from ...BlueFoxTools.ModConfig import MOD_NAME
# ---- 死亡生物·生成引擎: 玩家杀猪/牛/羊/鱿鱼/村民 -> 尸体方块 ----
# 它是"普通逻辑类"(自己不监听), 由本分发器实例化并把 ServerEntityDieEvent 转给它。
from ..siwangshengwu.service.DeadmobGeneratorService import DeadmobGeneratorService
# ---- 汤锅盛出自定义事件名(与客户端 ClientFeatureDispatcher TAKEOUT_EVENT 一致) ----
TAKEOUT_EVENT = "TakeoutRequest"
@@ -112,6 +116,7 @@ class FeatureDispatcher(BlueFoxServerSystem):
timerSet = set()
tryUseOwner = {} # 物品名 -> 服务: 手持"直接吃"物品(长按使用)路由
tryUseSet = set()
grassDropOwners = [] # 破坏"草/杂草"时概率掉食材(如黄豆)的服务列表; OnDestroy 逐个尝试
# ---- 拆方块掉落映射: 方块名 → 掉落对应的原版生食材 ----
# 玩家左键破坏这些"放置的半成品/死鱼"方块时, 取消默认掉落, 改为掉对应的原版生食材
@@ -166,6 +171,15 @@ class FeatureDispatcher(BlueFoxServerSystem):
cls.tryUseOwner[item] = service
cls.tryUseSet.add(item)
@classmethod
def registerGrassDrop(cls, service):
"""
破坏草/杂草掉食材(如黄豆)的服务初始化时调用:把自己挂进 grassDropOwners。
OnDestroy 遇到草/杂草方块会逐个调用这些服务的 handleGrassDrop(args)。
"""
if service not in cls.grassDropOwners:
cls.grassDropOwners.append(service)
@classmethod
def startTimerFor(cls, blockName, dim, pos, playerId=None):
"""
@@ -190,6 +204,7 @@ class FeatureDispatcher(BlueFoxServerSystem):
super(FeatureDispatcher, self).__init__(namespace, systemName)
print "===== FeatureDispatcher init ====="
self._cancelItemUse = {} # 将要取消下一次物品使用的玩家ID集合(防止喝药水等)
self._deadmobGen = DeadmobGeneratorService() # 死亡生物·生成引擎(杀猪牛羊等->尸体方块)
self.ListenEvent()
def Destroy(self):
@@ -202,15 +217,19 @@ class FeatureDispatcher(BlueFoxServerSystem):
def ListenEvent(self):
"""
注册六个事件:
注册七个事件:
1. ServerBlockUseEvent —— 玩家右键方块(所有功能的右键转换入口)
2. ServerItemUseOnEvent —— 手持物品右键方块表面(如放置生鸡肉)
3. ServerItemTryUseEvent —— 玩家尝试使用物品(拦截喝药水/吃食物等默认行为)
4. ServerBlockEntityTickEvent —— 计时方块每tick触发(烤制/饲料变空等,持久化计时)
5. ServerPlayerTryDestroyBlockEvent —— 玩家拆方块(放置的死鱼/生鸡肉掉回原版生食材)
5. ServerPlayerTryDestroyBlockEvent —— 玩家拆方块(放置的死鱼/生鸡肉掉回原版生食材;
以及草/杂草 概率掉黄豆→ 转给 shicai 的 handleGrassDrop)
6. ItemUseOnAfterServerEvent —— 放置方块/生态后触发(携带放置者 entityId)。
用于"创造模式直接放生态"也能拿到是谁放的 → 记录到计时源方块 rtdd_tp,
让计时源服务能"谁放提示发给谁"(如烧烤直接放烤乳猪也提示营火)。
7. MobDieEvent —— 生物死亡(玩家杀死猪/牛/羊/鱿鱼/村民 →
转给 _deadmobGen 生成尸体方块)。注意: 网易该 SDK 里死亡事件叫 MobDieEvent
(参数 id=死亡实体, attacker=击杀者), 不是 ServerEntityDieEvent。
"""
self.ListenForEvent(serverApi.GetEngineNamespace(),
serverApi.GetEngineSystemName(),
@@ -230,6 +249,9 @@ class FeatureDispatcher(BlueFoxServerSystem):
self.ListenForEvent(serverApi.GetEngineNamespace(),
serverApi.GetEngineSystemName(),
"ItemUseOnAfterServerEvent", self, self.OnItemPlaceAfter)
self.ListenForEvent(serverApi.GetEngineNamespace(),
serverApi.GetEngineSystemName(),
"MobDieEvent", self, self.OnEntityDie)
# 7. TakeoutRequest —— 客户端拦截(碗/盘,汤锅/炒锅/油锅成品)放置预览后, 显式请求服务端盛出。
# 客户端系统 ClientFeatureDispatcher 用 NotifyToServer(TAKEOUT_EVENT) 发送,
# 服务端以 MOD_NAME + 客户端系统名 监听该自定义事件。
@@ -268,6 +290,9 @@ class FeatureDispatcher(BlueFoxServerSystem):
self.UnListenForEvent(serverApi.GetEngineNamespace(),
serverApi.GetEngineSystemName(),
"ItemUseOnAfterServerEvent", self, self.OnItemPlaceAfter)
self.UnListenForEvent(serverApi.GetEngineNamespace(),
serverApi.GetEngineSystemName(),
"MobDieEvent", self, self.OnEntityDie)
self.UnListenForEvent(MOD_NAME, "ClientFeatureDispatcher",
TAKEOUT_EVENT, self, self.OnTakeoutRequest)
self.UnListenForEvent(MOD_NAME, "ClientFeatureDispatcher",
@@ -630,6 +655,17 @@ class FeatureDispatcher(BlueFoxServerSystem):
# 主入口5:玩家拆方块 → 放置的死鱼/生鸡肉掉回原版生食材
# ============================================================================
def OnEntityDie(self, args):
"""
玩家杀死生物时触发(ServerEntityDieEvent)。
转给死亡生物·生成引擎: 若击杀者是玩家且被杀是 猪/牛/羊/鱿鱼/村民,
在死亡位置生成对应尸体方块。
"""
try:
self._deadmobGen.OnEntityDie(args)
except Exception as e:
print "[FD-OnEntityDie-ERR]", e
def OnDestroy(self, args):
"""
玩家拆方块时触发。
@@ -642,6 +678,13 @@ class FeatureDispatcher(BlueFoxServerSystem):
blockName = args.get("fullName") or args.get("blockName")
args["blockName"] = blockName # 统一成 blockName 字段, 服务内部直接用
# 0. 草/杂草 概率掉食材(黄豆): 逐个尝试挂了 handleGrassDrop 的服务。
# 如 shicai: 打草概率掉黄豆, 不取消破坏本身。
if self.grassDropOwners:
for _s in self.grassDropOwners:
if hasattr(_s, "handleGrassDrop") and _s.handleGrassDrop(args):
return
# 1. 优先: 归属的右键服务实现了 handleDestroy → 交给服务处理(逐个 fallback)
services = self.blockOwnerAll.get(blockName)
if services:
@@ -107,4 +107,14 @@ DESTROY_DROP = {
"rtdd:cutscallions_2": "rtdd:scallions",
"rtdd:cutscallions_3": "rtdd:scallions",
"rtdd:cutscallions_4": "rtdd:scallions",
}
# ---- 打碎草/杂草 概率获得黄豆(和黄原版打草掉小麦种子一样, ~14%) ----
# 黄豆物品与石磨(CHUJU)共用同一物品 rtndd:soybean
SOYBEAN_ITEM = "rtdd:soybean" # 黄豆(与石磨同物品, 见 ChujuData)
GRASS_DROP_CHANCE = 0.15 # 打草掉黄豆概率(参照原版小麦种子)
GRASS_BLOCKS = { # 草/杂草方块(网易 tallgrass 含 aux 变体)
"minecraft:tallgrass",
"minecraft:short_grass",
"minecraft:double_plant",
}
@@ -21,6 +21,7 @@
================================================================================
"""
import time
import random
import mod.server.extraServerApi as serverApi
# ---- 网易API基础对象 ----
@@ -36,6 +37,7 @@ from ..ShicaiData import (
CLEAVER_ITEM, CHOP_CHAINS, GARLIC_BLOCK, GARLIC_CHOP_COUNT,
GARLIC_DROP, FLOUR_BLOCK, COATING_RULES, DESTROY_DROP,
MEAT_PRESS_RULES,
SOYBEAN_ITEM, GRASS_DROP_CHANCE, GRASS_BLOCKS,
)
# ---- 统一分发器 ----
@@ -87,6 +89,8 @@ class ShicaiService(BlueFoxServerSystem):
# ---- 注册到统一分发器 ----
FeatureDispatcher.register(self)
# 打碎草掉黄豆: 把本服务的草掉落逻辑挂到分发器的破坏方块路由上
FeatureDispatcher.registerGrassDrop(self)
# ============================================================================
# 一、给 FeatureDispatcher 的接口
@@ -161,6 +165,29 @@ class ShicaiService(BlueFoxServerSystem):
pass
return True
def handleGrassDrop(self, args):
"""由 FeatureDispatcher 破坏方块路由调用的"打草掉黄豆"。
被破坏方块是草/杂草时, 按 GRASS_DROP_CHANCE 概率掉 1 个黄豆掉落物。
只产生掉落, 不取消破坏本身、不设 skip, 原版打草行为保持不变。
返回 True=已处理(是草); False=不属本功能(非草)。"""
blockName = args.get("fullName") or args.get("blockName")
if not blockName or blockName not in GRASS_BLOCKS:
return False
playerId = args.get("playerId")
if not playerId:
return True
# 概率掉落黄豆
if random.random() <= GRASS_DROP_CHANCE:
x, y, z = args.get("x"), args.get("y"), args.get("z")
dim = comp.CreateDimension(playerId).GetEntityDimensionId()
try:
self.CreateEngineItemEntity(
{"itemName": SOYBEAN_ITEM, "count": 1, "auxValue": 0},
dim, (x, y + 0.5, z))
except Exception:
pass
return True
def handleBlockUse(self, args):
"""由 FeatureDispatcher 转发的右键方块入口"""
blockName = args.get("blockName")
@@ -0,0 +1,191 @@
# -*- coding: utf-8 -*-
"""
================================================================================
死亡生物·生成引擎(服务端逻辑类)
================================================================================
功能:
玩家杀死对应生物(牛/猪/羊/鱿鱼/村民)后, 在该生物死亡位置附近生成对应
死亡方块(尸体), 供其后的"挂肉钩挂放/切肉"体系使用。
【这个类为什么是"普通类"、不继承 BlueFoxServerSystem、也不监听事件】
全模组只有 FeatureDispatcher 监听游戏事件(架构约定), 本类不监听任何东西。
由于此前本类继承 BlueFoxServerSystem 想靠框架自动注册监听, 但新类未被打进
运行环境 -> 一直没被实例化, 日志里连 init 都没有。现在改为:
- 本类只是"逻辑处理类": __init__ 不需要 namespace/systemName, 方法用模块级
comp / LevelId 直接工作。
- 真正的 ServerEntityDieEvent 监听在 FeatureDispatcher, 由它实例化本类并
把事件转发给本类的 OnEntityDie。
【黄豆打草掉黄豆】
已移回"乡野食材"(shicai): 常量在 ShicaiData, 掉落方法在 ShicaiService,
由 FeatureDispatcher 的破坏方块路由调用。本类不再包含黄豆逻辑。
【死亡方块清单】
死亡方块: 牛=rtdd:dead_cow, 猪=rtdd:dead_pig, 羊=rtdd:dead_sheep,
鱿鱼=rtdd:deadsquida, 村民=rtdd:dead_villager
对应实体类型(minecraft EntityType 枚举名): Cow/Pig/Sheep/Squid/Villager
================================================================================
"""
import mod.server.extraServerApi as serverApi
# ---- 网易API基础对象 ----
comp = serverApi.GetEngineCompFactory() # 组件工厂
# ---- 只有方块信息组件用 LevelId(查询全维方块) ----
from ....BlueFoxTools.ToolsConfig import LevelId
# ---- 尸体生成延迟(秒): 死亡 1.5 秒后才生成尸体方块, 不会刚死瞬间就出现 ----
GEN_DELAY = 1.5
class DeadmobGeneratorService(object):
"""
死亡生物·生成引擎(逻辑类, 由 FeatureDispatcher 实例化并调用)
"""
def __init__(self):
print "===== DeadmobGeneratorService 逻辑类已挂载到 FeatureDispatcher ====="
# ============================================================================
# 一、玩家杀死生物 → 生成死亡方块(由 FeatureDispatcher 的
# MobDieEvent 监听转发到这里)
# ============================================================================
def OnEntityDie(self, args):
"""
玩家杀死对应生物时触发(网易死亡事件 MobDieEvent)。args:
id 死亡实体
attacker 伤害来源(击杀者)实体(近战=玩家; 远程=玩家射出的抛射物)
仅当击杀者是玩家、且死亡生物属于牛/猪/羊/鱿鱼/村民时, 在死亡位置延迟生成尸体。
"""
try:
killedId = args.get("id") or args.get("entityId")
attackerId = args.get("attacker") or args.get("attackedByEntId")
if not killedId or not attackerId:
return
# 解析"由玩家击杀": 近战 attacker 就是玩家; 远程 attacker 是抛射物, 取其发射者
if not self._resolvePlayerAttacker(attackerId):
return
# 命中"被玩家击杀": 打印实际类型串与映射结果, 便于确认事件与类型判断是否命中
blockName = self._mobTypeToBlock(killedId)
print "[DeadmobGen-Attack] killedType=%s -> block=%s" % (
self._mobTypeStr(killedId), blockName)
if not blockName:
return
# 死亡位置: 取实体脚下坐标(死亡时实体仍在场)
pos = self._getFootPos(killedId)
if not pos:
return
px, py, pz = (int(pos[0]), int(pos[1]), int(pos[2]))
dim = self._getDim(killedId)
# 该格若非空气则垫高找到可放格, 避免覆盖已存在的方块(如草/其他方块)
target = (px, py, pz)
if not self._isPlaceable(target, dim):
for dy in range(1, 4):
candidate = (px, py + dy, pz)
if self._isAir(candidate, dim):
target = candidate
break
else:
return # 周围都放不下
# 延迟生成尸体方块: 死亡后约 1.5 秒才出现(不是刚死瞬间)
comp.CreateGame(LevelId).AddTimer(
GEN_DELAY, self._placeBlockDelayed, target, blockName, dim)
except Exception as e:
print "[DeadmobGen-ERR]", e
def _resolvePlayerAttacker(self, attackerId):
"""判断击杀者是否玩家(近战直接是玩家; 远程是玩家射出的抛射物)。
返回 True 表示由玩家击杀。"""
if not attackerId:
return False
if self._isPlayer(attackerId):
return True
# 远程: attacker 是抛射物(箭等), 取发射者, 发射者是玩家则算玩家击杀
try:
owner = comp.CreateBulletAttributes(attackerId).GetSourceEntityId()
return bool(owner) and self._isPlayer(owner)
except Exception:
return False
def _placeBlockDelayed(self, target, blockName, dim):
"""AddTimer 到点后真正放置尸体方块"""
self._setBlock(target, blockName, dim)
print "[DeadmobGen]", blockName, "at", target
def _mobTypeStr(self, entityId):
"""取实体类型字符串(如 minecraft:pig), 小写; 取不到返回空串"""
try:
return (comp.CreateEngineType(entityId).GetEngineTypeStr() or "").lower()
except Exception:
return ""
def _mobTypeToBlock(self, entityId):
"""返回该实体对应的死亡方块名; 非目标生物返回 None。
用 GetEngineTypeStr() 拿类型字符串精确匹配(项目已有用法见 EventFilter),
不使用 EntityType 位掩码(不同版本的枚举/Squid/Villager 存在性不稳)。"""
return {
"minecraft:pig": "rtdd:dead_pig",
"minecraft:cow": "rtdd:dead_cow",
"minecraft:sheep": "rtdd:dead_sheep",
"minecraft:squid": "rtdd:deadsquida",
"minecraft:villager": "rtdd:dead_villager",
"minecraft:villager_v2": "rtdd:dead_villager",
}.get(self._mobTypeStr(entityId))
# ============================================================================
# 二、工具方法
# ============================================================================
def _isPlayer(self, entityId):
try:
ET = serverApi.GetMinecraftEnum().EntityType
v = comp.CreateEngineType(entityId).GetEngineType()
return v & ET.Player == ET.Player
except Exception:
return False
def _getFootPos(self, entityId):
try:
return comp.CreatePos(entityId).GetFootPos()
except Exception:
return None
def _getDim(self, entityId):
try:
return comp.CreateDimension(entityId).GetEntityDimensionId()
except Exception:
return 0
def _isAir(self, pos, dim):
try:
b = comp.CreateBlockInfo(LevelId).GetBlockNew(pos, dim)
return b is None or b.get("name") == "minecraft:air"
except Exception:
return True
def _isPlaceable(self, pos, dim):
"""目标格要么是空气、要么是可通过的草/花等非实体方块(可直接替换)"""
try:
b = comp.CreateBlockInfo(LevelId).GetBlockNew(pos, dim)
if not b:
return True
name = b.get("name", "minecraft:air")
if name == "minecraft:air":
return True
if name in ("minecraft:tallgrass", "minecraft:short_grass",
"minecraft:red_flower", "minecraft:yellow_flower",
"minecraft:double_plant"):
return True
return False
except Exception:
return True
def _setBlock(self, pos, blockName, dim):
"""放置死亡方块, aux 强制 0"""
try:
comp.CreateBlockInfo(LevelId).SetBlockNew(
pos, blockDict={"name": blockName, "aux": 0},
oldBlockHandling=0, dimensionId=dim)
except Exception:
pass