Files
rtdd/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/Function.py
T
2026-07-20 16:20:06 +08:00

426 lines
18 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# coding=utf-8
"""
Function.py — 模组所有"自定义方块行为"的定义文件
=====================================================
这个文件定义了游戏中每种烹饪方块被玩家右键点击后会做什么。
每个方块都是一个"类"(class),类里写了它的各种行为。
整个文件的结构:
1. 导入(import)—— 把需要用到的工具引进来
2. CookingStateEnum —— 烹饪状态的标记(煮/不煮)
3. BaseBlock —— 所有方块的"爸爸",定义了公共行为
4. WoK —— 空炒锅
5. OilCan —— 油瓶
6. CookingWok —— 加了油的炒锅
7. CarrotAndBeef —— 胡萝卜牛肉(第一阶段:翻炒中)
8. CarrotAndBeef1 —— 胡萝卜牛肉(第二阶段:快熟了)
9. blockIndex —— 方块ID → 类的对照表(注册表)
10. GetBaseBlock —— 根据坐标查找方块实例的工具函数
"""
import time
import ModUtil as util # 自己写的工具函数包(Normal和ServerUtil)
from BlueFoxTools.ItemTools import CreateItemDict # 快速创建一个物品数据包
from BlueFoxTools.SpecialFunciton.BlockBlock import BlockBlock # 多格方块系统(解决大模型消失问题)
from BlueFoxTools.SystemTools import SystemTools # 系统工具箱(用来找到MainServer)
from BlueFoxTools.ToolsConfig import * # 全局配置和工具(LevelId, serverCompFactory等)
class CookingStateEnum(object):
"""
烹饪状态标记
就像开关一样,告诉游戏当前方块是在"煮"还是"默认"状态
"""
Default = -1 # 默认状态:没在煮
Boil = 0 # 煮的状态
class BaseBlock(BlockBlock):
"""
===== 所有烹饪方块的"爸爸"类 =====
所有具体的烹饪方块(炒锅、油瓶、牛肉锅...)都继承这个类。
这里定义了所有方块共有的东西:
- 翻炒次数(stirFryProperty)
- 煮的时间(boilProperty)
- 拆掉方块时掉落物品(Destroy)
- 方块变身(Translater)
- 存档数据(SaveData)
"""
# ====== 类级别的变量(所有实例共享) ======
instances = {} # 全局字典:记录世界上所有活着的BaseBlock实例
# 键 = 坐标(如(10, 64, 5, 0)),值 = 方块实例本身
dropItems = [] # 拆掉方块后会掉落的物品列表(子类会覆盖)
def __init__(self, posDim, aux, posDims=None):
"""
方块"出生"时调用(玩家放下方块时触发)
:param posDim: 位置坐标 (x, y, z, 维度)
:param aux: 方块朝向(0=南, 1=西, 2=北, 3=东)
:param posDims: 多格方块的所有坐标列表(如果不是多格方块就是None)
"""
# 第一步:先让"爸爸"(BlockBlock)完成它的初始化
# 这一步会处理多格方块的放置、数据加载等
super(BaseBlock, self).__init__(posDim, aux, posDims)
# 第二步:从方块存档数据里读取"翻炒次数"和"煮的时间"
# 如果之前存过档,就能恢复;如果是新方块,会是None
self.__stirFryProperty = self.dataComp['stirFry']
self.__boilProperty = self.dataComp['boil']
# 第三步:把自己登记到全局实例字典里(方便后续查找)
BaseBlock.instances[self.centerPosDim] = self
# 第四步:设置初始状态
self.cookingState = CookingStateEnum.Default # 默认不在煮
self.isFinish = False # 还没熟
# 第五步:拿到MainServer的引用(后面掉落物品时需要用到)
self.serverSystem = SystemTools.GetSystemByClsName('MainServer')
@classmethod
def Instances(cls):
"""获取所有活着的BaseBlock实例的列表"""
return cls.instances.values()
def Tick(self):
"""每游戏帧调用一次(目前为空,留给子类扩展)"""
pass
def BlockUse(self, playerId, itemDict):
"""
玩家右键点击方块时触发
:param playerId: 点击方块的玩家ID
:param itemDict: 玩家手里拿的物品信息
每个子类会重写这个方法,定义自己的右键行为
"""
pass
# ====== 翻炒属性 ======
@property
def stirFryProperty(self):
# type:()->int
"""
读取"翻炒了多少次"
如果从来没翻过(None),就返回0
"""
return self.__stirFryProperty if self.__stirFryProperty is not None else 0
@stirFryProperty.setter
def stirFryProperty(self, value):
"""设置"翻炒次数"(每翻一次就+1)"""
self.__stirFryProperty = value
# ====== 煮的时间属性 ======
@property
def boilProperty(self):
# type:()->float
"""
读取"煮了多久"(单位:秒)
如果从来没煮过(None),就返回0.0
"""
return self.__boilProperty if self.__boilProperty is not None else 0.0
@boilProperty.setter
def boilProperty(self, value):
"""设置"煮的时间"(单位:秒)"""
self.__boilProperty = value
def SaveData(self):
"""
把翻炒次数和煮的时间写入方块存档数据
这样关闭游戏再打开,数据不会丢失
"""
self.dataComp['stirFry'] = self.__stirFryProperty
self.dataComp['boil'] = self.__boilProperty
def Destroy(self, drop=0):
"""
方块被拆掉时调用
:param drop: 1=掉落物品,0=不掉落
流程:
1. 从全局实例字典里把自己删掉
2. 调用父类的Destroy清除方块
3. 如果需要掉落,把dropItems列表里的物品一个一个丢出来
"""
# 从实例字典里注销自己
del BaseBlock.instances[self.centerPosDim]
# 调用BlockBlock的Destroy(参数0=不让原版方块掉落,我们自己控制掉落)
super(BaseBlock, self).Destroy(0)
# 如果需要掉落物品,就把dropItems里的东西一个个生成到世界上
if drop:
for dropItem in self.dropItems:
self.serverSystem.CreateEngineItemEntity(
dropItem, self.centerPosDim[3], self.centerPosDim[:3]
)
def Translater(self, newBlock):
"""
让方块"变身"成另一个方块
:param newBlock: 目标方块的ID,如 'rtdd:cookinggarlic'
流程:
1. 在blockIndex注册表里查这个ID对应的类
2. 如果找到了,先销毁当前方块,再在同一个位置创建新方块
"""
cls = blockIndex.get(newBlock)
if cls:
self.Destroy() # 把自己拆了
cls.getInstance(self.centerPosDim) # 在同一个位置创建新方块实例
# ============================================================================
# 下面是具体的方块类,每个类代表游戏里的一种方块
# ============================================================================
class WoK(BaseBlock):
"""
===== 空炒锅 =====
方块ID: rtdd:wok
行为:玩家右键时,如果手里有油且锅下面是营火,锅就变成"带油的煎锅"
"""
# 告诉BlockBlock系统:这个方块只占1格,ID是rtdd:wok
blockList = {(0, 0, 0): {'name': 'rtdd:wok', 'aux': 0}}
def __init__(self, *args):
super(WoK, self).__init__(*args)
def BlockUse(self, playerId, itemDict):
"""
玩家右键空炒锅时:
条件1:手里拿的物品有油(GetItemOil检查标签)
条件2:锅下面那一格是营火(IsBlockBelow检查)
都满足 → 锅变身 + 油用掉 + 播放滋滋声
"""
if util.Normal.GetItemOil(itemDict) and util.Normal.IsBlockBelow(
self.centerPosDim, 'minecraft:campfire'
):
# 锅变身成"带油的煎锅"(cookinggarlic = cooking + garlic,意思是煎蒜/煎东西)
self.Translater('rtdd:cookinggarlic')
# 把油标签从物品上去掉(油用掉了)
util.Normal.SetItemOil(itemDict, False)
# 把更新后的物品还给玩家手里
serverCompFactory.CreateItem(LevelId).SpawnItemToPlayerCarried(itemDict, playerId)
# 让附近所有玩家听到滋滋声
util.ServerUtil.PlaySoundToAllClient(
self.centerPosDim, 'random.fizz', 2.4
)
OID_CAN_MAX_SIZE = 16 # 油瓶最大能装多少份油(注意:OID是拼写错误,应该是OIL)
class OilCan(BaseBlock):
"""
===== 油瓶 =====
方块ID: rtdd:oilcan
行为:右键可以存油/取油,最多存16份
"""
blockList = {(0, 0, 0): {'name': 'rtdd:oilcan', 'aux': 0}}
def __init__(self, *args):
super(OilCan, self).__init__(*args)
# 从存档数据里读取当前油量
self.__oil = self.dataComp['oil']
# 初始化时自动装满油
self.SetFill()
def SetFill(self):
"""把油瓶装满(16份)"""
self.oil = OID_CAN_MAX_SIZE
def BlockUse(self, playerId, itemDict):
"""
玩家右键油瓶时:
情况A:手里有油 → 往瓶里加油(if self.oil < 16 才加)
情况B:手里没油 → 从瓶里取油(if self.oil > 0 才取)
最后显示剩余油量提示
"""
if util.Normal.GetItemOil(itemDict):
# ===== 情况A:玩家手里有油,往瓶里存 =====
if self.oil < OID_CAN_MAX_SIZE:
self.oil += 1 # 瓶里油量+1
# 把物品上的油标签去掉
util.Normal.SetItemOil(itemDict, False)
serverCompFactory.CreateItem(playerId).SpawnItemToPlayerCarried(itemDict, playerId)
elif self.oil > 0:
# ===== 情况B:玩家手里没油,从瓶里取 =====
# 给物品贴上"有油"标签
util.Normal.SetItemOil(itemDict, True)
serverCompFactory.CreateItem(playerId).SpawnItemToPlayerCarried(itemDict, playerId)
self.oil -= 1 # 瓶里油量-1
# 在玩家屏幕上显示剩余油量(如"剩余油量8/16")
serverCompFactory.CreateGame(LevelId).SetOneTipMessage(
playerId, '剩余油量{}/{}'.format(self.oil, OID_CAN_MAX_SIZE)
)
@property
def oil(self):
"""读取油量,如果是None(新方块)就返回0"""
return 0 if self.__oil is None else self.__oil
@oil.setter
def oil(self, value):
"""设置油量"""
self.__oil = value
def SaveData(self):
"""
存档:先让父类存翻炒/煮的数据,再额外存油量
"""
super(OilCan, self).SaveData()
self.dataComp['oil'] = self.__oil
class CookingWok(BaseBlock):
"""
===== 带油的煎锅(准备炒菜状态) =====
方块ID: rtdd:cookinggarlic
行为:玩家右键时,如果手里是牛肉,锅就变成"胡萝卜牛肉锅"
BUG:目前没有消耗玩家手里的牛肉!玩家可以无限放牛肉。
"""
blockList = {(0, 0, 0): {'name': 'rtdd:cookinggarlic', 'aux': 0}}
# 拆掉带油锅会掉落空锅
dropItems = [CreateItemDict('rtdd:wok')]
def __init__(self, *args):
super(CookingWok, self).__init__(*args)
def BlockUse(self, playerId, itemDict):
"""
玩家右键带油煎锅时:
如果手里拿的是生牛肉(minecraft:beef)
→ 锅变身成"胡萝卜牛肉锅"(carrotandbeef)
→ 播放滋滋声
"""
if itemDict and itemDict.get('newItemName') == 'minecraft:beef':
self.Translater('rtdd:carrotandbeef')
util.ServerUtil.PlaySoundToAllClient(
self.centerPosDim, 'random.fizz', 0.8
)
class CarrotAndBeef(BaseBlock):
"""
===== 胡萝卜牛肉(第一阶段:翻炒中) =====
方块ID: rtdd:carrotandbeef
行为:玩家用木剑右键 = 翻炒动作
每1.2秒才能翻一次
翻够5次以上 → 变身成第二阶段
"""
blockList = {(0, 0, 0): {'name': 'rtdd:carrotandbeef', 'aux': 0}}
# 拆掉会退锅和生牛肉
dropItems = [CreateItemDict('rtdd:wok'), CreateItemDict('minecraft:beef')]
def __init__(self, *args):
super(CarrotAndBeef, self).__init__(*args)
# 记录上次翻炒的时间,用来判断冷却
self.stirTime = time.time()
def BlockUse(self, playerId, itemDict):
"""
玩家右键胡萝卜牛肉锅时:
1. 先检查距离上次翻炒是否超过1.2秒(冷却时间)
2. 如果没到冷却时间 → 提示"这时候翻动会不好熟的!"
3. 如果到了冷却时间且手里是木剑 → 执行翻炒:
- 翻炒次数+1
- 播放滋滋声
- 播放挥剑动画
- 切换锅的纹理(0→1→2→3循环,模拟翻炒效果)
- 如果翻够5次以上 → 变身成第二阶段(carrotandbeef_1)
"""
stirTime = time.time() # 当前时间
if stirTime - self.stirTime > 1.2: # 冷却时间1.2秒
if itemDict and itemDict.get('newItemName') == 'minecraft:wooden_sword':
# 翻炒次数+1
self.stirFryProperty += 1
# 播放滋滋声
util.ServerUtil.PlaySoundToAllClient(
self.centerPosDim, 'random.fizz', 2.1
)
# 让玩家挥剑(模拟翻炒动作)
util.ServerUtil.PlayAttackToClient(playerId)
# 替换方块纹理(aux 在 0,1,2,3 之间循环,制造翻炒动画效果)
self.ReplaceBlocks({(0, 0, 0): {'name': 'rtdd:carrotandbeef', 'aux': self.stirFryProperty % 4}})
# 翻够5次以上 → 熟了!变身第二阶段
if self.stirFryProperty > 5:
self.Translater('rtdd:carrotandbeef_1')
# 更新最后一次翻炒时间
self.stirTime = stirTime
else:
# 冷却时间还没到,提示玩家
serverCompFactory.CreateGame(LevelId).SetOneTipMessage(playerId, '这时候翻动会不好熟的!')
class CarrotAndBeef1(BaseBlock):
"""
===== 胡萝卜牛肉(第二阶段:快熟了) =====
方块ID: rtdd:carrotandbeef_1
行为:继续用木剑翻炒,冷却时间缩短到0.8秒
翻够3次以上 → 熟了!提示"出锅啦!拿盘子盛出来"
拆掉熟锅 → 掉落熟牛肉(不再是生牛肉!)
"""
blockList = {(0, 0, 0): {'name': 'rtdd:carrotandbeef_1', 'aux': 0}}
# 没熟之前拆掉,退锅和生牛肉
dropItems = [CreateItemDict('rtdd:wok'), CreateItemDict('minecraft:beef')]
def __init__(self, *args):
super(CarrotAndBeef1, self).__init__(*args)
self.stirTime = time.time()
def BlockUse(self, playerId, itemDict):
"""
玩家右键第二阶段牛肉锅时:
1. 冷却时间0.8秒(比第一阶段更快)
2. 没到冷却 → 提示"这时候翻动会不好熟的!"
3. 到了冷却且手里是木剑且还没熟 → 翻炒
4. 到了冷却且手里是木剑且已经熟了 → 提示"出锅啦!"
"""
stirTime = time.time()
if stirTime - self.stirTime > 0.8:
# 还没熟,继续翻炒
if itemDict and itemDict.get('newItemName') == 'minecraft:wooden_sword' and not self.isFinish:
self.stirFryProperty += 1
util.ServerUtil.PlaySoundToAllClient(
self.centerPosDim, 'random.fizz', 2.1
)
util.ServerUtil.PlayAttackToClient(playerId)
self.ReplaceBlocks({(0, 0, 0): {'name': 'rtdd:carrotandbeef_1', 'aux': self.stirFryProperty % 4}})
# 翻够3次以上 → 熟了!
self.isFinish = self.stirFryProperty > 3
# 已经熟了,提示出锅
elif itemDict and itemDict.get('newItemName') == 'minecraft:wooden_sword' and self.isFinish:
serverCompFactory.CreateGame(LevelId).SetOneTipMessage(playerId, '出锅啦!拿盘子盛出来')
else:
serverCompFactory.CreateGame(LevelId).SetOneTipMessage(playerId, '这时候翻动会不好熟的!')
def Destroy(self, drop=0):
"""
拆掉方块时:
如果已经熟了 → 掉落物改成"熟牛肉+空锅"(不再是生牛肉!)
如果还没熟 → 掉落物还是"生牛肉+空锅"(用父类定义的dropItems)
"""
if self.isFinish:
self.dropItems = [CreateItemDict('minecraft:cooked_beef'), CreateItemDict('rtdd:wok')]
super(CarrotAndBeef1, self).Destroy(drop)
# ============================================================================
# blockIndex — 方块ID注册表
# ============================================================================
# 这个字典告诉游戏:"遇到这个方块ID时,用对应的类来处理它"
# 当MainServer捕获到玩家放方块/右键方块事件时,会先来这个表里查
blockIndex = {
'rtdd:wok': WoK, # 空炒锅
'rtdd:cookinggarlic': CookingWok, # 带油的煎锅
'rtdd:carrotandbeef': CarrotAndBeef, # 胡萝卜牛肉(第一阶段)
'rtdd:carrotandbeef_1': CarrotAndBeef1,# 胡萝卜牛肉(第二阶段)
'rtdd:oilcan': OilCan # 油瓶
}
def GetBaseBlock(posDim):
"""
根据坐标查找方块实例
:param posDim: (x, y, z, 维度) 坐标
:return: 该位置的BaseBlock实例,如果没找到返回None
被MainServer调用,用来判断玩家右键的是不是我们的烹饪方块
"""
return BaseBlock.instances.get(posDim)