代码初始化

This commit is contained in:
Giao committed 2026-10-09 18:23:57 +08:00
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