代码初始化
This commit is contained in:
1 parent
c2f5cd98ff
commit
e3efeb74ad
2439 files changed
+305661
-1
No files matched your search
Whitespace-only changes.
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,358 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# 一盒钉子(dn_sa:box_of_nails):物品 + 方块双形态的补给容器
|
||||
# - 物品耐久用于显示盒内钉子数:max_damage = 65,remaining = 钉子数 + 1
|
||||
# (空盒 remaining = 1,满盒 remaining = 65;+1 是为了避免耐久归零导致物品损坏)
|
||||
# - 手持盒子右键方块:放置为 dn_sa:box_of_nails_block(面向跟随玩家朝向),盒内钉子数存入世界存档(按坐标索引)
|
||||
# - 右键方块:手持钉子时放入(潜行放满,否则放 1 颗);否则取出(潜行全部取出,否则取 1 颗)
|
||||
# - 破坏方块:还原为一个物品,耐久保留盒内钉子数
|
||||
# - 描述("已存储:x/64")走物品的 customTips:生成物品时写进 itemDict,之后由
|
||||
# ChangePlayerItemTipsAndExtraId 覆盖刷新。customTips 会直接覆盖物品 JSON 里的静态文案,
|
||||
# 所以 JSON 里那行 "已存储:1/64" 只是还没刷新时的兜底显示。
|
||||
# 注意:读玩家背包必须用 GetPlayerItem —— GetEntityItem 只适用于生物/左右手/装备,
|
||||
# 官方文档写明"背包不行",用它读背包会一直返回 None,导致描述永远停在 JSON 的静态文案。
|
||||
import math
|
||||
import time
|
||||
|
||||
import mod.server.extraServerApi as serverApi
|
||||
|
||||
compFactory = serverApi.GetEngineCompFactory()
|
||||
|
||||
# 测试参数:与 modMain 共用开关与探针(print + 写文件),仅在 DEBUG_TEST 为 True 时输出
|
||||
try:
|
||||
from Script_dn_sa.modMain import DEBUG_TEST, probe
|
||||
except Exception:
|
||||
DEBUG_TEST = False
|
||||
|
||||
|
||||
def probe(msg):
|
||||
pass
|
||||
|
||||
ITEM_ID = "dn_sa:box_of_nails"
|
||||
BLOCK_ID = "dn_sa:box_of_nails_block"
|
||||
NAILS_ID = "dn_sa:nails"
|
||||
BLOCK_AIR = "minecraft:air"
|
||||
|
||||
NAILS_MAX_STACK = 64 # 单叠钉子上限
|
||||
MAX_CHARGES = 64 # 钉子盒容量
|
||||
MAX_DAMAGE = MAX_CHARGES + 1 # 物品最大耐久
|
||||
CONTENTS_KEY = "dn_sa_box_of_nails_contents" # {"维度:x,y,z": 钉子数}
|
||||
|
||||
# ServerBlockUseEvent 在按住右键时逐 tick 触发,用冷却间隔避免一次长按连续存取
|
||||
COOLDOWN_SECONDS = 0.25 # 每次成功存取后的冷却时间
|
||||
|
||||
# 物品描述(末行动态显示当前钉子数)
|
||||
TIPS_TEXT = "手持钉子点击放入,空手点击取出\n包含:钉子\n已存储:%d/%d"
|
||||
|
||||
# 点击面 -> 法线偏移(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),
|
||||
}
|
||||
|
||||
ITEM_POS = serverApi.GetMinecraftEnum().ItemPosType
|
||||
INVENTORY = ITEM_POS.INVENTORY
|
||||
CARRIED = ITEM_POS.CARRIED
|
||||
|
||||
|
||||
def clamp(v, lo, hi):
|
||||
return lo if v < lo else (hi if v > hi else v)
|
||||
|
||||
|
||||
class BoxOfNailsServer(serverApi.GetServerSystemCls()):
|
||||
def __init__(self, namespace, name):
|
||||
super(BoxOfNailsServer, self).__init__(namespace, name)
|
||||
if DEBUG_TEST:
|
||||
probe("3/4 BoxOfNailsServer 系统实例已创建")
|
||||
self.levelId = serverApi.GetLevelId()
|
||||
self._lastUse = {} # playerId -> 上次成功存取的时间戳(秒)
|
||||
self._tipsSet = {} # (playerId, slot) -> 已写入的描述文本,防重复写入
|
||||
self.ListenForEvent(serverApi.GetEngineNamespace(), serverApi.GetEngineSystemName(),
|
||||
"AddServerPlayerEvent", self, self.OnPlayerJoin)
|
||||
self.ListenForEvent(serverApi.GetEngineNamespace(), serverApi.GetEngineSystemName(),
|
||||
"ServerItemUseOnEvent", self, self.OnItemUseOn)
|
||||
self.ListenForEvent(serverApi.GetEngineNamespace(), serverApi.GetEngineSystemName(),
|
||||
"ServerBlockUseEvent", self, self.OnBlockUse)
|
||||
self.ListenForEvent(serverApi.GetEngineNamespace(), serverApi.GetEngineSystemName(),
|
||||
"ServerPlayerTryDestroyBlockEvent", self, self.OnTryDestroy)
|
||||
self.ListenForEvent(serverApi.GetEngineNamespace(), serverApi.GetEngineSystemName(),
|
||||
"InventoryItemChangedServerEvent", self, self.OnInventoryChanged)
|
||||
|
||||
# ---------- 基础工具 ----------
|
||||
def _blockComp(self):
|
||||
return compFactory.CreateBlockInfo(self.levelId)
|
||||
|
||||
def _extraComp(self):
|
||||
return compFactory.CreateExtraData(self.levelId)
|
||||
|
||||
@staticmethod
|
||||
def _key(dim, x, y, z):
|
||||
return "%d:%d,%d,%d" % (dim, x, y, z)
|
||||
|
||||
@staticmethod
|
||||
def _itemName(itemDict):
|
||||
"""兼容读取物品 identifier:新版物品字典为 newItemName,旧版为 itemName。"""
|
||||
if not itemDict:
|
||||
return None
|
||||
return itemDict.get("newItemName") or itemDict.get("itemName")
|
||||
|
||||
def _loadContents(self):
|
||||
data = self._extraComp().GetExtraData(CONTENTS_KEY)
|
||||
return data if isinstance(data, dict) else {}
|
||||
|
||||
def _saveContents(self, data):
|
||||
self._extraComp().SetExtraData(CONTENTS_KEY, data, True)
|
||||
|
||||
def _getBlockName(self, x, y, z, dim):
|
||||
info = self._blockComp().GetBlockNew((x, y, z), dim)
|
||||
if not info:
|
||||
return None
|
||||
return info.get("name")
|
||||
|
||||
def _setBlock(self, x, y, z, dim, name, aux=0):
|
||||
# oldBlockHandling=0(替换),isLegacy=True,updateNeighbors=False
|
||||
self._blockComp().SetBlockNew((x, y, z), {"name": name, "aux": aux}, 0, dim, True, False)
|
||||
|
||||
@staticmethod
|
||||
def _facingAux(playerId):
|
||||
"""按玩家面朝方向计算四面向 aux(南=0/西=1/北=2/东=3)。
|
||||
|
||||
组件 netease:face_directional 的面向由 SetBlockNew 的 auxValue 决定。
|
||||
玩家 yaw:0=南(+Z)、90=西(-X)、180=北(-Z)、270=东(+X);
|
||||
放置时方块「北面」朝向玩家,即方块面向 = 玩家准心方向的反方向。
|
||||
"""
|
||||
try:
|
||||
yaw = float(compFactory.CreateRot(playerId).GetRot()[1]) % 360.0
|
||||
except Exception:
|
||||
return 2
|
||||
quad = int(math.floor(yaw / 90.0 + 0.5)) % 4
|
||||
return (quad + 2) % 4
|
||||
|
||||
# ---------- 物品耐久 <-> 钉子数 ----------
|
||||
@staticmethod
|
||||
def _chargesOfRemaining(remaining):
|
||||
return clamp(int(remaining) - 1, 0, MAX_CHARGES)
|
||||
|
||||
@staticmethod
|
||||
def _remainingOfCharges(charges):
|
||||
return clamp(int(charges), 0, MAX_CHARGES) + 1
|
||||
|
||||
def _handCharges(self, playerId):
|
||||
itemComp = compFactory.CreateItem(playerId)
|
||||
return self._chargesOfRemaining(itemComp.GetItemDurability(CARRIED, 0))
|
||||
|
||||
def _consumeOne(self, playerId):
|
||||
itemComp = compFactory.CreateItem(playerId)
|
||||
slotId = itemComp.GetSelectSlotId()
|
||||
itemDict = itemComp.GetPlayerItem(INVENTORY, slotId, True)
|
||||
if not itemDict:
|
||||
return
|
||||
count = itemDict.get("count", 1)
|
||||
itemComp.SetInvItemNum(slotId, count - 1 if count > 1 else 0)
|
||||
|
||||
def _giveBox(self, playerId, charges):
|
||||
"""给予带指定钉子数的盒子;优先放入第一个空槽,便于写入耐久。"""
|
||||
if not playerId:
|
||||
return
|
||||
itemComp = compFactory.CreateItem(playerId)
|
||||
remainingOfCharges = self._remainingOfCharges(charges)
|
||||
itemDict = {
|
||||
"itemName": ITEM_ID, "count": 1, "auxValue": 0, 'customTips': TIPS_TEXT % (remainingOfCharges - 1, 64),
|
||||
'durability': remainingOfCharges
|
||||
}
|
||||
slot = -1
|
||||
items = itemComp.GetPlayerAllItems(INVENTORY, False) or []
|
||||
for i, it in enumerate(items):
|
||||
if it is None:
|
||||
slot = i
|
||||
break
|
||||
if slot < 0:
|
||||
itemComp.SpawnItemToPlayerInv(itemDict, playerId, -1)
|
||||
return
|
||||
itemComp.SpawnItemToPlayerInv(itemDict, playerId, slot)
|
||||
itemComp.SetItemDurability(INVENTORY, slot, remainingOfCharges)
|
||||
self._refreshBoxTips(playerId, slot)
|
||||
|
||||
# ---------- 动态描述(显示盒内钉子数) ----------
|
||||
@staticmethod
|
||||
def _boxTips(charges):
|
||||
return TIPS_TEXT % (clamp(int(charges), 0, MAX_CHARGES), MAX_CHARGES)
|
||||
|
||||
def _refreshBoxTips(self, playerId, slot):
|
||||
"""把指定槽位盒子物品的描述更新为当前钉子数。"""
|
||||
if not playerId:
|
||||
return
|
||||
itemComp = compFactory.CreateItem(playerId)
|
||||
key = (playerId, slot)
|
||||
itemDict = itemComp.GetEntityItem(INVENTORY, slot, True)
|
||||
if not itemDict or self._itemName(itemDict) != ITEM_ID:
|
||||
self._tipsSet.pop(key, None)
|
||||
return
|
||||
tips = self._boxTips(self._chargesOfRemaining(itemComp.GetItemDurability(INVENTORY, slot)))
|
||||
if self._tipsSet.get(key) == tips:
|
||||
return
|
||||
itemComp.ChangePlayerItemTipsAndExtraId(INVENTORY, slot, tips, "box1")
|
||||
self._tipsSet[key] = tips
|
||||
|
||||
def OnInventoryChanged(self, args):
|
||||
newItem = args.get("newItemDict")
|
||||
if not newItem or self._itemName(newItem) != ITEM_ID:
|
||||
return
|
||||
self._refreshBoxTips(args.get("playerId"), args.get("slot", -1))
|
||||
|
||||
def _notifyOne(self, playerId, text):
|
||||
"""测试参数用:给玩家发一条聊天提示(仅在 DEBUG_TEST 开启时调用)。"""
|
||||
if not playerId:
|
||||
return
|
||||
try:
|
||||
compFactory.CreateMsg(playerId).NotifyOneMessage(playerId, text)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def OnPlayerJoin(self, args):
|
||||
"""玩家进入世界时刷新背包里所有盒子的描述。"""
|
||||
playerId = args.get("id")
|
||||
if DEBUG_TEST:
|
||||
probe("4/4 AddServerPlayerEvent 已触发:脚本与事件监听均正常")
|
||||
self._notifyOne(playerId, "[dn_sa] 脚本已加载,事件监听正常")
|
||||
self._refreshAllBoxTips(playerId)
|
||||
|
||||
def _refreshAllBoxTips(self, playerId):
|
||||
if not playerId:
|
||||
return
|
||||
itemComp = compFactory.CreateItem(playerId)
|
||||
items = itemComp.GetPlayerAllItems(INVENTORY, False) or []
|
||||
for slot, it in enumerate(items):
|
||||
if it and self._itemName(it) == ITEM_ID:
|
||||
self._refreshBoxTips(playerId, slot)
|
||||
|
||||
# ---------- 放入 / 取出 ----------
|
||||
def _depositNails(self, playerId, dim, x, y, z, handCount, sneaking):
|
||||
"""把手中钉子放入盒子(潜行=放满,否则=1 颗);返回是否放入成功。"""
|
||||
key = self._key(dim, x, y, z)
|
||||
contents = self._loadContents()
|
||||
charges = clamp(contents.get(key, 0), 0, MAX_CHARGES)
|
||||
move = clamp(handCount if sneaking else 1, 0, MAX_CHARGES - charges)
|
||||
if move <= 0:
|
||||
return False
|
||||
itemComp = compFactory.CreateItem(playerId)
|
||||
itemComp.SetInvItemNum(itemComp.GetSelectSlotId(), handCount - move)
|
||||
contents[key] = charges + move
|
||||
self._saveContents(contents)
|
||||
self._lastUse[playerId] = time.time()
|
||||
return True
|
||||
|
||||
def _withdrawNails(self, playerId, dim, x, y, z, sneaking):
|
||||
"""从盒子取出钉子(潜行=全部取出,否则=1 颗);返回是否取出成功。"""
|
||||
key = self._key(dim, x, y, z)
|
||||
contents = self._loadContents()
|
||||
charges = clamp(contents.get(key, 0), 0, MAX_CHARGES)
|
||||
if charges <= 0:
|
||||
return False
|
||||
itemComp = compFactory.CreateItem(playerId)
|
||||
capacity = 0
|
||||
for it in (itemComp.GetPlayerAllItems(INVENTORY, False) or []):
|
||||
if it is None:
|
||||
capacity += NAILS_MAX_STACK
|
||||
elif self._itemName(it) == NAILS_ID:
|
||||
capacity += NAILS_MAX_STACK - it.get("count", 0)
|
||||
take = clamp(charges if sneaking else 1, 0, capacity)
|
||||
if take <= 0:
|
||||
return False
|
||||
itemComp.SpawnItemToPlayerInv({"itemName": NAILS_ID, "count": take, "auxValue": 0}, playerId, -1)
|
||||
contents[key] = charges - take
|
||||
self._saveContents(contents)
|
||||
self._lastUse[playerId] = time.time()
|
||||
return True
|
||||
|
||||
def _isSneaking(self, playerId):
|
||||
try:
|
||||
return bool(compFactory.CreatePlayer(playerId).isSneaking())
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
# ---------- 放置 ----------
|
||||
def OnItemUseOn(self, args):
|
||||
itemDict = args.get("itemDict") or {}
|
||||
itemName = self._itemName(itemDict)
|
||||
playerId = args.get("entityId")
|
||||
if not playerId:
|
||||
return
|
||||
# 手持钉子点击盒子方块 -> 放入(部分情况下潜行只触发本事件,作为兼容入口)
|
||||
if itemName == NAILS_ID and args.get("blockName") == BLOCK_ID:
|
||||
if time.time() - self._lastUse.get(playerId, 0.0) < COOLDOWN_SECONDS:
|
||||
return
|
||||
ok = self._depositNails(playerId, args.get("dimensionId", 0),
|
||||
args.get("x", 0), args.get("y", 0), args.get("z", 0),
|
||||
itemDict.get("count", 0), self._isSneaking(playerId))
|
||||
if ok:
|
||||
args["ret"] = True
|
||||
return
|
||||
if itemName != ITEM_ID:
|
||||
return
|
||||
off = FACE_OFFSET.get(args.get("face", 0))
|
||||
if off is None:
|
||||
return
|
||||
# 点击的正是钉子盒方块时不放置,避免在盒子上再叠一个盒子
|
||||
if args.get("blockName") == BLOCK_ID:
|
||||
return
|
||||
dim = args.get("dimensionId", 0)
|
||||
tx = args.get("x", 0) + off[0]
|
||||
ty = args.get("y", 0) + off[1]
|
||||
tz = args.get("z", 0) + off[2]
|
||||
if self._getBlockName(tx, ty, tz, dim) != BLOCK_AIR:
|
||||
return
|
||||
charges = self._handCharges(playerId)
|
||||
self._setBlock(tx, ty, tz, dim, BLOCK_ID, self._facingAux(playerId))
|
||||
contents = self._loadContents()
|
||||
contents[self._key(dim, tx, ty, tz)] = charges
|
||||
self._saveContents(contents)
|
||||
self._consumeOne(playerId)
|
||||
# 记录放置时刻:避免按住右键时同一动作紧接着触发 ServerBlockUseEvent 误取出钉子
|
||||
self._lastUse[playerId] = time.time()
|
||||
args["ret"] = True
|
||||
|
||||
# ---------- 存取 ----------
|
||||
def OnBlockUse(self, args):
|
||||
if args.get("blockName") != BLOCK_ID:
|
||||
return
|
||||
playerId = args.get("playerId")
|
||||
if not playerId:
|
||||
return
|
||||
args["cancel"] = True
|
||||
# 该事件按住右键时逐 tick 触发,冷却期内不再处理,避免一次长按连续存取
|
||||
if time.time() - self._lastUse.get(playerId, 0.0) < COOLDOWN_SECONDS:
|
||||
return
|
||||
dim = args.get("dimensionId", 0)
|
||||
x, y, z = args.get("x", 0), args.get("y", 0), args.get("z", 0)
|
||||
# 手上/事件中使用的物品:优先用事件自带的物品信息,更准确
|
||||
itemDict = args.get("itemDict") or {}
|
||||
itemComp = compFactory.CreateItem(playerId)
|
||||
hand = itemComp.GetPlayerItem(INVENTORY, itemComp.GetSelectSlotId(), True) or {}
|
||||
usedName = self._itemName(itemDict) or self._itemName(hand)
|
||||
# 手持盒子物品时不做存取(属于放置动作的延续,避免误取出钉子)
|
||||
if usedName == ITEM_ID:
|
||||
return
|
||||
sneaking = self._isSneaking(playerId)
|
||||
if usedName == NAILS_ID:
|
||||
self._depositNails(playerId, dim, x, y, z,
|
||||
itemDict.get("count") or hand.get("count", 0), sneaking)
|
||||
else:
|
||||
self._withdrawNails(playerId, dim, x, y, z, sneaking)
|
||||
|
||||
# ---------- 破坏 ----------
|
||||
def OnTryDestroy(self, args):
|
||||
if args.get("fullName") != BLOCK_ID:
|
||||
return
|
||||
dim = args.get("dimensionId", 0)
|
||||
key = self._key(dim, args.get("x", 0), args.get("y", 0), args.get("z", 0))
|
||||
contents = self._loadContents()
|
||||
charges = clamp(contents.pop(key, 0), 0, MAX_CHARGES)
|
||||
self._saveContents(contents)
|
||||
# 屏蔽默认掉落,改为返还带钉子数的盒子物品
|
||||
args["spawnResources"] = False
|
||||
self._giveBox(args.get("playerId"), charges)
|
||||
@@ -0,0 +1,33 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# 草药使用效果(对齐 Java 版 sa_essentials 原设定)
|
||||
# 大部分草药的效果已在各自物品 JSON 的 minecraft:food.effects 中直接配置;
|
||||
# 本脚本只处理组件无法表达的「解毒」:使用后移除玩家身上的中毒效果。
|
||||
# crushed_blue_herb / mixed_gb_herbs / mixed_ggb_herbs
|
||||
# mixed_gyb_herbs / mixed_grb_herbs / mixed_rb_herbs
|
||||
import mod.server.extraServerApi as serverApi
|
||||
|
||||
compFactory = serverApi.GetEngineCompFactory()
|
||||
|
||||
POISON_HERBS = (
|
||||
"dn_sa:crushed_blue_herb",
|
||||
"dn_sa:mixed_gb_herbs",
|
||||
"dn_sa:mixed_ggb_herbs",
|
||||
"dn_sa:mixed_gyb_herbs",
|
||||
"dn_sa:mixed_grb_herbs",
|
||||
"dn_sa:mixed_rb_herbs",
|
||||
)
|
||||
|
||||
|
||||
class HerbServer(serverApi.GetServerSystemCls()):
|
||||
def __init__(self, namespace, name):
|
||||
super(HerbServer, self).__init__(namespace, name)
|
||||
self.ListenForEvent(serverApi.GetEngineNamespace(), serverApi.GetEngineSystemName(),
|
||||
"PlayerEatFoodServerEvent", self, self.OnEatFood)
|
||||
|
||||
def OnEatFood(self, args):
|
||||
playerId = args.get("playerId")
|
||||
itemName = (args.get("itemDict") or {}).get("itemName")
|
||||
if playerId and itemName in POISON_HERBS:
|
||||
# 解毒:移除中毒状态
|
||||
compFactory.CreateEffect(playerId).RemoveEffectFromEntity("poison")
|
||||
@@ -0,0 +1,88 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# 火柴盒:点击方块消耗一点耐久并点燃地面,共20点耐久(对齐Java版sa_core火柴盒=打火石)
|
||||
import mod.server.extraServerApi as serverApi
|
||||
|
||||
compFactory = serverApi.GetEngineCompFactory()
|
||||
|
||||
MATCHBOX_ID = "dn_sa:matchbox"
|
||||
|
||||
# 需要监听右键的原版方块(地面及常见方块),aux 用 * 匹配所有附加值
|
||||
LISTEN_BLOCKS = [
|
||||
"minecraft:grass_block:*",
|
||||
"minecraft:dirt:*",
|
||||
"minecraft:coarse_dirt:*",
|
||||
"minecraft:podzol:*",
|
||||
"minecraft:mycelium:*",
|
||||
"minecraft:moss_block:*",
|
||||
"minecraft:mud:*",
|
||||
"minecraft:stone:*",
|
||||
"minecraft:cobblestone:*",
|
||||
"minecraft:mossy_cobblestone:*",
|
||||
"minecraft:gravel:*",
|
||||
"minecraft:sand:*",
|
||||
"minecraft:sandstone:*",
|
||||
"minecraft:clay:*",
|
||||
"minecraft:snow_layer:*",
|
||||
"minecraft:dirt_path:*",
|
||||
"minecraft:netherrack:*",
|
||||
"minecraft:end_stone:*",
|
||||
"minecraft:obsidian:*",
|
||||
"minecraft:log:*",
|
||||
"minecraft:log2:*",
|
||||
"minecraft:planks:*",
|
||||
"minecraft:stone_bricks:*",
|
||||
"minecraft:bricks:*",
|
||||
"minecraft:terracotta:*",
|
||||
]
|
||||
|
||||
# 点击方块面 -> 火焰放置位置偏移(Facing枚举:0下 1上 2北 3南 4西 5东)
|
||||
FACE_OFFSETS = {
|
||||
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 MatchboxServer(serverApi.GetServerSystemCls()):
|
||||
def __init__(self, namespace, name):
|
||||
super(MatchboxServer, self).__init__(namespace, name)
|
||||
self.levelId = serverApi.GetLevelId()
|
||||
self.ListenForEvent(serverApi.GetEngineNamespace(), serverApi.GetEngineSystemName(),
|
||||
"ServerBlockUseEvent", self, self.OnBlockUse)
|
||||
self.ListenForEvent(serverApi.GetEngineNamespace(), serverApi.GetEngineSystemName(),
|
||||
"LoadServerAddonScriptsAfter", self, self.OnLoadAddon)
|
||||
|
||||
def OnLoadAddon(self, args):
|
||||
# 注册原版方块对ServerBlockUseEvent的监听
|
||||
whitelistComp = compFactory.CreateBlockUseEventWhiteList(self.levelId)
|
||||
for blockName in LISTEN_BLOCKS:
|
||||
whitelistComp.AddBlockItemListenForUseEvent(blockName)
|
||||
|
||||
def OnBlockUse(self, args):
|
||||
playerId = args.get("playerId")
|
||||
itemDict = args.get("itemDict") or {}
|
||||
if itemDict.get("itemName") != MATCHBOX_ID:
|
||||
return
|
||||
# 检查主手火柴盒耐久,耐久为0时无法点燃
|
||||
itemComp = compFactory.CreateItem(playerId)
|
||||
carried = serverApi.GetMinecraftEnum().ItemPosType.CARRIED
|
||||
durability = itemComp.GetItemDurability(carried, 0)
|
||||
if durability <= 0:
|
||||
return
|
||||
# 计算点击面外的火焰放置位置
|
||||
offset = FACE_OFFSETS.get(args.get("face", 1), (0, 1, 0))
|
||||
firePos = (args["x"] + offset[0], args["y"] + offset[1], args["z"] + offset[2])
|
||||
dimensionId = args.get("dimensionId")
|
||||
# 获取目标位置方块(GetBlockNew 属于 CreateBlockInfo 组件)
|
||||
blockInfoComp = compFactory.CreateBlockInfo(self.levelId)
|
||||
curBlock = blockInfoComp.GetBlockNew(firePos, dimensionId)
|
||||
if curBlock is not None and curBlock.get("name") == "minecraft:air":
|
||||
# 目标位置为空气,放置火焰并消耗一点耐久,拦截该方块的交互逻辑(如同打火石)
|
||||
# SetBlockNew 同属 CreateBlockInfo 组件
|
||||
blockInfoComp.SetBlockNew(firePos, {"name": "minecraft:fire", "aux": 0}, 0, dimensionId)
|
||||
itemComp.SetItemDurability(carried, 0, durability - 1)
|
||||
args["cancel"] = True
|
||||
@@ -0,0 +1,91 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# 医疗物品:自定义蓄力物品(custom_item_type: ranged_weapon)
|
||||
# 右键按下开始蓄力,右键抬起触发 ItemReleaseUsingServerEvent;
|
||||
# 蓄满才生效并消耗 1 个,未蓄满松开则取消(不生效、不消耗)。
|
||||
import mod.server.extraServerApi as serverApi
|
||||
|
||||
compFactory = serverApi.GetEngineCompFactory()
|
||||
|
||||
PLASTER = "dn_sa_plaster:bow"
|
||||
SPLINT = "dn_sa_splint:bow"
|
||||
ALCOHOL_WIPES = "dn_sa_alcohol_wipes:bow"
|
||||
BLOOD_BAG = "dn_sa:blood_bag"
|
||||
BLOOD_BAG_EMPTY = "dn_sa:blood_bag_empty"
|
||||
|
||||
# 蓄满所需持有时长(tick,20 tick = 1 秒)
|
||||
CHARGE_TICKS = 60
|
||||
CHARGE_ITEMS = (PLASTER, SPLINT, ALCOHOL_WIPES)
|
||||
|
||||
HEAL_AMOUNT = 6.0 # 血袋回血量
|
||||
DONATE_TOTAL = 6 # 空血袋总扣血量
|
||||
DONATE_STEP = 1.0 # 每次扣血量
|
||||
DONATE_INTERVAL = 0.5 # 扣血间隔秒数
|
||||
|
||||
|
||||
class MedicalServer(serverApi.GetServerSystemCls()):
|
||||
def __init__(self, namespace, name):
|
||||
super(MedicalServer, self).__init__(namespace, name)
|
||||
self.ListenForEvent(serverApi.GetEngineNamespace(), serverApi.GetEngineSystemName(),
|
||||
"ItemReleaseUsingServerEvent", self, self.OnReleaseUsing)
|
||||
self.ListenForEvent(serverApi.GetEngineNamespace(), serverApi.GetEngineSystemName(),
|
||||
"PlayerEatFoodServerEvent", self, self.OnEatFood)
|
||||
|
||||
# ---------- 蓄力物品 ----------
|
||||
def OnReleaseUsing(self, args):
|
||||
playerId = args.get("playerId")
|
||||
itemName = (args.get("itemDict") or {}).get("itemName")
|
||||
if not playerId or itemName not in CHARGE_ITEMS:
|
||||
return
|
||||
# 已持有帧数 = 最大使用时长 - 剩余时长
|
||||
maxUse = args.get("maxUseDuration") or 0
|
||||
durationLeft = args.get("durationLeft") or 0
|
||||
held = (maxUse - durationLeft) if maxUse > 0 else CHARGE_TICKS
|
||||
# 未蓄满:取消本次使用
|
||||
if held < CHARGE_TICKS:
|
||||
args["cancel"] = True
|
||||
return
|
||||
# 蓄满:施加效果
|
||||
effectComp = compFactory.CreateEffect(playerId)
|
||||
if itemName == PLASTER:
|
||||
effectComp.AddEffectToEntity("regeneration", 5, 0, False)
|
||||
elif itemName == SPLINT:
|
||||
effectComp.AddEffectToEntity("resistance", 10, 0, False)
|
||||
elif itemName == ALCOHOL_WIPES:
|
||||
effectComp.RemoveEffectFromEntity("poison")
|
||||
# 消耗 1 个
|
||||
self._ConsumeOne(playerId)
|
||||
args["changeItem"] = True
|
||||
|
||||
def _ConsumeOne(self, playerId):
|
||||
itemComp = compFactory.CreateItem(playerId)
|
||||
slotId = itemComp.GetSelectSlotId()
|
||||
itemDict = itemComp.GetPlayerItem(serverApi.GetMinecraftEnum().ItemPosType.INVENTORY, slotId, True)
|
||||
if not itemDict:
|
||||
return
|
||||
count = itemDict.get("count", 1)
|
||||
itemComp.SetInvItemNum(slotId, count - 1)
|
||||
|
||||
# ---------- 血袋 / 空血袋 ----------
|
||||
def OnEatFood(self, args):
|
||||
playerId = args.get("playerId")
|
||||
itemName = (args.get("itemDict") or {}).get("itemName")
|
||||
if not playerId:
|
||||
return
|
||||
if itemName == BLOOD_BAG:
|
||||
self._AddHealth(playerId, HEAL_AMOUNT)
|
||||
elif itemName == BLOOD_BAG_EMPTY:
|
||||
for i in range(1, DONATE_TOTAL + 1):
|
||||
self.AddTimer(DONATE_INTERVAL * i, self._AddHealth, playerId, -DONATE_STEP)
|
||||
self.AddTimer(DONATE_INTERVAL * (DONATE_TOTAL + 1), self._GiveBloodBag, playerId)
|
||||
|
||||
def _AddHealth(self, playerId, delta):
|
||||
attrType = serverApi.GetMinecraftEnum().AttrType.HEALTH
|
||||
attrComp = compFactory.CreateAttr(playerId)
|
||||
cur = attrComp.GetAttrValue(attrType)
|
||||
limit = attrComp.GetAttrMaxValue(attrType)
|
||||
attrComp.SetAttrValue(attrType, max(0.0, min(cur + delta, limit)))
|
||||
|
||||
def _GiveBloodBag(self, playerId):
|
||||
itemDict = {"itemName": BLOOD_BAG, "count": 1, "auxValue": 0}
|
||||
compFactory.CreateItem(playerId).SpawnItemToPlayerInv(itemDict, playerId, -1)
|
||||
@@ -0,0 +1,53 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from mod.common.mod import Mod
|
||||
import mod.server.extraServerApi as serverApi
|
||||
|
||||
# ===== 测试参数(排查脚本是否被引擎加载) =====
|
||||
# True :输出各阶段探针(print + 聊天提示 + 写文件),用以定位脚本卡在哪一层
|
||||
# False:关闭全部探针,恢复正常行为
|
||||
DEBUG_TEST = True
|
||||
|
||||
# 写文件探针路径:print 只进 MCStudio「脚本测试日志」窗口,不保证进 Debug_Log,
|
||||
# 这里直接追加写入客户端可写目录,作为"脚本是否被引擎加载"的决定性证据。
|
||||
PROBE_FILE = "C:/Users/30272/AppData/Roaming/MinecraftPE_Netease/dn_sa_probe.log"
|
||||
|
||||
|
||||
def probe(msg):
|
||||
"""脚本加载探针:仅在 DEBUG_TEST 为 True 时输出(print + 写文件)。"""
|
||||
if not DEBUG_TEST:
|
||||
return
|
||||
try:
|
||||
print("[dn_sa][PROBE] " + msg)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
import time
|
||||
f = open(PROBE_FILE, "a")
|
||||
f.write(time.strftime("%Y-%m-%d %H:%M:%S") + " " + msg + "\n")
|
||||
f.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
probe("1/4 modMain 模块已被引擎导入")
|
||||
|
||||
|
||||
@Mod.Binding(name="dn_sa", version="1.0")
|
||||
class DnSaMod(object):
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@Mod.InitServer()
|
||||
def DnSaServerInit(self):
|
||||
probe("2/4 InitServer 已执行,开始注册服务端系统")
|
||||
serverApi.RegisterSystem("dn_sa", "MatchboxServer", "Script_dn_sa.matchboxServer.MatchboxServer")
|
||||
serverApi.RegisterSystem("dn_sa", "MedicalServer", "Script_dn_sa.medicalServer.MedicalServer")
|
||||
serverApi.RegisterSystem("dn_sa", "HerbServer", "Script_dn_sa.herbServer.HerbServer")
|
||||
serverApi.RegisterSystem("dn_sa", "AmbulanceServer", "Script_dn_sa.ambulanceServer.AmbulanceServer")
|
||||
serverApi.RegisterSystem("dn_sa", "BoxOfNailsServer", "Script_dn_sa.boxOfNailsServer.BoxOfNailsServer")
|
||||
probe(" 所有服务端系统注册完成")
|
||||
|
||||
@Mod.DestroyServer()
|
||||
def DnSaServerDestroy(self):
|
||||
pass
|
||||
Reference in new issue
Block a user