人工代码初始化,entities文件夹保留

This commit is contained in:
Giao committed 2026-10-10 09:36:09 +08:00
1 parent e3efeb74ad
commit 98e9903f73
48 files changed
+6545

No files matched your search

@@ -0,0 +1,323 @@
# coding=utf-8
import time
from FastComp import *
from ToolsConfig import *
CdTags = {}
class BlueFoxFilterEnum(object):
"""
事件过滤器枚举值
"""
class ServerEvent(object):
class ProjectileDoHitEffectEvent(object):
@staticmethod
def IsEntityInList(entityStrType):
# type:(List) -> Callable
"""
是否为规定的生物
"""
def _(args):
return serverCompFactory.CreateEngineType(args['id']).GetEngineTypeStr() in entityStrType
return _
class NormalEvent(object):
@staticmethod
def NotInCd(cdTag, cdSize):
# type:(str,float) -> Callable
"""
是否在CdTag所设置的Cd之外,注意cdTag是所有事件通用的
"""
def _(*args):
lastTime = CdTags.get(MOD_NAME + cdTag, 0)
if time.time() - lastTime >= cdSize:
CdTags[MOD_NAME + cdTag] = time.time()
return True
return False
return _
@staticmethod
def IsNight():
"""
是否为黑夜
"""
def _(*args):
_time = serverCompFactory.CreateTime(LevelId).GetTime()
_time %= 24000
return 12000 <= _time < 24000
return _
@staticmethod
def IsDay():
"""
是否为白天
"""
def _(*args):
_time = serverCompFactory.CreateTime(LevelId).GetTime()
_time %= 24000
return 0 <= _time < 12000
return _
class DestroyBlockEvent(object):
@staticmethod
def BlockNameInList(blockNameList):
# type:(List[str]) -> Callable
"""
被破坏的方块是否在规定的方块列表内
"""
def _(args):
return args['fullName'] in blockNameList
return _
class ServerPlayerTryDestroyBlockEvent(object):
@staticmethod
def BlockNameInList(blockNameList):
# type:(List[str]) -> Callable
"""
被破坏的方块是否在规定的方块列表内
"""
def _(args):
return args['fullName'] in blockNameList
return _
@staticmethod
def NotCancel():
"""
事件未被取消
"""
def _(args):
return not args['cancel']
return _
class ServerBlockUseEvent(object):
@staticmethod
def BlockNameInList(blockNameList):
# type:(List[str]) -> Callable
"""
被使用方块是否在规定的方块列表内
"""
def _(args):
return args['blockName'] in blockNameList
return _
@staticmethod
def NoCarriedItem():
"""
当没有手持物品时
"""
def _(args):
return not bool(serverCompFactory.CreateItem(args['playerId']).GetPlayerItem(2, 0))
return _
@staticmethod
def CarriedItem():
"""
当有手持物品时
"""
def _(args):
return bool(serverCompFactory.CreateItem(args['playerId']).GetPlayerItem(2, 0))
return _
class ItemUseOnAfterServerEvent(object):
@staticmethod
def NotInCd(cdTag, cdSize):
# type:(str,float) -> Callable
"""
是否在CdTag所设置的Cd之外
"""
def _(args):
lastTime = CdTags.get(MOD_NAME + cdTag, 0)
if time.time() - lastTime >= cdSize:
CdTags[MOD_NAME + cdTag] = time.time()
return True
return False
return _
@staticmethod
def IsBlock(blockName):
# type:(str) -> Callable
"""
是否是规定的被使用方块
"""
def _(args):
return args['blockName'] == blockName
return _
@staticmethod
def BlockNameInList(blockNameList):
# type:(List[str]) -> Callable
"""
被使用方块是否在规定的方块列表内
"""
def _(args):
return args['blockName'] in blockNameList
return _
@staticmethod
def IsItemDict(itemDict):
# type:(Dict[str]) -> Callable
"""
被使用的物品是否为规定的物品
"""
def _(args):
return IsSameItem(args['itemDict'], itemDict)
return _
@staticmethod
def ItemNameInList(itemNameList):
# type:(List[str]) -> Callable
"""
被使用的物品是否在规定的物品列表内
"""
def _(args):
return args['itemDict']['itemName'] in itemNameList
return _
class DamageEvent(object):
@staticmethod
def DamageFilter(damage=0):
"""
伤害数值过滤器, 当伤害大于等于输入的伤害时将会触发
"""
def _(args):
return args['damage_f'] >= damage
return _
@staticmethod
def CauseInList(causeList):
# type:(List[str]) -> Callable
"""
伤害类型过滤器, 当于输入的类型相同时触发
"""
def _(args):
return args['cause'] in causeList
return _
@staticmethod
def IgniteFilter():
"""
忽视过滤器, 未被忽视时触发
"""
def _(args):
return not args['ignite']
return _
@staticmethod
def AttackerIsPlayer():
"""
攻击者是否为玩家
"""
def _(args):
return IsPlayer(args['srcId'])
return _
@staticmethod
def InjuredIsPlayer():
"""
受伤者者是否为玩家
"""
def _(args):
return IsPlayer(args['entityId'])
return _
class ServerBlockEntityTickEvent(object):
@staticmethod
def BlockNameInList(blockNameList):
"""
方块名称过滤器
"""
def _(args):
return args['blockName'] in blockNameList
return _
class ClientEvent(object):
class NormalEvent(object):
@staticmethod
def NotInCd(cdTag, cdSize):
# type:(str,float) -> Callable
"""
是否在CdTag所设置的Cd之外,注意cdTag是所有事件通用的
"""
def _(*args):
lastTime = CdTags.get(MOD_NAME + cdTag, 0)
if time.time() - lastTime >= cdSize:
CdTags[MOD_NAME + cdTag] = time.time()
return True
return False
return _
@staticmethod
def IsNight():
"""
是否为黑夜
"""
def _(*args):
_time = serverCompFactory.CreateTime(LevelId).GetTime()
_time %= 24000
return 12000 <= _time < 24000
return _
@staticmethod
def IsDay():
"""
是否为白天
"""
def _(*args):
_time = serverCompFactory.CreateTime(LevelId).GetTime()
_time %= 24000
return 0 <= _time < 12000
return _
@@ -0,0 +1,88 @@
# coding=utf-8
from ItemTools import IsSameItem
from ToolsConfig import serverCompFactory, clientCompFactory, serverApi, LevelId
def DictFromBlockEntityData(blockEntityData, posDim):
"""
从方块实体中获取全部数据
:param blockEntityData:方块实体自定义数据
:param posDim:坐标维度
:return:dict
"""
data = getattr(getattr(blockEntityData, '_BlockEntityData__exDataMgr'), 'mBlockEntityData')
return data[posDim[3]][posDim[:3]]
def SetBlockAir(posDim):
"""
设置某位置的方块为空,仅服务端可用
"""
return serverCompFactory.CreateBlockInfo(LevelId).SetBlockNew(
posDim[:3], {'name': 'minecraft:air'}, dimensionId=posDim[3]
)
def GetEntityDim(entityId):
"""
获取实体维度,仅服务端可用
"""
return serverCompFactory.CreateDimension(entityId).GetEntityDimensionId()
def DeclarePlayerInvItem(playerId, itemDict1, count=1):
"""
减少玩家背包内特定物品
:param playerId:玩家ID
:param count:减少数量
:param itemDict1:需要检测的物品字典,必须包含newItemName字段
:return:是否成功减少
"""
itemComp = serverCompFactory.CreateItem(playerId)
itemDicts = itemComp.GetPlayerAllItems(0)
for index in range(itemDicts.__len__()):
itemDict = itemDicts[index]
if itemDict and IsSameItem(itemDict, itemDict1):
itemComp.SetInvItemNum(index, itemDict['count'] - count)
return True
return False
def GetEntityCarriedItem(entityId):
"""
获取玩家右手物品
"""
return serverCompFactory.CreateItem(entityId).GetEntityItem(2, 0, True)
def GetEntityTypeComp(entityId):
"""
获取实体类型组件
"""
return serverCompFactory.CreateEngineType(entityId) or clientCompFactory.CreateEngineType(entityId)
def IsPlayer(entityId):
"""
判断一个实体是否为玩家
"""
player = serverApi.GetMinecraftEnum().EntityType.Player
return GetEntityTypeComp(entityId).GetEngineType() & player == player
def GetEntityItemComp(entityId):
"""
获取实体类型组件
"""
return serverCompFactory.CreateItem(entityId) or clientCompFactory.CreateItem(entityId)
def DeclarePlayerCarriedItem(playerId):
"""
减少玩家右手物品一个,仅服务端有效
"""
itemComp = GetEntityItemComp(playerId)
itemDict = itemComp.GetPlayerItem(2, 0, True)
if itemDict:
itemDict['count'] -= 1
return itemComp.SpawnItemToPlayerCarried(itemDict, playerId)
@@ -0,0 +1,171 @@
# coding=utf-8
from ToolsConfig import *
def IsItemDict(itemDict):
"""
是否为有效的物品字典
:param itemDict:
:return:
"""
if itemDict and (
not isinstance(itemDict, dict) or itemDict.get('newItemName') is None or
itemDict.get('newAuxValue') is None or itemDict['newItemName'] == 'minecraft:air'
):
return False
return True
def GetItemUserData(itemDict, *args):
"""
获取物品的userData,若不含有相应的键,则创建
:param itemDict: 物品字典
:param args: 键值key
:return:
"""
itemDict['userData'] = itemDict.get('userData', {})
if itemDict.get('userData') is None:
itemDict['userData'] = {}
ver = itemDict['userData']
for key in args:
ver[key] = ver.get(key, {})
ver = ver[key]
return ver
def CreateItemDict(itemName, aux=0, count=1):
"""
快速生成最基本的物品字典
"""
itemInfo = GetItemBaseInfoSafely({'newItemName': itemName, 'newAuxValue': aux})
return {
'newItemName': itemName, 'newAuxValue': aux, 'count': count, 'durability': itemInfo.get('maxDurability', 0)
}
def GetItemBaseInfoSafely(itemDict):
"""
安全获取物品基本信息,网易接口对于不存在的物品ID_AUX获取为None
:param itemDict:
:return:
"""
default = None
try:
if itemDict:
default = clientCompFactory.CreateItem(LevelId).GetItemBasicInfo(
itemDict['newItemName'], itemDict['newAuxValue'],
(itemDict.get('enchantData', []) + itemDict.get('modEnchantData', [])).__len__() > 0
) or serverCompFactory.CreateItem(LevelId).GetItemBasicInfo(
itemDict['newItemName'], itemDict['newAuxValue'],
(itemDict.get('enchantData', []) + itemDict.get('modEnchantData', [])).__len__() > 0
)
except:
pass
default = default if default else {
'foodNutrition': 0, 'maxDurability': 0, 'armorDefense': 0, 'tierDict': None,
'itemType': '', 'fuelDuration': 0.0, 'foodSaturation': 0.0,
'armorKnockbackResistance': 0.0, 'customItemType': '', 'itemTierLevel': -1,
'maxStackSize': 64, 'weaponDamage': 0, 'itemName': '',
'itemCategory': 'construction', 'id_aux': 0, 'armorToughness': 0
}
return default
def DeclareItem(itemDict):
# type:(Dict) -> Tuple[Dict,Dict]
"""
减少物品一个,如果个数为0则会返回空dict
:return:减少后的物品字典,独立出来的物品
"""
if not itemDict:
return itemDict, {}
itemDict['count'] -= 1
newItem = copy.deepcopy(itemDict)
newItem['count'] = 1
if itemDict['count'] > 0:
return itemDict, newItem
else:
return {}, newItem
def DeclareItemAnyNum(itemDict, num):
# type:(Dict,int) -> Tuple[Dict,Dict]
"""
减少物品多个,如果个数为0则会返回空dict
:return:减少后的物品字典,独立出来的物品
"""
if not isinstance(itemDict, dict): return {}, {}
if not IsItemDict(itemDict): return {}, {}
if not itemDict: return {}, itemDict
if itemDict['count'] < num: return {}, itemDict
itemDict['count'] -= num
newItem = copy.deepcopy(itemDict)
newItem['count'] = num
if itemDict['count'] > 0:
return itemDict, newItem
else:
return {}, newItem
def StackItems(item1, item2):
# type:(Dict,Dict) -> Dict
"""
使item1与item2堆叠在一起,返回多余的物品
"""
if not item1 or not item2: return {}
if not IsSameItemAndCanStack(item1, item2): return {}
itemInfo = GetItemBaseInfoSafely(item1)
maxCount = itemInfo['maxStackSize']
count1 = item1['count']
count2 = item2['count']
t_count = count1 + count2 - maxCount
t_item = {}
if t_count > 0:
t_item = copy.deepcopy(item1)
t_item['count'] = t_count
item1['count'] = maxCount
else:
item1['count'] += item2['count']
return t_item
def IsSameItemAndCanStack(item1, item2):
# type:(Dict,Dict) -> bool
"""
判断item1是否能与item2堆叠在一起
"""
if not item1 or not item2:
return True
if IsSameItem(item1, item2):
itemInfo = GetItemBaseInfoSafely(item1)
return item1['count'] + item2['count'] <= itemInfo['maxStackSize']
return False
def IsSameItem(item1, item2, args=('newItemName', 'newAuxValue', 'durability')):
"""
判断item1是否与item2相同.目前判断物品ID,AUX,耐久度
"""
if item1 and item2:
same = True
for key in args: same = same and item1.get(key, 0) == item2.get(key, 0)
return same
else:
return False
def GetItemDurabilityPercent(itemDict, itemInfo=None):
"""
获取耐久值百分比与总值
:param itemDict:物品信息字典
:param itemInfo:物品基础信息字典
:return:
"""
if itemInfo is None:
itemInfo = GetItemBaseInfoSafely(itemDict)
return (
(itemDict.get('durability', 0.0) / float(itemInfo['maxDurability']))
if itemInfo['maxDurability'] != 0 else 0.0,
1.0 if itemInfo['maxDurability'] != 0 else 0.0
)
@@ -0,0 +1,6 @@
# coding=utf-8
MOD_NAME = 'rtdd'
MOD_VERSION = '0.0.1'
Time = 1754134400.0
@@ -0,0 +1,39 @@
# coding=utf-8
from mod.common.mod import Mod
from OtherTools import *
from SpecialFunciton.BlueFoxSystem import *
from SystemTools import *
from ToolsConfig import *
def LoadMod():
"""
一键加载脚本
:return:
"""
print """
---->>>加载脚本<<<----
模组名称:{}
模组版本:{}
""".format(MOD_NAME, MOD_VERSION)
Mod.Binding(MOD_NAME, MOD_VERSION)(BlueFoxModLoader)
class BlueFoxModLoader:
def __init__(self):
pass
@Mod.InitClient()
def InitClient(self):
RegisterSystemFastly(clientApi, BlueFoxClientSystem)
lastSubclasses = GetLastSubclass(BlueFoxClientSystem)
for lastSubclass in lastSubclasses:
RegisterSystemFastly(clientApi, lastSubclass)
@Mod.InitServer()
def InitServer(self):
RegisterSystemFastly(serverApi, BlueFoxServerSystem)
lastSubclasses = GetLastSubclass(BlueFoxServerSystem)
for lastSubclass in lastSubclasses:
RegisterSystemFastly(serverApi, lastSubclass)
@@ -0,0 +1,60 @@
# coding=utf-8
from ToolsConfig import *
if 1 > 2:
from SpecialFunciton.BlueFoxSystem import BlueFoxServerSystem
blueFoxServerSystem = BlueFoxServerSystem
molangNames = {}
def ChangeBlockEntityMolangFromServerToAllClient(posDim, molangName, value):
# type:(Tuple[int,int,int,int],str,float) -> None
"""
从服务端修改所有客户端某一位置的方块实体里的molang值
:param posDim: 方块实体的坐标与维度
:param molangName: molang名称
:param value: molang值
:return:
"""
blueFoxServerSystem.BroadcastToAllClient('ChangeBlockEntityMolang', {
'molangName': molangName, 'value': value, 'posDim': posDim
})
def ChangeBlockEntityMolangFromServerToClient(playerId, posDim, molangName, value):
# type:(Union[int,str],Tuple[int,int,int,int],str,float) -> None
"""
从服务端修改某一客户端某一位置的方块实体里的molang值
:param posDim: 方块实体的坐标与维度
:param molangName: molang名称
:param value: molang值
:param playerId: 玩家id
:return:
"""
blueFoxServerSystem.NotifyToClient(playerId, 'ChangeBlockEntityMolang', {
'molangName': molangName, 'value': value, 'posDim': posDim
})
def RegisterMolang(molangName, defaultValue=0.0):
"""
注册并获取molang查询字段
:param molangName: molang名称,无需query.mod.
:param defaultValue: 默认值
:return:
"""
name = 'query.mod.' + molangName
molangNames[name] = defaultValue
return name
def RegisterMolangWithNameSpace(molangName, defaultValue=0.0):
"""
注册并获取molang查询字段,自带命名空间,使用时需要在前部加入MOD_NAME_molangName
:param molangName: molang名称
:param defaultValue: 默认值
:return:
"""
return RegisterMolang('{}_{}'.format(MOD_NAME, molangName), defaultValue)
@@ -0,0 +1,219 @@
# coding=utf-8
from ToolsConfig import *
import copy
def GetItemUserData(itemDict, *args):
"""
获取物品的userData,若不含有相应的键,则创建
:param itemDict: 物品字典
:param args: 键值key
:return:
"""
itemDict['userData'] = itemDict.get('userData', {})
if itemDict.get('userData') is None:
itemDict['userData'] = {}
ver = itemDict['userData']
for key in args:
ver[key] = ver.get(key, {})
ver = ver[key]
return ver
def FixList(_list, _len, defaultValue):
"""
修正列表到目标长度
:param _list:列表
:param _len:目标长度
:param defaultValue:默认值
:return:
"""
if _list.__class__ is not list: _list = []
if len(_list) == _len: return _list
_list = _list if isinstance(_list, list) else [copy.deepcopy(defaultValue) for _ in range(_len)]
del _list[_len:]
_list.extend(copy.deepcopy(defaultValue) for _ in range(len(_list), _len))
return _list
def GetPlayerHasAut(playerId):
"""
判断玩家是否拥有创造和管理员权限
:param playerId:
:return:
"""
return serverCompFactory.CreatePlayer(playerId).GetPlayerOperation() == 2 and \
serverCompFactory.CreateGame(LevelId).GetPlayerGameType(playerId) == 1
def NbtTranslator(nbtDict):
"""
将nbt格式的数据转换为python数据
:param nbtDict:
:return:
"""
if nbtDict is None:
return {}
if type(nbtDict) is list:
return NbtList(nbtDict)
elif type(nbtDict) is dict and nbtDict.get('__type__', None) is None:
return NbtDict(nbtDict)
else:
return NbtValue(nbtDict)
def NbtValue(nbtDict):
"""
处理数Nbt数据
:param nbtDict:
:return:
"""
if type(nbtDict) is dict:
valueType = nbtDict.get('__type__', 0)
return NbtTranslatorFunc[valueType - 1](nbtDict['__value__']) if valueType else 0
else:
return nbtDict
def NbtDict(nbtDict):
"""
处理字典型Nbt数据
:param nbtDict:
:return:
"""
value = {}
for _ in nbtDict.items():
value[_[0]] = NbtTranslator(_[1])
return value
def NbtList(nbtList):
"""
处理列表型Nbt数据
:param nbtList:
:return:
"""
value = []
for _ in nbtList:
value.append(NbtTranslator(_))
return value
def TransBool(nbtValue):
"""
处理bool数据
"""
if nbtValue == 2:
return None
return bool(nbtValue)
NbtTranslatorFunc = [
TransBool, int, int, int, float, int, bytearray, str, list, NbtDict, list
]
def UpdateFunction(old_func, new_func):
"""
热更新函数
"""
old_func.__doc__ = new_func.__doc__
old_func.__dict__ = new_func.__dict__
old_func.__defaults__ = new_func.__defaults__
old_func.__code__ = new_func.__code__
mounts = {'s': [], 'c': []}
def Mount(system):
# type:(Union[clientBaseSystem,serverBaseSystem]) -> Any
"""
挂载系统,为非系统类添加属性blueFoxSystem
服务端只挂载BlueFoxServerSystem,客户端只挂载BlueFoxClientSystem
"""
def _mount(cls):
if issubclass(system, serverBaseSystem):
mounts['s'].append(cls)
elif issubclass(system, clientBaseSystem):
mounts['c'].append(cls)
return cls
return _mount
def GetLastSubclass(cls, basicClass=None):
"""
获取类的最终继承类
:param cls:
:param basicClass:
:return:
"""
if basicClass is None:
basicClass = []
subclasses = cls.__subclasses__()
for subclass in subclasses:
if subclass.__subclasses__():
GetLastSubclass(subclass, basicClass)
else:
basicClass.append(subclass)
return basicClass
def AddTupleContext(list1, list2):
# type:(Union[Tuple,List],Union[Tuple,List]) -> Union[Tuple[Any,...],Tuple[int,int,int,int]]
"""
将两个相同长度的tuple or list内容彼此相加,并返回新的tuple
:return:list1 + list2
"""
if list2.__len__() == list1.__len__():
newList = [list1[n] + list2[n] for n in range(list1.__len__())]
return tuple(newList)
return tuple([])
def AddTupleContextReList(list1, list2):
"""
将两个相同长度的tuple or list内容彼此相加,并返回新的list
:return:list1 + list2
"""
if list2.__len__() == list1.__len__():
newList = [list1[n] + list2[n] for n in range(list1.__len__())]
return newList
return []
def GetDistance(pos1, pos2):
# type:(Union[List,Tuple],Union[List, Tuple]) -> float
"""
计算三维上两点距离
:return:
"""
_sum = 0
for _ in range(min(len(pos1), len(pos2))): _sum += (pos1[_] - pos2[_]) ** 2
return _sum ** 0.5
def VecMod(vec):
# type:(Union[List,Tuple]) -> float
"""
计算向量的模
:param vec:
:return:
"""
_sum = 0
for _ in vec:
_sum += _ ** 2
return _sum ** 0.5
def ReduceTupleContext(list1, list2):
# type:(Union[Tuple,List],Union[Tuple,List]) -> Union[Tuple[Any,...],Tuple[int,int,int,int]]
"""
将两个相同长度的tuple or list内容彼此相减,并返回新的tuple
:return:list1 - list2
"""
if list2.__len__() == list1.__len__():
newList = [list1[n] - list2[n] for n in range(list1.__len__())]
return tuple(newList)
return tuple([])
@@ -0,0 +1,65 @@
# coding=utf-8
from ToolsConfig import *
def InvisiblePlayerModel(playerId):
"""
隐藏玩家模型
"""
comp = clientCompFactory.CreateActorRender(playerId)
comp.RemovePlayerRenderController('controller.render.player.first_person')
comp.RemovePlayerRenderController('controller.render.player.third_person')
comp.RemovePlayerRenderController('controller.render.player.map')
comp.RebuildPlayerRender()
def RestorePlayerModel(playerId):
"""
显示玩家模型
"""
comp = clientCompFactory.CreateActorRender(playerId)
comp.AddPlayerRenderController('controller.render.player.first_person', 'variable.is_first_person')
comp.AddPlayerRenderController(
'controller.render.player.third_person', '!variable.is_first_person && !variable.map_face_icon'
)
comp.AddPlayerRenderController('controller.render.player.map', 'variable.map_face_icon')
comp.RebuildPlayerRender()
class PlayerVanillaAnimation(object):
Attack = 'first_person_attack_controller'
MoveArms = 'move.arms'
MoveLegs = 'move.legs'
Holding = 'holding'
EmptyHand = 'first_person_empty_hand'
__d = {
PlayerVanillaAnimation.Attack: 'controller.animation.player.first_person_attack',
PlayerVanillaAnimation.MoveArms: 'animation.player.move.arms',
PlayerVanillaAnimation.MoveLegs: 'animation.player.move.legs',
PlayerVanillaAnimation.Holding: 'animation.player.holding',
PlayerVanillaAnimation.EmptyHand: 'animation.player.first_person.empty_hand'
}
def InvisibleVanillaAnimation(playerId, *args):
"""
屏蔽原版动画
"""
comp = clientCompFactory.CreateActorRender(playerId)
for _ in args:
comp.AddPlayerAnimation(_, 'animation.unvanilla.all') if __d[_].startswith(
'animation') else comp.AddPlayerAnimationController(_, 'controller.animation.unvanilla.all')
return comp
def RestoreVanillaAnimation(playerId, *args):
"""
还原原版动画
"""
comp = clientCompFactory.CreateActorRender(playerId)
for _ in args:
comp.AddPlayerAnimation(_, __d[_]) if __d[_].startswith(
'animation') else comp.AddPlayerAnimationController(_, __d[_])
return comp
@@ -0,0 +1,137 @@
# coding=utf-8
from ToolsConfig import *
CreatedTools = {}
ButtonTouchMap = {
ButtonTouchTypeEnum.TouchDown: 'SetButtonTouchDownCallback',
ButtonTouchTypeEnum.TouchUp: 'SetButtonTouchUpCallback',
ButtonTouchTypeEnum.MoveOut: 'SetButtonTouchMoveOutCallback',
ButtonTouchTypeEnum.TouchCancel: 'SetButtonTouchCancelCallback'
}
def RegisterScreenFastly(cls, UiScreenDef):
"""
快速注册Ui界面,其中:
--namespace为ToolsConfig.MOD_NAME
--uiKey为类名
"""
clientApi.RegisterUI(
MOD_NAME, cls.__name__,
'{}.{}'.format(cls.__module__, cls.__name__),
UiScreenDef
)
class ScreenTools(object):
"""
界面工具
"""
autoRegister = set()
screens = {}
@classmethod
def AutoRegister(cls, uiScreenDef):
def register(screenCls):
ScreenTools.autoRegister.add((uiScreenDef, screenCls))
return screenCls
return register
@classmethod
def GetTools(cls, tag):
"""
创建获取工具
:param tag:界面定义路径
:return:
"""
tag = tag + MOD_NAME + MOD_VERSION
instance = CreatedTools.get(tag)
if instance is None:
instance = cls()
CreatedTools[tag] = instance
return instance
def ExtendTools(self, tag):
"""
继承已经存在的工具
:param tag:界面定义路径
:return:
"""
tag = tag + MOD_NAME + MOD_VERSION
instance = CreatedTools.get(tag)
if instance is None:
instance = type(self)()
instance.buttonTouchBackFunc = copy.deepcopy(self.buttonTouchBackFunc)
CreatedTools[tag] = instance
return instance
def __init__(self):
self.buttonTouchBackFunc = {}
self.bindingInstance = None
def Binding(self, screenInstance):
self.bindingInstance = screenInstance
self.FirstAddTouchBackFunc()
self.screens[screenInstance.__class__.__name__] = screenInstance
@classmethod
def GetScreenByClsName(cls, clsName):
return cls.screens.get(clsName)
def ButtonCallFuncBackBinder(self, touchType, buttonPath):
"""
绑定按钮回调函数
:param buttonPath:按钮路径
:param touchType:接触类型,位于ToolsConfig.ButtonTouchTypeEnum
"""
def _binding(func):
items = self.buttonTouchBackFunc.get(touchType, set())
if not items: self.buttonTouchBackFunc[touchType] = items
items.add((buttonPath, func.__name__))
if self.bindingInstance:
button = self.bindingInstance.GetBaseUIControl(buttonPath)
if button:
button = button.asButton()
button.AddTouchEventParams()
getattr(button, ButtonTouchMap[touchType])(getattr(self.bindingInstance, func.__name__))
return func
return _binding
def FirstAddTouchBackFunc(self):
if self.bindingInstance:
for touchType, backFuncs in self.buttonTouchBackFunc.items():
for buttonPath, backFuncName in backFuncs:
button = self.bindingInstance.GetBaseUIControl(buttonPath)
if button:
button = button.asButton()
button.AddTouchEventParams()
getattr(button, ButtonTouchMap[touchType])(getattr(self.bindingInstance, backFuncName))
@staticmethod
def CollectionViewBinder(bindFlag, collectionName, bindingName, func):
"""
绑定数据函数,为执行函数添加一个新的参数:collectionName.此collectionName与形参collectionName一致
这是一个逆天工具,改造网易屎山
若不传入cls,确保在binding之后调用
:param func:必须是bound函数,即self.func
:param bindFlag:
:param collectionName:
:param bindingName:
:return:
"""
def __(*args):
return func(collectionName, args[1])
cls = func.im_class
funcBindingName = collectionName + bindingName
setattr(cls, funcBindingName, __)
newFunc = getattr(cls, funcBindingName).im_func
newFunc.__name__ = funcBindingName
viewBinder.binding_collection(bindFlag, collectionName, bindingName)(newFunc)
@@ -0,0 +1,166 @@
# coding=utf-8
import copy
from BlueFoxSystem import BlueFoxServerSystem
from LocalCoord import FixPosition, OriginCoord, RotCoord, RotAxes, BlockFaceRot
from ..OtherTools import Mount
from ..ToolsConfig import *
blockBlocks = {}
blockEntityDataComp = serverCompFactory.CreateBlockEntityData(LevelId)
def GetLoadedBlockBlockByPosDim(posDim):
# type:(Tuple[int,int,int,int]) -> BlockBlock
"""
通过维度坐标获取已经加载的方块块
"""
return blockBlocks.get(posDim)
def GetLoadedBlockBlock():
# type:() -> List[BlockBlock]
"""
获取所有已加载的方块块
"""
return list(set(blockBlocks.values()))
@Mount(BlueFoxServerSystem)
class BlockBlock(object):
"""
方块块,用于解决模型体积过大导致的模型消失的问题
blockList = {
(0, 0, 0): {'name': 'rtdd:carrotandbeef', 'aux': 0}
(相对坐标位置): {'name': 方块ID, 'aux': 方块AUX}
}
"""
blueFoxSystem = BlueFoxServerSystem if 0 else None
blockList = {}
@classmethod
def getInstance(cls, posDim):
instance = blockBlocks.get(posDim)
if instance: return instance
data = blockEntityDataComp.GetBlockEntityData(posDim[3], posDim[:3])
if data and data['isCenter']:
posDims = [tuple(_) for _ in data['posDims']]
return cls(tuple(data['centerPosDim']), data['aux'], posDims)
blockData = serverCompFactory.CreateBlockInfo(LevelId).GetBlockNew(posDim[:3], posDim[3])
return cls(posDim, blockData['aux'])
def __init__(self, posDim, aux, posDims=None):
# type:(Tuple,int,List[Tuple]) -> None
self.blockPosDims = posDims # 方块块控制坐标
self.centerPosDim = posDim # 控制中心
self.aux = aux
if self.blockPosDims is None:
self.blockPosDims = self.CreatePosDims(posDim, aux)
self.CreateBlocks()
self.dataComp = blockEntityDataComp.GetBlockEntityData(self.centerPosDim[3], self.centerPosDim[:3])
self.dataComp['posDims'] = self.blockPosDims
self.dataComp['aux'] = self.aux
for posDim in self.blockPosDims:
blockBlocks[posDim] = self
def ReplaceBlocks(self, newBlockList, otherData=None, drop=0):
"""
替换方块,新方块依然具有方块实体的要求
"""
oldData = {
'posDims': self.blockPosDims,
'aux': self.aux
}
if otherData is None: otherData = {}
oldData.update(otherData)
self.DestroyBlocks(drop)
self.CreateBlocks(newBlockList)
def DestroyFunction(self):
"""
销毁处理逻辑不清除方块
"""
for posDim in self.blockPosDims:
del blockBlocks[posDim]
def Destroy(self, drop=0):
"""
销毁方块块
:param drop:是否掉落核心方块
"""
self.DestroyFunction()
self.DestroyBlocks(drop)
def DestroyBlocks(self, drop=0):
"""
销毁方块块所控制的方块
"""
blockComp = serverCompFactory.CreateBlockInfo(LevelId)
posDim = copy.copy(self.blockPosDims)
posDim.remove(self.centerPosDim)
for posDim in posDim:
blockComp.SetBlockNew(
posDim[:3], blockDict={'name': 'minecraft:air'}, dimensionId=posDim[3]
)
blockComp.SetBlockNew(
self.centerPosDim[:3], blockDict={'name': 'minecraft:air'}, dimensionId=self.centerPosDim[3],
oldBlockHandling=1 if drop else 0
)
def CreateBlocks(self, blockList=None):
"""
放置方块块所控制的方块
"""
if blockList is None:
blockList = self.blockList
blockComp = serverCompFactory.CreateBlockInfo(LevelId)
for pos in self.blockList:
blockInfo = blockList[pos]
posDim = self.CalculatePosDimFromOffset(pos, self.centerPosDim, self.aux)
blockComp.SetBlockNew(posDim[:3], blockDict=blockInfo, dimensionId=posDim[3])
dataComp = blockEntityDataComp.GetBlockEntityData(posDim[3], posDim[:3])
dataComp['isCenter'] = True if not cmp(pos, (0, 0, 0)) else False
dataComp['centerPosDim'] = self.centerPosDim
self.dataComp = serverCompFactory.CreateBlockEntityData(LevelId).GetBlockEntityData(
self.centerPosDim[3], self.centerPosDim[:3]
)
def IsSelf(self, posDim):
"""
判断一个维度坐标是否为本方块块
"""
for posDimSelf in self.blockPosDims:
if not cmp(posDimSelf, posDim):
return True
return False
@classmethod
def CreatePosDims(cls, posDim, aux):
# type:(Tuple,int) -> List[Tuple]
"""
创建方块块控制坐标
"""
return [cls.CalculatePosDimFromOffset(pos, posDim, aux) for pos in cls.blockList]
@classmethod
def CalculatePosDimFromOffset(cls, offset, posDim, aux):
"""计算偏移坐标维度"""
originCoord = OriginCoord(posDim)
_, __, ___ = FixPosition(originCoord.AbsPos(
RotCoord(BlockFaceRot[aux], RotAxes.Y).CalculatePos(offset)
))
return _, __, ___, posDim[3]
@classmethod
def Able(cls, posDim, aux):
"""
判断是否可以生成块
"""
blockComp = serverCompFactory.CreateBlockInfo(LevelId)
for posDim in cls.CreatePosDims(posDim, aux):
block = blockComp.GetBlockNew(posDim[:3], posDim[3])
if block is None or (block and block['name'] != 'minecraft:air'):
return False
return True
@@ -0,0 +1,116 @@
# coding=utf-8
import time
from math import *
class GraduallySum(object):
"""
渐变数
"""
def __init__(self, coefficient, target, initValue):
self.target = target
self.coefficient = coefficient
self.value = initValue
def ResetCoefficient(self, newCoefficient):
self.coefficient = newCoefficient
def Refresh(self):
self.value += self.coefficient * (self.target - self.value)
def ResetTarget(self, newTarget):
self.target = newTarget
class GraduallySumTime(GraduallySum):
"""
时间相关渐变数
"""
def __init__(self, coefficient, target, initSum):
self.time = time.time()
super(GraduallySumTime, self).__init__(coefficient, target, initSum)
def Refresh(self):
_time = time.time()
coe = self.coefficient * (_time - self.time)
# 放置系数过大
coe = coe if coe < 0.99 else 0.99
self.value += coe * (self.target - self.value)
def Clamp(value, _min, _max):
# type:(float,float,float) -> float
return _max if value > _max else _min if value < _min else value
def ComBin(i, place, result):
# type:(int,int,int) -> bool
"""
提取参数i位于place的结果并于result比较
例如输入:0b00101,0b10000,0b01110,则会返回false
"""
return i & place == result & place
def BinCal(i, place, result):
# type:(int,int,bool) -> int
"""
为参数i设置位于place的result结果
例如输入:0b00100,0b00101,True
输出将会是:0b00101
"""
return ((i | place) ^ place) | place * result
def CosOfTwoVec(vec1, vec2):
"""
获取两个相同维度向量的夹角cos值
"""
if vec1.__len__() != vec2.__len__():
return 0
nv1 = Normalize(vec1)
nv2 = Normalize(vec2)
_ = 0
for index in range(nv1.__len__()):
_ += nv1[index] * nv2[index]
return _
def Normalize(vec):
# type:(Union[List,Tuple]) -> Union[List,Tuple]
"""
将任意维度向量转换为单位向量
"""
r = 0
for _ in vec:
r += _ ** 2.0
r = r ** 0.5
cls = vec.__class__
return cls([(_ / r) for _ in vec])
def GetTwoPointVec(pos1, pos2):
# type:(Union[List,Tuple],Union[List,Tuple]) -> Tuple
"""
计算任意两点的方向向量, 非单位向量, pos1 -> pos2
"""
li = []
if pos1.__len__() != pos2.__len__():
return tuple(li)
for index in range(pos1.__len__()):
li.append(pos2[index] - pos1[index])
return tuple(li)
def MatProduct(vec, mat):
"""
计算向量或者坐标与矩阵相乘, 需要i=j
"""
new = [0.0 for _ in range(vec.__len__())]
lengthIndex = range(vec.__len__())
for _ in lengthIndex:
for __ in lengthIndex:
new[_] += vec[__] * mat[_][__]
return new
@@ -0,0 +1,660 @@
# coding=utf-8
from BlueFoxSystem import BlueFoxClientSystem
from ControllableUi.Animation import MoveToPos
from ..ItemTools import IsSameItem, GetItemBaseInfoSafely, GetItemDurabilityPercent, DeclareItemAnyNum, IsItemDict
from ..OtherTools import Mount, FixList
from ..ScreenTools import ScreenTools
from ..ToolsConfig import *
class SlotOffset(dict):
def __getitem__(self, item):
return super(SlotOffset, self).get(item, 0)
@Mount(BlueFoxClientSystem)
class RucksackScreen(baseScreen):
flyingItemRender = None
slotOffset = SlotOffset()
flyingMoveControl = None # type:MoveToPos
blueFoxClient = BlueFoxClientSystem if 0 else None
def __init__(self, name_spase, ui_key, param, items=None, customData=None, flyingPanelPath=None):
super(RucksackScreen, self).__init__(name_spase, ui_key, param)
self._selectSlot = -1
self.__itemNum = [] # 物品数量
self.__itemD = [] # (目前,总) 耐久度
self.__itemId = [] # 物品ID
self.__isItemNone = [] # 是否为缺失物品
self.flyingItem = {} # 跟随鼠标的物品
self.moveControls = []
self.exchangeLock = False
self.items = items # 所有物品
if self.items is None: self.items = [] # type:List[Dict]
self.customData = customData # 自定义数据
self.gridIndex = {}
self.flyingPanelPath = flyingPanelPath
self.offsetSlot = {}
self._selectSlot = -1
@property
def selectSlot(self):
return self._selectSlot
@selectSlot.setter
def selectSlot(self, index):
self._selectSlot = index
def GetStartAndFinalIndex(self, collectName):
"""
获取起止索引段
:param collectName:
:return:
"""
offsetKey = self.offsetSlot.keys()
offsetKey.sort()
offset = self.slotOffset[collectName]
if offset == offsetKey[-1]: return offset, len(self.items)
for index in range(len(offsetKey) - 1):
if offset == offsetKey[index]: return offset, offsetKey[index + 1]
def RecalculateOffsetSlot(self):
"""
重新计算OffsetSlot
:return:
"""
self.offsetSlot = {}
for _ in self.slotOffset.items(): self.offsetSlot[_[1]] = _[0]
def PrintSlotName(self):
offsetKey = self.offsetSlot.keys()
offsetKey.sort()
for _ in offsetKey:
print (_, self.offsetSlot[_])
print '--------------'
def AddSlot(self, slotName, gridPath, items, dim, refresh=True):
"""
增加槽位,需要在Create函数执行之前绑定所需的connectionName
:param slotName: 槽位名称
:param refresh: 是否刷新数据
:param gridPath: 网格路径
:param items: 物品集
:param dim: 网格尺寸
:return:
"""
if slotName in self.slotOffset: return
self.InitSlot(slotName, gridPath, items, dim)
self.InitItemData() if refresh else 0
def DelSlot(self, slotName, refresh=True):
"""
删除槽位,包括items,slotOffset,offsetSlot
:param slotName:槽位名称
:param refresh: 是否刷新数据
:return:
"""
if not slotName in self.slotOffset: return
offsetKey = self.offsetSlot.keys()
offsetKey.sort()
startAndEndIndex = self.GetStartAndFinalIndex(slotName)
oldOffset = startAndEndIndex[0]
size = startAndEndIndex[1] - startAndEndIndex[0]
del self.items[startAndEndIndex[0]: startAndEndIndex[1]]
del self.offsetSlot[oldOffset]
del self.slotOffset[slotName]
del self.gridIndex[slotName]
for index in offsetKey[::-1]:
if oldOffset < index: self.slotOffset[self.offsetSlot[index]] = index - size
self.RecalculateOffsetSlot()
self.InitItemData() if refresh else 0
# ------------------------------ init ------------------------------
def InitItemDataOne(self, slot, slotName, item):
"""
对某个槽位进行初始化,需要在InitData之后调用
"""
if item:
itemInfo = GetItemBaseInfoSafely(item)
self.__itemNum[slot] = item['count']
self.__itemId[slot] = itemInfo['id_aux']
self.__itemD[slot] = GetItemDurabilityPercent(item, itemInfo)
self.__isItemNone[slot] = False
else:
self.__itemNum[slot] = 0
self.__itemD[slot] = (0.0, 0.0)
self.__itemId[slot] = 0
self.__isItemNone[slot] = False
def InitItemData(self):
"""
依据当前items初始化显示所需的物品数据
:return:
"""
self.moveControls = [None for _ in self.items]
self.__itemNum = [0 for _ in self.items]
self.__itemD = [(0.0, 0.0) for _ in self.items]
self.__itemId = [0 for _ in self.items]
self.__isItemNone = [False for _ in self.items]
offsets = self.offsetSlot.keys()
offsets.sort()
for offsetIndex in range(len(offsets[:-1])):
for index in range(offsets[offsetIndex], offsets[offsetIndex + 1]):
if not IsItemDict(self.items[index]): self.items[index] = {}
self.InitItemDataOne(index, self.offsetSlot[offsets[offsetIndex]], self.items[index])
for index in range(offsets[-1], len(self.items)):
if not IsItemDict(self.items[index]): self.items[index] = {}
self.InitItemDataOne(index, self.offsetSlot[offsets[-1]], self.items[index])
def InitFlyingPanel(self, path):
self.flyingItemRender = self.GetBaseUIControl(path).asItemRenderer()
self.flyingMoveControl = MoveToPos(self.flyingItemRender, 0.1)
def InitSlot(self, slotName, gridPath, items, dimension):
"""
初始化物品槽
:param dimension: 物品槽网格的尺寸
:param items: 物品槽内的物品
:param slotName:物品槽名称
:param gridPath:物品槽的网格路径
:return:
"""
items = FixList(items, dimension[0] * dimension[1], {})
self.GetBaseUIControl(gridPath).asGrid().SetGridDimension(tuple(dimension))
self.gridIndex[slotName] = gridPath
offset = len(self.items)
self.slotOffset[slotName] = offset
self.offsetSlot[offset] = slotName
self.items += items
self.BindingAll(slotName)
# ------------------------------ init ------------------------------
def PlayerInvItemChanged(self, args):
"""
玩家背包服务端主动发生改变时触发
"""
self.SetUiVisibleInfo(args['item'], args['slot'])
def Create(self):
self.blueFoxClient.ListenForEvent(
MOD_NAME, 'BlueFoxServerSystem', 'PlayerInvItemChanged', self, self.PlayerInvItemChanged
)
def Destroy(self):
self.blueFoxClient.UnListenForEvent(
MOD_NAME, 'BlueFoxServerSystem', 'PlayerInvItemChanged', self, self.PlayerInvItemChanged
)
playerId = clientApi.GetLocalPlayerId()
x, y, z = clientCompFactory.CreatePos(playerId).GetFootPos()
rot = clientCompFactory.CreateRot(playerId).GetRot()
rx, ry, rz = clientApi.GetDirFromRot(rot)
self.blueFoxClient.NotifyToServer('RucksackScreenClosed', {
'posDim': (
(x + rx * 0.3, y + 1.08, z + rz * 0.3), clientCompFactory.CreateGame(LevelId).GetCurrentDimension()
), 'playerRot': rot
})
for moveControl in self.moveControls: moveControl.Finish() if moveControl else 0
def ItemCellGridInit(self, gridPath, collectionName):
"""
物品槽网格初始化时触发
:param gridPath:网格路径
:param collectionName:集合名称
:return:
"""
pass
def BindingAll(self, collectionName):
bindingFunc = ScreenTools.CollectionViewBinder
bindingBool = viewBinder.BF_BindBool
bindingInt = viewBinder.BF_BindInt
bindingFloat = viewBinder.BF_BindFloat
bindingString = viewBinder.BF_BindString
bindingFunc(bindingBool, collectionName, '#item_num_visible', self.ItemsNumVisible) # 是否显示物品数量文字
bindingFunc(bindingString, collectionName, '#item_num', self.ItemsNum) # 显示物品数量
bindingFunc(bindingBool, collectionName, '#select_visible', self.SelectVisible) # 是否显示物品槽选择
bindingFunc(bindingInt, collectionName, '#item_id_aux', self.RenderItems) # 显示物品
bindingFunc(bindingBool, collectionName, '#item_durability_visible', self.ItemsDurabilityVisible) # 是否显示物品耐久度条
bindingFunc(
bindingFloat, collectionName, '#item_durability_total_amount', self.ItemsDurabilityTotal
) # 物品耐久度条总量
bindingFunc(
bindingFloat, collectionName, '#item_durability_current_amount', self.ItemsDurabilityCurrent
) # 物品耐久度条当前量
bindingFunc(bindingBool, collectionName, '#item_none_visible', self.ItemsNone) # 是否显示缺失物品背景
bindingFunc(bindingBool, collectionName, '#item_cell_init', self.ItemCellInit) # 初始化时触发
# --------------------- bindings ---------------------
def GetGirdPath(self, collectionName):
return self.gridIndex[collectionName]
def ItemCellInit(self, collectionName, index):
"""
初始化时触发,无需任何返回值
:param collectionName:集合名称
:param index:在网格内的索引值
:return:
"""
if not index: self.ItemCellGridInit(self.GetGirdPath(collectionName), collectionName)
def ItemsNumVisible(self, collectionName, index):
index = index + self.slotOffset.get(collectionName, 0)
return self.__itemNum[index] > 1 if index < self.__itemNum.__len__() else False
def ItemsNum(self, collectionName, index):
index = index + self.slotOffset.get(collectionName, 0)
return str(self.__itemNum[index]) if index < self.__itemNum.__len__() else ''
def SelectVisible(self, collectionName, index):
index = index + self.slotOffset.get(collectionName, 0)
return index == self.selectSlot
def RenderItems(self, collectionName, index):
index = index + self.slotOffset.get(collectionName, 0)
return self.__itemId[index] if index < self.__itemId.__len__() else 0
def ItemsDurabilityVisible(self, collectionName, index):
index = index + self.slotOffset.get(collectionName, 0)
if index >= self.__itemD.__len__():
return False
info = self.__itemD[index]
return info[0] != info[1] and info[1] > 0
def ItemsDurabilityTotal(self, collectionName, index):
index = index + self.slotOffset.get(collectionName, 0)
return self.__itemD[index][1] if index < self.__itemD.__len__() else 0.0
def ItemsDurabilityCurrent(self, collectionName, index):
index = index + self.slotOffset.get(collectionName, 0)
return self.__itemD[index][0] if index < self.__itemD.__len__() else 0.0
def ItemsNone(self, collectionName, index):
index = index + self.slotOffset.get(collectionName, 0)
return self.__isItemNone[index] if index < self.__isItemNone.__len__() else False
# --------------------- bindings ---------------------
def SetNoneItem(self, slot, itemDict):
"""
将某个槽位设置为缺少物品
:param slot:
:param itemDict:
:return:
"""
itemInfo = GetItemBaseInfoSafely(itemDict)
self.__itemNum[slot] = itemDict['count']
self.__itemD[slot] = GetItemDurabilityPercent(itemDict, itemInfo)
self.__itemId[slot] = itemInfo['id_aux']
self.__isItemNone[slot] = True
self.items[slot] = {}
def NoticeServerChangeItem(self, slot, itemDict, itemOperate):
self.blueFoxClient.NotifyToServer(
'ChangeItem', {
'slot': slot, 'item': itemDict, 'itemOperate': itemOperate,
'posDim': None, 'posType': self.GetOffsetNameByIndex(slot),
'startSlot': self.GetOffsetStartIndex(slot)
}
)
def NoticeServerSpawnItemEntities(self, itemDict):
playerId = clientApi.GetLocalPlayerId()
x, y, z = clientCompFactory.CreatePos(playerId).GetFootPos()
self.blueFoxClient.NotifyToServer("SpawnItemEntity", {
'posDim': (
(x, y + 1.6, z), clientCompFactory.CreateGame(LevelId).GetCurrentDimension()
), 'item': itemDict,
'playerRot': clientCompFactory.CreateRot(playerId).GetRot()
})
def SetItem(self, slot, itemDict=None, noticeServer=True):
"""
设置某个槽位, 不论是否物品为空, 返回原本物品
:param noticeServer:
:param itemDict:
:param slot:
:return:
"""
return self.SetUiItem(slot, itemDict, noticeServer) if itemDict else self.SetUiItemNone(slot, noticeServer)
def SetItemAbs(self, slot, itemDict=None, noticeServer=False):
"""
设置某个槽位, 不论是否物品为空
:param noticeServer:
:param itemDict:
:param slot:
:return:
"""
self.SetUiItemAbs(slot, itemDict, noticeServer) if itemDict else self.SetUiItemNone(slot, noticeServer)
def SetUiItemNone(self, slot, noticeServer=None):
"""
将某个槽位设置为空,返回原本物品
:param noticeServer:
:param slot:
:return:
"""
originItem = self.items[slot]
self.SetUiItemAbs(slot, {}, noticeServer)
return originItem
def SetUiItemAbs(self, slot, destinationItem, noticeServer=True, itemOperate=None):
"""
设置某一个槽位的物品, 不返回任何物品及合并
:param itemOperate: 物品操作,用于服务端操作
:param noticeServer: 是否通知服务端,默认发送
:param slot:设置槽位
:param destinationItem:目标物品字典
:return:
"""
destinationItemInfo = GetItemBaseInfoSafely(destinationItem)
self.__itemNum[slot] = destinationItem.get('count', 0)
self.__itemD[slot] = GetItemDurabilityPercent(destinationItem, destinationItemInfo)
self.__itemId[slot] = destinationItemInfo.get('id_aux', 0)
self.__isItemNone[slot] = False
self.items[slot] = destinationItem
self.NoticeServerChangeItem(slot, destinationItem, itemOperate) if noticeServer else 0
def SetUiItem(self, slot, destinationItem, noticeServer=True):
"""
设置某一个槽位的物品,返回原本物品如果不可合并或者合并后多出的物品
:param noticeServer: 是否通知服务端
:param slot:设置槽位
:param destinationItem:目标物品字典
:return:
"""
originItem = self.items[slot]
originItemInfo = GetItemBaseInfoSafely(destinationItem)
if IsSameItem(originItem, destinationItem):
if originItem['count'] + destinationItem['count'] <= originItemInfo['maxStackSize']:
destinationItem['count'] += originItem['count']
originItem = {}
else:
originItem['count'] += destinationItem['count'] - originItemInfo['maxStackSize']
destinationItem['count'] = originItemInfo['maxStackSize']
self.SetUiItemAbs(slot, destinationItem, noticeServer)
return originItem
def SetUiVisibleInfo(self, itemDict, slot):
"""
设置物品,通常用于服务端主动设置物品,此接口不会发送ChangeItem事件
:param itemDict:物品信息字典
:param slot:设置槽位
:return:
"""
self.SetUiItemAbs(slot, itemDict, False)
def GetOffsetNameByIndex(self, index):
"""
通过index获取偏移名称
:param index:
:return:
"""
offsetKey = self.offsetSlot.keys()
offsetKey.sort()
offset = offsetKey[-1]
for _ in range(offsetKey.__len__() - 1):
if offsetKey[_] <= index < offsetKey[_ + 1]:
return self.offsetSlot[offsetKey[_]]
return self.offsetSlot[offset]
def GetOffsetStartIndex(self, index):
"""
通过index获取偏移起始index
:param index:
:return:
"""
offsetKey = self.offsetSlot.keys()
offsetKey.sort()
offset = offsetKey[-1]
for _ in range(offsetKey.__len__() - 1):
if offsetKey[_] <= index < offsetKey[_ + 1]:
return offsetKey[_]
return offsetKey[-1]
def GetItemCellByIndex(self, index):
"""
通过槽位获取itemCell的路径
:param index:槽位编号
:return:
"""
offset = self.GetOffsetStartIndex(index)
return self.gridIndex[self.offsetSlot[offset]] + '/item_cell{}'.format(index - offset + 1)
# 以下三个方法是pe背包槽位交互模式
def FlyingMoveFinish(self, slot):
"""
pe下槽位发生物品堆叠时, flyRender动画完成时触发
:return:
"""
self.flyingItemRender.SetVisible(False) if self.flyingItemRender else 0
self.NoticeServerChangeItem(
slot, self.flyingItem,
ItemOperate.ExchangeFlying(
slot - self.GetOffsetStartIndex(slot), self.GetOffsetNameByIndex(slot)
)
)
self.flyingItem = {}
def ExchangeFinish(self, slot, destinationSlot):
"""
pe下槽位发生物品交换时, 动画完成时触发
:param destinationSlot: 与其交换的槽位
:param slot: 槽位
:return:
"""
deSlotName = self.GetOffsetNameByIndex(destinationSlot)
deSlotOffset = destinationSlot - self.GetOffsetStartIndex(destinationSlot)
fromSlotName = self.GetOffsetNameByIndex(slot)
fromSlotOffset = slot - self.GetOffsetStartIndex(slot)
originItem = self.items[slot]
destinationItem = self.items[destinationSlot]
self.SetUiItemAbs(slot, destinationItem, False)
deSlot = self.slotOffset[deSlotName] + deSlotOffset
if deSlotName not in self.slotOffset or deSlot >= len(self.items):
self.flyingItem = originItem
else:
self.SetUiItemAbs(deSlot, originItem, False)
self.NoticeServerChangeItem(slot, {}, ItemOperate.Exchange(
fromSlotOffset, fromSlotName,
deSlotOffset, deSlotName
))
self.flyingItem = {}
def StackFinish(self, slot, destinationSlot):
"""
pe下槽位发生物品堆叠时, slot动画完成时触发
:param slot: 槽位
:param destinationSlot: 与其交换的槽位
:return:
"""
orSlotName = self.GetOffsetNameByIndex(slot)
orSlotOffset = slot - self.GetOffsetStartIndex(slot)
deSlotName = self.GetOffsetNameByIndex(destinationSlot)
deSlotOffset = destinationSlot - self.GetOffsetStartIndex(destinationSlot)
self.flyingItem = self.SetItem(destinationSlot, self.items[slot], False)
orSlot = self.slotOffset[orSlotName] + orSlotOffset
if orSlotName not in self.slotOffset or orSlot >= len(self.items):
self.flyingItem = {}
else:
self.SetItemAbs(slot, self.flyingItem, False)
self.flyingItem = {}
self.flyingItemRender.SetVisible(False, False) if self.flyingItemRender else 0
self.NoticeServerChangeItem(slot, {}, ItemOperate.Merge(
orSlotOffset, orSlotName, deSlotOffset, deSlotName
))
@staticmethod
def SetUiItemForItemRender(render, itemDict):
"""
使用itemDict设置物品渲染
:param render:itemRender
:param itemDict:需要设置的物品字典
:return:
"""
if not itemDict:
render.SetVisible(False)
return
render.SetUiItem(
itemDict.get('newItemName', 'minecraft:air'), itemDict.get('newAuxValue', 0),
(itemDict.get('enchantData', []) + itemDict.get('modEnchantData', [])).__len__() > 0,
itemDict.get('userData', None)
)
def PcDown(self, selectSlot):
"""
电脑点击物品槽触发
:param selectSlot:槽位
:return:
"""
oldItem = self.items[selectSlot]
if self.flyingItem is None: self.flyingItem = {}
self.SetUiItemAbs(
selectSlot, self.flyingItem, True,
ItemOperate.ExchangeFlying(
selectSlot - self.GetOffsetStartIndex(selectSlot), self.GetOffsetNameByIndex(selectSlot)
)
)
self.flyingItem = oldItem
if self.flyingItemRender:
self.SetUiItemForItemRender(
self.flyingItemRender, self.flyingItem
) if self.flyingItem else self.flyingItemRender.SetVisible(False, False)
def GetSlotAnimationState(self, originSlot, destinationSlot):
"""
pe模式下, 交换物品时根据槽位确认动画形式
:param originSlot:先前选择槽位
:param destinationSlot:后选择槽位
:return:先前, 后选择, flyingItem
"""
originItem = self.items[originSlot]
destinationItem = self.items[destinationSlot]
itemInfo = GetItemBaseInfoSafely(originItem)
if IsSameItem(originItem, destinationItem):
if originItem['count'] + destinationItem['count'] < itemInfo['maxStackSize']:
return True, False, False
elif destinationItem['count'] == itemInfo['maxStackSize']:
return True, True, False
else:
return False, False, True
return True, True, False
def GetPeFlyingItem(self, originSlot, destinationSlot):
"""
在pe模式下, 获取flyingItem
:param originSlot: 现物品槽位
:param destinationSlot: 目标物品槽位
:return:
"""
itemInfo = GetItemBaseInfoSafely(self.items[originSlot])
maxStack = (itemInfo or {}).get('maxStackSize') or 64
itemDict1, itemDict2 = DeclareItemAnyNum(
self.items[originSlot], maxStack - self.items[destinationSlot]['count'])
self.SetItemAbs(originSlot, itemDict1)
return itemDict2
def DeliverItem(self, originSlot, destinationSlot):
"""
以动画形式从origin向destination传递物品
:param originSlot:起源物品槽位
:param destinationSlot:目标物品槽位
:return:
"""
ox, oy = self.GetAbsPos(self.GetItemCellByIndex(originSlot) + '/item_cell/item_renderer')
dx, dy = self.GetAbsPos(self.GetItemCellByIndex(destinationSlot) + '/item_cell/item_renderer')
_, __, ___ = self.GetSlotAnimationState(originSlot, destinationSlot)
if _:
self.moveControls[originSlot].StartMove(
(dx - ox, dy - oy), self.ExchangeFinish if __ else self.StackFinish, originSlot, destinationSlot
)
if __:
self.moveControls[destinationSlot].StartMove(
(-dx + ox, -dy + oy), lambda: None
)
if ___:
self.SetUiItemForItemRender(self.flyingItemRender, self.items[originSlot]) if self.flyingItemRender else 0
self.flyingItemRender.SetVisible(True) if self.flyingItemRender else 0
self.flyingMoveControl.ResetInitialPos((ox, oy))
self.flyingMoveControl.StartMove(
(dx, dy), self.StackFinish, originSlot, destinationSlot
)
def PeDown(self, selectSlot):
"""
手机点击物品槽触发
:param selectSlot:槽位
:return:
"""
if self.selectSlot == selectSlot:
self.selectSlot = -1
return
else:
if self.selectSlot is not -1:
if self.items[self.selectSlot]:
self.DeliverItem(self.selectSlot, selectSlot)
self.selectSlot = -1
return
if self.items[selectSlot]:
self.selectSlot = selectSlot
self.flyingItemRender.SetPosition(
self.GetAbsPos(self.GetItemCellByIndex(selectSlot) + '/item_cell/item_renderer')
) if self.flyingItemRender else 0
self.flyingMoveControl.ResetInitialPos()
@viewBinder.binding(viewBinder.BF_ButtonClickUp, '#item_cell_touch')
def ButtonUp(self, args):
"""
物品槽被点击会触发无论电脑或者手机
:param args:与普通的按钮回调参数一致
:return:
"""
touchIndex = args['#collection_index'] + self.slotOffset.get(args['#collection_name'], 0)
self.CreateItemRenderMoveControl(touchIndex)
self.PeDown(touchIndex) if Platform else self.PcDown(touchIndex)
def CreateItemRenderMoveControl(self, slot):
"""
创建物品渲染的移动动画控制器
:param slot:
:return:
"""
if self.moveControls[slot] is None:
itemCell = self.GetBaseUIControl(self.GetItemCellByIndex(slot))
self.moveControls[slot] = MoveToPos(
itemCell.GetChildByName('item_cell').GetChildByName('item_renderer'), 0.1
)
def GetAbsPos(self, path):
"""
获取元素的绝对坐标
:param path:路径
:return:绝对坐标, xy
"""
control = self.GetBaseUIControl(path)
return control.GetGlobalPosition() if control else (0, 0)
@viewBinder.binding(viewBinder.BF_BindInt, '#main.gametick')
def GameTick(self):
if clientCompFactory and not Platform and self.flyingItemRender and self.flyingItem:
x, y = clientCompFactory.CreateActorMotion(clientApi.GetLocalPlayerId()).GetMousePosition()
self.flyingItemRender.SetPosition((x - 9, y - 9))
self.flyingItemRender.SetVisible(self.flyingItem, False)
for moveControl in self.moveControls:
moveControl.Refresh() if moveControl else 0
self.flyingMoveControl.Refresh() if self.flyingMoveControl else 0
def CreateItemEntityIfFlyItem(self):
"""
创建跟随鼠标的物品
:return:
"""
if self.flyingItem: self.NoticeServerSpawnItemEntities(self.flyingItem)
@@ -0,0 +1,117 @@
# coding=utf-8
from Grapher import *
class SizeAnim(MovableUi):
def __init__(self, base_ui_control, time):
# type:(BaseUIControl,float)->None
self.initialSize = base_ui_control.GetSize()
self.totalTime = float(time)
self.backFunc = lambda *args: None
self.args = []
self.fromSize = (0, 0)
self.fatherSize = (0, 0)
self.fromPos = (0, 0)
self.speed = 0
super(SizeAnim, self).__init__(base_ui_control)
def ResetInitialSize(self, initialSize=None):
"""
重置原位置
:return:
"""
self.initialSize = self.baseUiControl.GetSize() if initialSize is None else initialSize
def Reset(self):
super(SizeAnim, self).Reset()
self.baseUiControl.SetSize(self.initialSize)
self.backFunc = lambda: None
self.args = tuple()
def Finish(self):
super(SizeAnim, self).Finish()
self.backFunc(*self.args)
self.Reset()
def StartAnim(self, fromSize, fatherSize, fromPos, backFunc, *backFuncArgs):
self.fromSize = fromSize
self.fatherSize = fatherSize
self.fromPos = fromPos
self.speedX = (fromSize[0] - self.initialSize[0]) / self.totalTime
self.speedY = (fromSize[1] - self.initialSize[1]) / self.totalTime
self.args = backFuncArgs
self.backFunc = backFunc
super(SizeAnim, self).Start()
def Refresh(self):
if self and self.state != ControlState.Run:
return
runTime = self.GetWorkTime()
if runTime < self.totalTime:
size = (
self.fromSize[0] - self.speedX * runTime, self.fromSize[1] - self.speedY * runTime
)
self.baseUiControl.SetSize(size)
position = ((self.fatherSize[0] - size[0]) / 2.0, (self.fatherSize[1] - size[1]) / 2.0)
self.baseUiControl.SetPosition(position)
else:
self.baseUiControl.SetPosition(self.fromPos)
self.baseUiControl.SetSize(self.initialSize)
self.Finish()
return runTime
class MoveToPos(MovableUi):
def __init__(self, base_ui_control, time):
# type:(BaseUIControl,float)->None
self.initialPos = base_ui_control.GetPosition()
self.totalTime = float(time)
self.speedX = 0.0
self.speedY = 0.0
self.destinationPos = (0, 0)
self.backFunc = lambda *args: None
self.args = []
super(MoveToPos, self).__init__(base_ui_control)
def ResetInitialPos(self, initialPos=None):
"""
重置原位置
:return:
"""
self.initialPos = self.baseUiControl.GetPosition() if initialPos is None else initialPos
def Reset(self):
super(MoveToPos, self).Reset()
self.baseUiControl.SetPosition(self.initialPos)
self.backFunc = lambda: None
self.args = tuple()
def Finish(self):
super(MoveToPos, self).Finish()
self.backFunc(*self.args)
self.Reset()
def StartMove(self, destinationPos, backFunc, *backFuncArgs):
self.destinationPos = destinationPos
self.args = backFuncArgs
self.backFunc = backFunc
dx, dy = destinationPos[0] - self.initialPos[0], destinationPos[1] - self.initialPos[1]
self.speedX = dx / self.totalTime
self.speedY = dy / self.totalTime
super(MoveToPos, self).Start()
def Refresh(self):
if self and self.state != ControlState.Run:
return
runTime = self.GetWorkTime()
if runTime < self.totalTime:
self.baseUiControl.SetPosition((
self.speedX * runTime + self.initialPos[0], self.speedY * runTime + self.initialPos[1]
))
else:
self.baseUiControl.SetPosition((
self.destinationPos[0] + self.initialPos[0], self.destinationPos[1] + self.initialPos[1]
))
self.Finish()
return runTime
@@ -0,0 +1,49 @@
# coding=utf-8
from __init__ import *
class MovableUi(Base):
"""
可控的移动性Ui
以当前位置为原点,位置随时间变化
"""
def __init__(self, base_ui_control, moving_route_x=lambda t: 0, moving_route_y=lambda t: 0):
# type:(BaseUIControl,Any,Any)->None
"""
@param base_ui_control: ui控件
@param moving_route_x: x轴的运动路径
@param moving_route_y: y轴的运动路径
"""
self.baseUiControl = base_ui_control
self.movingRouteX = moving_route_x
self.movingRouteY = moving_route_y
self.initialPos = base_ui_control.GetPosition()
Base.__init__(self)
def ResetRouteX(self, routeX=lambda t: 0):
"""
设置为新的X轴运动函数
:param routeX: 函数, 传参运动时间
:return:
"""
self.Reset()
self.movingRouteX = routeX
def ResetRouteY(self, routeY=lambda t: 0):
"""
设置为新的Y轴运动函数
:param routeY: 函数, 传参运动时间
:return:
"""
self.Reset()
self.movingRouteY = routeY
def Refresh(self):
if self.state != ControlState.Run:
return
runTime = self.GetWorkTime()
self.baseUiControl.SetPosition((
self.movingRouteX(runTime) + self.initialPos[0], self.movingRouteY(runTime) + self.initialPos[1]
))
return runTime
@@ -0,0 +1,77 @@
# coding=utf-8
import datetime
try:
from mod.client.ui.controls.baseUIControl import *
from typing import *
except ImportError:
BaseUIControl = None
ImageUIControl = None
LabelUIControl = None
class ControlState(object):
Finish = 0
Run = 1
Suspend = 2
class Base(object):
state = ControlState.Finish
suspendTimePeriod = 0.0
runTime = datetime.datetime.now()
suspendTimePoint = runTime
def __init__(self):
self.runTime = datetime.datetime.now()
self.suspendTimePoint = self.runTime
self.suspendTimePeriod = 0.0
def Refresh(self):
"""
刷新,开始执行移动任务时需要刷新
:return:已执行任务的时间
"""
if not self.state == ControlState.Run:
return
now = (datetime.datetime.now() - self.runTime).total_seconds() - self.suspendTimePeriod
return now
def GetWorkTime(self):
"""
获取执行了多少时间
"""
return (datetime.datetime.now() - self.runTime).total_seconds() - self.suspendTimePeriod
def Suspend(self):
"""暂停任务.与完成不同的是,下一次开始时,继续执行暂停时的状态"""
self.state = ControlState.Suspend
self.suspendTimePoint = datetime.datetime.now()
def Reset(self):
"""重置已运作时间"""
self.runTime = datetime.datetime.now()
def GetState(self):
# type:()->str
"""
获取状态
"""
return self.state
def Start(self):
"""开始任务"""
if self.state == ControlState.Finish:
self.runTime = datetime.datetime.now()
self.state = ControlState.Run
elif self.state == ControlState.Suspend:
self.suspendTimePeriod += (datetime.datetime.now() - self.suspendTimePoint).total_seconds()
self.state = ControlState.Run
def Finish(self):
"""完成任务"""
self.runTime = datetime.datetime.now()
self.suspendTimePoint = self.runTime
self.suspendTimePeriod = 0.0
self.state = ControlState.Finish
# 初始化
@@ -0,0 +1,81 @@
# coding=utf-8
from BlueFoxMath import cos, sin, floor, pi, ceil, MatProduct
def FixPosition(pos):
"""
修正坐标, 通常用于方块坐标
"""
pos = list(pos)
for index in range(pos.__len__()):
pos[index] = int(floor(pos[index]) if ceil(pos[index]) - pos[index] > 0.5 else ceil(pos[index]))
return tuple(pos)
class OriginCoord(object):
"""
对三维坐标进行原点变换
"""
def __init__(self, originPos):
self.originPos = originPos
def CalculatePos(self, pos):
"""
计算变换
"""
x, y, z = pos
return x - self.originPos[0], y - self.originPos[1], z - self.originPos[2]
def AbsPos(self, pos):
"""
逆计算变换
"""
x, y, z = pos
return x + self.originPos[0], y + self.originPos[1], z + self.originPos[2]
class RotAxes(object):
X = 0
Y = 1
Z = 2
BlockFaceRot = [90, 0, 270, 180] # 四面向方块对于不同aux对应不同旋转角度
class RotCoord(object):
"""
对三维坐标进行旋转变换
"""
def __init__(self, rot, axes):
"""
:param rot:旋转角度
:param axes:旋转轴
"""
self.rotation = rot / 180.0 * pi
if axes == RotAxes.X:
self.rotMat = [
[1, 0, 0],
[0, cos(self.rotation), -sin(self.rotation)],
[0, sin(self.rotation), cos(self.rotation)]
]
elif axes == RotAxes.Y:
self.rotMat = [
[cos(self.rotation), 0, sin(self.rotation)],
[0, 1, 0],
[-sin(self.rotation), 0, cos(self.rotation)]
]
else:
self.rotMat = [
[0, cos(self.rotation), -sin(self.rotation)],
[0, sin(self.rotation), cos(self.rotation)],
[0, 0, 1],
]
def CalculatePos(self, pos):
"""
计算变换
"""
return MatProduct(pos, self.rotMat)
@@ -0,0 +1,173 @@
# coding=utf-8
import copy
from ..ToolsConfig import *
class MountDataKeyName(object):
Animation = 'animation'
AnimationController = 'animation_controller'
ScriptAnimation = 'script_animation'
Geometry = 'geometry'
Texture = 'texture'
RenderController = 'render_controller'
Material = 'material'
class BasicMountData(dict):
"""
外挂模型动画基本数据字典
"""
def __init__(self, _data=None):
# type:(dict) -> None
if _data is None:
_data = {}
data = {
MountDataKeyName.Animation: {},
MountDataKeyName.AnimationController: {},
MountDataKeyName.ScriptAnimation: {},
MountDataKeyName.Geometry: {},
MountDataKeyName.Texture: {},
MountDataKeyName.RenderController: [],
MountDataKeyName.Material: {}
}
for kv in _data.items():
data[kv[0]] = kv[1]
super(BasicMountData, self).__init__(data)
def Copy(self):
"""
继承本字典的所有信息
"""
return copy.deepcopy(self)
def SetAnimationData(self, value):
self[MountDataKeyName.Animation] = value
return self
def AddAnimationData(self, key, value):
self[MountDataKeyName.Animation][key] = value
return self
def SetAnimationControllerData(self, value):
self[MountDataKeyName.AnimationController] = value
return self
def AddAnimationControllerData(self, key, value):
self[MountDataKeyName.AnimationController][key] = value
return self
def SetScriptAnimationData(self, value):
self[MountDataKeyName.ScriptAnimation] = value
return self
def AddScriptAnimationData(self, key, value):
self[MountDataKeyName.ScriptAnimation][key] = value
return self
def SetGeometryData(self, value):
self[MountDataKeyName.Geometry] = value
return self
def AddGeometryData(self, key, value):
self[MountDataKeyName.Geometry][key] = value
return self
def SetTextureData(self, value):
self[MountDataKeyName.Texture] = value
return self
def AddTextureData(self, key, value):
self[MountDataKeyName.Texture][key] = value
return self
def SetRenderControllerData(self, value):
self[MountDataKeyName.RenderController] = value
return self
def AddRenderControllerData(self, value):
self[MountDataKeyName.RenderController].append(value)
return self
def SetMaterialData(self, value):
self[MountDataKeyName.Material] = value
return self
def AddMaterialData(self, key, value):
self[MountDataKeyName.Material][key] = value
return self
def RemoveItem(playerId, data):
# type:(Union[str,int],BasicMountData)->None
"""
删除外挂模型
"""
playerRenderComp = clientCompFactory.CreateActorRender(playerId)
RemoveRenderController(playerRenderComp, data['render_controller'])
def LoadItem(playerId, data):
# type:(Union[str,int],BasicMountData)->None
"""
加载外挂模型贴图动画渲染器等
"""
playerRenderComp = clientCompFactory.CreateActorRender(playerId)
LoadGeometry(playerRenderComp, data['geometry'])
LoadTexture(playerRenderComp, data['texture'])
LoadRenderController(playerRenderComp, data['render_controller'])
LoadAnimation(playerRenderComp, data['animation'])
LoadAnimationController(playerRenderComp, data['animation_controller'])
LoadScriptAnimation(playerRenderComp, data['script_animation'])
LoadMaterial(playerRenderComp, data['material'])
def RemoveGeometry(playerRenderComp, data):
for _ in data.items():
playerRenderComp.RemovePlayerGeometry(_[0])
def RemoveRenderController(playerRenderComp, data):
for _ in data:
playerRenderComp.RemovePlayerRenderController(_)
def RemoveAnimationController(playerRenderComp, data):
for _ in data.items():
playerRenderComp.RemovePlayerAnimationController(_[0])
def LoadGeometry(playerRenderComp, data):
for _ in data.items():
playerRenderComp.AddPlayerGeometry(*_)
def LoadTexture(playerRenderComp, data):
for _ in data.items():
playerRenderComp.AddPlayerTexture(*_)
def LoadRenderController(playerRenderComp, data):
for _ in data:
playerRenderComp.AddPlayerRenderController(_)
def LoadAnimation(playerRenderComp, data):
for _ in data.items():
playerRenderComp.AddPlayerAnimation(*_)
def LoadAnimationController(playerRenderComp, data):
for _ in data.items():
playerRenderComp.AddPlayerAnimationController(*_)
def LoadScriptAnimation(playerRenderComp, data):
for _ in data.items():
playerRenderComp.AddPlayerScriptAnimate(*_)
def LoadMaterial(playerRenderComp, data):
for _ in data.items():
playerRenderComp.AddPlayerRenderMaterial(*_)
@@ -0,0 +1,80 @@
# coding=utf-8
import random
from ..ToolsConfig import *
def Probability(odds):
"""
根据输入概率判断一次结果
:param odds:概率float
:return:bool
"""
t = odds / 2.0
d = random.uniform(t, 1.0 - t)
return d - t < random.random() < d + t
class AddProbability(object):
"""
累加概率算法,当进行到times次时,期望行动概率为100%,但并不代表本次判定为100%
"""
def __init__(self, times):
self.runTime = 0
self.times = times
self.k = 2.0 / times ** 2.0
def ChanceOnce(self):
"""
判断一次结果
:return:bool
"""
self.runTime += 1
return Probability(self.runTime * self.k)
class AddProbabilityForMcServerTick(AddProbability):
"""
针对MC系统的特殊算法
status:R运行,S中止,F完成
"""
def __init__(self, maxTime, system, func, *args):
self.system = system
super(AddProbabilityForMcServerTick, self).__init__(int(maxTime * 30.0))
self.system.ListenForEvent(MC_SYSTEM_NAMESPACE, MC_SYSTEM_NAME, 'OnScriptTickServer', self, self.McTick)
self.func = func
self.funcParm = args
self.status = 'R'
def Suspend(self):
"""中止"""
print "递增概率函数中止:{}".format(self.func.__name__)
self.status = 'S'
self.system.UnListenForEvent(MC_SYSTEM_NAMESPACE, MC_SYSTEM_NAME, 'OnScriptTickServer', self, self.McTick)
def Start(self):
"""开始"""
print "递增概率函数中止后再次开始:{}".format(self.func.__name__)
self.status = 'R'
self.system.ListenForEvent(MC_SYSTEM_NAMESPACE, MC_SYSTEM_NAME, 'OnScriptTickServer', self, self.McTick)
def McTick(self):
self.ChanceOnce()
def ChanceOnce(self):
"""
判断一次结果
:return:bool
"""
self.runTime += 1
if Probability(self.runTime * self.k) or self.runTime >= self.times:
self.system.UnListenForEvent(MC_SYSTEM_NAMESPACE, MC_SYSTEM_NAME, 'OnScriptTickServer', self, self.McTick)
self.func(*self.funcParm)
self.status = 'F'
return True
return False
def Destroy(self):
self.system.UnListenForEvent(MC_SYSTEM_NAMESPACE, MC_SYSTEM_NAME, 'OnScriptTickServer', self, self.McTick)
@@ -0,0 +1,325 @@
# coding=utf-8
import copy
from ..OtherTools import GetDistance
routeControls = {} # 所有路径节点
class ConnectChangedState(object):
"""
当发生连接变化时的状态
"""
DisConnect = 0b00
Connect = 0b01
ConvertConnect = 0b00
ReallyConnect = 0b10
def GetRouteByPosDim(posDim):
# type:(Union[List,Tuple]) -> RouteControl
"""
通过维度坐标获取加载的路径节点
"""
return routeControls.get(posDim)
class RouteControl(object):
"""
路径节点
"""
def __init__(self, posDim):
# type:(Tuple[int,int,int,int]) -> None
self.posDim = posDim
self.connections = [] # 真连接
self.convertConnections = [] # 隐性连接,用于记录哪个路径节点连接与此
routeControls[posDim] = self
def HasConnection(self):
"""
判断是否有方向:自身 -> routeControl的连接
"""
return self.connections.__len__() > 0
def ConnectionChanged(self, routeControl, state):
"""
当连接情况发生改变完成后触发,包括真连接和隐性连接
:param routeControl: 将要变化的路径节点
:param state: ConnectChangedState不同位的不同state
"""
pass
def Destroy(self):
"""
销毁本路径节点
:return:
"""
for routeControl in copy.copy(self.convertConnections):
# 销毁, 不可触发自身连接改变
routeControl.DisConnectBefore(self)
routeControl.connections.remove(self)
self.convertConnections.remove(routeControl)
routeControl.ConnectionChanged(self, ConnectChangedState.ReallyConnect | ConnectChangedState.DisConnect)
for routeControl in copy.copy(self.connections):
# 销毁, 不可触发自身连接改变
self.DisConnectBefore(routeControl)
self.connections.remove(routeControl)
routeControl.convertConnections.remove(self)
routeControl.ConnectionChanged(self, ConnectChangedState.ConvertConnect | ConnectChangedState.DisConnect)
del routeControls[self.posDim]
def DisconnectTo(self, routeControl):
"""
单向取消与路径节点的单向连接, 方向:自身 -> routeControl
:param routeControl:将要取消的路径节点
:return:
"""
if self.IsConnection(routeControl):
self.DisConnectBefore(routeControl)
self.connections.remove(routeControl)
routeControl.convertConnections.remove(self)
self.ConnectionChanged(routeControl, ConnectChangedState.ReallyConnect | ConnectChangedState.DisConnect)
routeControl.ConnectionChanged(self, ConnectChangedState.ConvertConnect | ConnectChangedState.DisConnect)
@staticmethod
def DisconnectBy(routeControl):
"""
单向取消与路径节点的单向连接, 方向: routeControl -> 自身
:param routeControl:将要取消的路径节点
:return:
"""
routeControl.DisconnectTo(routeControl)
def InterDisconnect(self, routeControl):
"""
双向取消与路径节点的连接
:param routeControl:将要取消的路径节点
:return:
"""
self.DisconnectTo(routeControl)
self.DisconnectBy(routeControl)
def IsConnection(self, routeControl):
"""
判断路径节点是否与本路径节点连接
:param routeControl:路径节点
:return:
"""
return routeControl in self.connections
def Connectable(self, routeControl):
"""
在尝试与对方进行连接时触发,方向:自身 -> routeControl
:param routeControl:将要连接的路径节点
:return:是否与对方连接
"""
if abs(GetDistance(self.posDim[:3], routeControl.posDim[:3]) - 1) < 10e-6:
return True
def DisConnectBefore(self, routeControl):
"""
当断开方向为:routeControl -> 自身 连接之前触发
"""
pass
def ConnectBefore(self, routeControl):
"""
当发生方向为:routeControl -> 自身 连接之前触发
"""
pass
def ConnectTo(self, routeControl):
"""
与另外一个路径节点进行单向连接,方向:自身 -> routeControl
如果routeControl不可连接与此, 将无法建立连接
"""
routeControl.ConnectBefore(self)
if self.Connectable(routeControl) and routeControl not in self.connections:
self.connections.append(routeControl)
routeControl.convertConnections.append(self)
self.ConnectionChanged(routeControl, ConnectChangedState.ReallyConnect | ConnectChangedState.Connect)
routeControl.ConnectionChanged(self, ConnectChangedState.ConvertConnect | ConnectChangedState.Connect)
def ConnectBy(self, routeControl):
"""
与另外一个路径节点进行单向连接,方向:routeControl -> 自身
"""
routeControl.ConnectTo(self)
def InterConnect(self, routeControl):
"""
与另外一个路径节点进行双向连接
"""
self.ConnectTo(routeControl)
self.ConnectBy(routeControl)
def ConnectBeside(self):
"""
与周围的可连接的路径节点进行连接
"""
myPosDim = list(self.posDim)
dr = [-1, 1]
posDimBeside = [(myPosDim[0] + x, myPosDim[1], myPosDim[2], myPosDim[3]) for x in dr] + \
[(myPosDim[0], myPosDim[1] + y, myPosDim[2], myPosDim[3]) for y in dr] + \
[(myPosDim[0], myPosDim[1], myPosDim[2] + z, myPosDim[3]) for z in dr]
for posDim in posDimBeside:
routeControl = routeControls.get(posDim)
self.InterConnect(routeControl) if routeControl else 0
def IsFinal(self, *args, **kwargs):
"""
是否为终点
:param args:判断时可能传入的参数
:param kwargs:判断时可能传入的参数
:return:
"""
return self.connections.__len__() == 1
def Finder(routeControl, maxRuntime=10e5):
# type:(RouteControl,int)->List
"""
寻找器,不可跨维度寻找
:param routeControl:起点路径节点
:param maxRuntime:最大运行次数
:return:终点集
"""
destination = [] # 目标集
x = {} # 储存已经寻找过的节点
runTime = 0 # 寻找次数
hand = routeControl.posDim # 运行指针
subRoute = [] # 下一节点
while runTime < maxRuntime:
routeControl = routeControls.get(hand) # type:RouteControl
y = x.get(hand[0], {})
z = y.get(hand[1], {})
# 如果获取错误,或者此节点已被寻找,则尝试下一个分流节点
if routeControl is None or z.get(hand[2], False):
if subRoute.__len__() == 0:
break
hand = subRoute.pop()
continue
# 储存节点
z[hand[2]] = routeControl
y[hand[1]] = z
x[hand[0]] = y
if routeControl.IsFinal():
destination.append(routeControl)
for connection in routeControl.connections:
posDim = connection.posDim
# 判断下一节点是否被储存过
if not x.get(posDim[0], {}).get(posDim[1], {}).get(posDim[2], False):
subRoute.append(connection.posDim)
if subRoute.__len__() == 0:
break
hand = subRoute.pop()
runTime += 1
return destination
def FinderStopWhenFindDestination(routeControl, maxRuntime=10e5):
# type:(RouteControl,int)->List
"""
寻找器,不可跨维度寻找,每个分路遇到终点会停止寻路
:param routeControl:起点路径节点
:param maxRuntime:最大运行次数
:return:终点集
"""
destination = []
x = {}
runTime = 0
hand = routeControl.posDim
subRoute = []
while runTime < maxRuntime:
routeControl = routeControls.get(hand) # type:RouteControl
y = x.get(hand[0], {})
z = y.get(hand[1], {})
if routeControl is None or z.get(hand[2], False):
if subRoute.__len__() == 0:
break
hand = subRoute.pop()
continue
z[hand[2]] = True
y[hand[1]] = z
x[hand[0]] = y
if routeControl.IsFinal():
destination.append(routeControl)
else:
for connection in routeControl.connections:
posDim = connection.posDim
if not x.get(posDim[0], {}).get(posDim[1], {}).get(posDim[2], False):
subRoute.append(connection.posDim)
if subRoute.__len__() == 0:
break
hand = subRoute.pop()
runTime += 1
return destination
def FinderStopSampleNum(routeControl, sampleNum, maxRuntime=10e5):
# type:(RouteControl,int,int)->Dict
"""
寻找器,不可跨维度寻找,每个分路到达sampleNumber时停止寻路
:param routeControl:起点路径节点
:param maxRuntime:最大运行次数
:param sampleNum:距离起点相隔的节点个数
:return:结果集无论是否为终点
"""
destination = {} # 目标集
x = {} # 储存已经寻找过的节点
runTime = 0 # 寻找次数
hand = routeControl.posDim # 运行指针
subRoute = [] # 下一节点
subRouteNum = {} # 下一节点计数
presentNum = 0 # 当前计数
while runTime < maxRuntime:
routeControl = routeControls.get(hand) # type:RouteControl
y = x.get(hand[0], {})
z = y.get(hand[1], {})
# 如果获取错误,或者此节点已被寻找,再或者此时计数大于sampleNum则尝试下一个分流节点
if presentNum >= sampleNum or presentNum == -1 or z.get(hand[2], False) or routeControl is None:
if subRoute.__len__() == 0:
break
hand = subRoute.pop()
continue
# 储存节点
z[hand[2]] = routeControl
y[hand[1]] = z
x[hand[0]] = y
if presentNum > 0:
destination[presentNum] = destination.get(presentNum, [])
destination[presentNum].append(routeControl)
presentNum += 1
for connection in routeControl.connections:
posDim = connection.posDim
# 判断下一节点是否被储存过
if not x.get(posDim[0], {}).get(posDim[1], {}).get(posDim[2], False):
subRoute.append(connection.posDim)
a = subRouteNum.get(posDim[0], {})
subRouteNum[posDim[0]] = a
b = a.get(posDim[1], {})
a[posDim[1]] = b
b[posDim[2]] = presentNum
if subRoute.__len__() == 0:
break
hand = subRoute.pop()
presentNum = subRouteNum.get(hand[0], {}).get(hand[1], {}).get(hand[2], False)
runTime += 1
return destination
@@ -0,0 +1,668 @@
# -*- coding: utf-8 -*-
import math
import random
import re
import time
import mod.client.extraClientApi as clientApi
from mod.common.utils.mcmath import Matrix
from config.PlayConfig import PlayConfig
from exception.NotClientSystemException import NotClientSystemException
from exception.NotPlayConfigException import NotPlayConfigException
from utils.SafeEval import SafeEval
from .utils.CatmullRom import getCatmullromLerpFromKeyframe
compFactory = clientApi.GetEngineCompFactory()
levelId = clientApi.GetLevelId()
class Camera(object):
# 全局摄像机对象(用来判断当前客户端是否有摄像机正在播放)
globalCameraObj = None
def __init__(self, client, animationName, path='modconfigs/spring_camera_animations.json', cameraName='camera',
animationDict=None, speed=1.0):
"""
构造方法
Args:
client (ClientSystem): 客户端系统对象
animationName (str): 动画在文件中的名称
path (str, optional): 动画文件路径,需要包含 ".json"。默认值 'modconfigs/spring_camera_animations.json'
cameraName (str, optional): 动画 JSON 中的摄像机名称。默认值 'camera'
animationDict (dict, optional): 动画关键帧字典。默认值 None
speed (float, optional): 动画播放速度倍数。默认值 1.0
"""
if not isinstance(client, clientApi.GetClientSystemCls()):
raise NotClientSystemException("传入 Camera 内的 client 不是一个 ClientSystem")
# region 初始化变量
self.client = client
self.eval = SafeEval()
# 动画总时长
self.animationLength = None
# 动画旋转关键帧数组
self.animationRotationArray = None
# 动画位置关键帧数组
self.animationPositionArray = None
# 动画旋转关键帧数组的索引
self.rotationIndex = None
# 动画位置关键帧数组的索引
self.positionIndex = None
# 动画开始时间(单位:秒)
self.startAnimationTime = None
# 上一个帧的时间(单位:秒)
self.preAnimationTime = None
# 上一帧的旋转关键帧参数,形如:(0.0, [0, 180, 0], 'catmullrom')
self.preAnimationRotArgs = None
# 上一帧的位置关键帧参数,形如:(0.0, [0, 180, 0], 'catmullrom')
self.preAnimationPosArgs = None
# 动画开始前的摄像机旋转参数
self.rotationOrigin = None
# 动画开始前的摄像机锚点参数
self.positionOrigin = None
# play 配置对象
self.playConfig = PlayConfig()
# 是否正在播放摄像机动画
self.isPlaying = False
# 摄像机接口对象
self.cameraCompFactory = compFactory.CreateCamera(levelId)
# 绕 y 轴旋转的旋转矩阵
self.leftRotYMat = None
# 是否覆盖当前的位置和旋转(json 配置)
self.overridePreviousAnimation = False
# 动画循环状态:播放一次、停止于最后一帧、循环播放
self.loop = None
# 防止停不住
self.playTimer = None
# 前一帧设置的摄像机旋转角度(目前仅用于将当前帧设置的旋转角度传递给后续的 GameRenderTickEvent 事件)
self.beforeRot = None
# 是否正在暂停
self.isPausing = False
# 动画暂停开始时间
self.pauseStartTime = 0.0
# endregion
# 动画关键帧字典
if not animationDict:
animationDict = clientApi.GetModConfigJson(path)['animations'][animationName]
self.animationLength = animationDict.get('animation_length')
if not self.animationLength:
# 无时间的动画,默认动画时长 24 小时
self.animationLength = 86400.0
# 应用速度倍数
speed = 1.0 / speed
self.animationLength *= speed
self.overridePreviousAnimation = animationDict['override_previous_animation'] if animationDict.__contains__(
'override_previous_animation') else False
self.loop = animationDict['loop'] if animationDict.__contains__('loop') else False
if animationDict['bones'][cameraName].__contains__('rotation'):
animationRotationDict = animationDict['bones'][cameraName]['rotation']
# 解析只有单个关键帧的动画
if isinstance(animationRotationDict, list):
animationRotationDict = {'0.0': animationRotationDict}
# 动画旋转关键帧数组
self.animationRotationArray = sorted(animationRotationDict.items(), key=lambda item: float(item[0]))
self.animationRotationArray = [
(float(item[0]) * speed,
item[1]['post'] if isinstance(item[1], dict) else item[1],
item[1].get('lerp_mode') if isinstance(item[1], dict) else None)
for item in self.animationRotationArray
]
self._parseRotKeyframe(self.animationRotationArray)
if animationDict['bones'][cameraName].__contains__('position'):
animationPositionDict = animationDict['bones'][cameraName]['position']
# 解析只有单个关键帧的动画
if isinstance(animationPositionDict, list):
animationPositionDict = {'0.0': animationPositionDict}
# 动画位置关键帧数组
self.animationPositionArray = sorted(animationPositionDict.items(), key=lambda item: float(item[0]))
self.animationPositionArray = [
(float(item[0]) * speed,
item[1]['post'] if isinstance(item[1], dict) else item[1],
item[1].get('lerp_mode') if isinstance(item[1], dict) else None)
for item in self.animationPositionArray
]
self._parsePosKeyframe(self.animationPositionArray)
def _parseRotKeyframe(self, animationArray):
"""
转换旋转关键帧中的字符串
Args:
animationArray (list): 旋转关键帧数组
"""
rot = compFactory.CreateCamera(levelId).GetCameraRotation()
self._parseKeyframe(animationArray, rot)
def _parsePosKeyframe(self, animationArray):
"""
转换位置关键帧中的字符串
Args:
animationArray (list): 位置关键帧数组
"""
pos = compFactory.CreatePos(clientApi.GetLocalPlayerId()).GetFootPos()
self._parseKeyframe(animationArray, (pos[0] * 16.0, pos[1] * 16.0, pos[2] * 16.0))
def _parseKeyframe(self, animationArray, replaceThis):
"""
转换关键帧中的字符串
Args:
animationArray (list): 关键帧数组
replaceThis (tuple): 替换 this 的元组
"""
for i in xrange(0, len(animationArray)):
for j in xrange(0, len(animationArray[i][1])):
if not isinstance(animationArray[i][1][j], str):
continue
if 'this' in animationArray[i][1][j]:
animationArray[i][1][j] = animationArray[i][1][j].replace('this', format(replaceThis[j], '.16f'))
animationArray[i][1][j] = self.eval.evaluate(animationArray[i][1][j])
elif 'math.random' in animationArray[i][1][j]:
match = re.search(r'(-?\d+(\.\d+)?),\s*(-?\d+(\.\d+)?)', animationArray[i][1][j])
if match:
animationArray[i][1][j] = random.uniform(float(match.group(1)), float(match.group(3)))
def _indexOutEnd(self):
"""
防止下标越界
"""
# 如果数组下标越界,设置下标为最后一个元素
if self.animationRotationArray and self.rotationIndex >= len(self.animationRotationArray):
self.rotationIndex = len(self.animationRotationArray) - 1
if self.animationPositionArray and self.positionIndex >= len(self.animationPositionArray):
self.positionIndex = len(self.animationPositionArray) - 1
def _leftRotationMatrixY(self, angleDegrees):
"""
生成绕 Y 轴旋转指定角度的 3x3 旋转矩阵(左手坐标系)
Args:
angleDegrees (float): 旋转角度
Returns:
Matrix: 3x3 旋转矩阵
"""
# 将角度转换为弧度
angleRad = math.radians(angleDegrees)
# 计算余弦和正弦值
cosAngle = math.cos(angleRad)
sinAngle = math.sin(angleRad)
# 构建绕 Y 轴旋转矩阵(左手坐标系)
rotationMatrix = Matrix.Create([
[cosAngle, 0, -sinAngle],
[0, 1, 0, ],
[sinAngle, 0, cosAngle]
])
return rotationMatrix
def _leftRotationMatrixX(self, angleDegrees):
"""
生成绕 X 轴旋转指定角度的 3x3 旋转矩阵(左手坐标系)
Args:
angleDegrees (float): 旋转角度
Returns:
Matrix: 3x3 旋转矩阵
"""
# 将角度转换为弧度
angleRad = math.radians(angleDegrees)
# 计算余弦和正弦值
cosAngle = math.cos(angleRad)
sinAngle = math.sin(angleRad)
# 构建绕 X 轴旋转矩阵(左手坐标系)
rotationMatrix = Matrix.Create([
[1, 0, 0],
[0, cosAngle, sinAngle],
[0, -sinAngle, cosAngle]
])
return rotationMatrix
def _getNowTime(self):
"""
获取当前时间
"""
if self.isPausing and self.preAnimationTime is not None:
return self.preAnimationTime
return time.time()
def playFirstPerson(self, playAfterFunc=None):
"""
播放第一人称动画(允许拖拽,允许移动)
Args:
playAfterFunc (function, optional): 摄像机动画播放结束后的回调函数。默认值 None
"""
self.play(True, True, True, False, True, playAfterFunc=playAfterFunc)
def playFirstPersonDisableMove(self, playAfterFunc=None):
"""
播放第一人称动画(允许拖拽,禁止移动)
Args:
playAfterFunc (function, optional): 摄像机动画播放结束后的回调函数。默认值 None
"""
self.play(True, True, False, False, True, playAfterFunc=playAfterFunc)
def playFirstPersonDisableDrag(self, playAfterFunc=None):
"""
播放第一人称动画(禁止拖拽,允许移动)
Args:
playAfterFunc (function, optional): 摄像机动画播放结束后的回调函数。默认值 None
"""
self.play(True, False, True, False, True, playAfterFunc=playAfterFunc)
def playFirstPersonDisableDragWithMove(self, playAfterFunc=None):
"""
播放第一人称动画(禁止拖拽,禁止移动)
Args:
playAfterFunc (function, optional): 摄像机动画播放结束后的回调函数。默认值 None
"""
self.play(True, False, False, False, True, playAfterFunc=playAfterFunc)
def playThirdPerson(self, playAfterFunc=None):
"""
播放第三人称动画(允许拖拽,允许移动)
Args:
playAfterFunc (function, optional): 摄像机动画播放结束后的回调函数。默认值 None
"""
self.play(False, True, True, playAfterFunc=playAfterFunc)
def playThirdPersonDisableMove(self, playAfterFunc=None):
"""
播放第三人称动画(允许拖拽,禁止移动)
Args:
playAfterFunc (function, optional): 摄像机动画播放结束后的回调函数。默认值 None
"""
self.play(False, True, False, playAfterFunc=playAfterFunc)
def playThirdPersonDisableDrag(self, playAfterFunc=None):
"""
播放第三人称动画(禁止拖拽,允许移动)
Args:
playAfterFunc (function, optional): 摄像机动画播放结束后的回调函数。默认值 None
"""
self.play(False, False, True, playAfterFunc=playAfterFunc)
def playThirdPersonDisableDragWithMove(self, playAfterFunc=None):
"""
播放第三人称动画(禁止拖拽,禁止移动)
Args:
playAfterFunc (function, optional): 摄像机动画播放结束后的回调函数。默认值 None
"""
self.play(False, False, False, playAfterFunc=playAfterFunc)
def play(self, isFirstPerson=True, isAllowDrag=True, isAllowMove=True, isPosFollowXRot=True, isPosFollowYRot=True,
isZRotForceReturn=True, isZRotStartOrigin=False,
isDelayOneFrame=False, isLockPerspective=False, tickPosFunc=None, tickRotFunc=None, playAfterFunc=None):
"""
播放摄像机动画
Args:
isFirstPerson (bool, optional): 是否是第一人称动画。如果是,那么 Y 轴旋转会减 180 度。默认值 False
isAllowDrag (bool, optional): 是否允许玩家拖拽视角。默认值 False
isAllowMove (bool, optional): 是否允许玩家移动。默认值 False
isPosFollowXRot (bool, optional): 摄像机空间坐标是否受 X 轴旋转影响(仅在第一人称下生效)。默认值 True
isPosFollowYRot (bool, optional): 摄像机空间坐标是否受 Y 轴旋转影响(仅在第一人称下生效)。默认值 True
isZRotForceReturn (bool, optional): 是否在动画播放完成后强制归零 Z 轴的旋转值。默认值 True
isZRotStartOrigin (bool, optional): 是否将动画播放前的 Z 轴旋转值作为初始的 Z 轴旋转值。置为 False 可防止玩家在受伤瞬间播放摄像机动画造成的 Z 轴旋转异常(json 配置 override_previous_animation 值为 True 时无效)。默认值 False
isDelayOneFrame (bool, optional): 是否延迟一帧再播放摄像机动画。默认值 True
isLockPerspective (bool, optional): 是否锁定并强制切换玩家视角。默认值 True
tickPosFunc (function, optional): 摄像机动画播放时,在每次设置摄像机位置前执行的函数。默认值 None
tickRotFunc (function, optional): 摄像机动画播放时,在每次设置摄像机旋转前执行的函数。默认值 None
playAfterFunc (function, optional): 摄像机动画播放结束后的回调函数。默认值 None
"""
self.playWithConfig(
PlayConfig(isFirstPerson, isAllowDrag, isAllowMove, isPosFollowXRot, isPosFollowYRot, isZRotForceReturn,
isZRotStartOrigin, isDelayOneFrame, isLockPerspective,
tickPosFunc, tickRotFunc, playAfterFunc))
def playWithConfig(self, config):
"""
使用播放配置对象播放摄像机动画
Args:
config (PlayConfig): 播放配置对象
"""
if not isinstance(config, PlayConfig):
raise NotPlayConfigException("传入 playWithConfig 内的 config 不是一个 PlayConfig")
if Camera.globalCameraObj:
# 强制停止正在播放的摄像机动画(全局)
Camera.globalCameraObj.stop(True)
# 开始监听
from .CameraListen import _enable
if not _enable:
self.client.ListenForEvent(clientApi.GetEngineNamespace(), clientApi.GetEngineSystemName(),
"GameRenderTickEvent", self, self.GameRenderTickEvent, 10)
self.client.ListenForEvent(clientApi.GetEngineNamespace(), clientApi.GetEngineSystemName(),
"HealthChangeClientEvent", self, self.HealthChangeClientEvent)
def realPlay():
# 初始化相关变量
self.playConfig = config
self.rotationIndex = 0
self.positionIndex = 0
operationComp = compFactory.CreateOperation(levelId)
# 不允许玩家移动
if not self.playConfig.isAllowMove:
operationComp.SetCanMove(False)
operationComp.SetCanJump(False)
operationComp.SetCanInair(False)
# 不允许玩家拖拽
if not self.playConfig.isAllowDrag:
operationComp.SetCanDrag(False)
# 不允许玩家切换视角
if self.playConfig.isLockPerspective:
operationComp.SetCanPerspective(False)
# 第三人称
if not self.playConfig.isFirstPerson:
if self.playConfig.isLockPerspective:
compFactory.CreatePlayerView(clientApi.GetLocalPlayerId()).SetPerspective(1)
self.cameraCompFactory.SetCameraDistanceFixed(True)
self.cameraCompFactory.DepartCamera()
self.cameraCompFactory.SetCameraOffset((0, 0.01, -4))
else:
if self.playConfig.isLockPerspective:
compFactory.CreatePlayerView(clientApi.GetLocalPlayerId()).SetPerspective(0)
if self.animationRotationArray:
self.preAnimationRotArgs = (float(self.animationRotationArray[self.rotationIndex][0]),
self.animationRotationArray[self.rotationIndex][1])
if self.animationPositionArray:
self.preAnimationPosArgs = (float(self.animationPositionArray[self.positionIndex][0]),
self.animationPositionArray[self.positionIndex][1])
self.preAnimationTime = self.startAnimationTime = time.time()
# 是否覆盖
if self.overridePreviousAnimation:
self.rotationOrigin = (0, 0, 0)
self.positionOrigin = (0, 0, 0)
else:
self.rotationOrigin = self.cameraCompFactory.GetCameraRotation()
self.positionOrigin = self.cameraCompFactory.GetCameraAnchor()
# 可防止玩家在受伤瞬间播放摄像机动画造成的 Z 轴旋转异常
if not self.playConfig.isZRotStartOrigin:
self.rotationOrigin = (self.rotationOrigin[0], self.rotationOrigin[1], 0.0)
# 绕 y 轴的旋转矩阵
self.leftRotYMat = self._leftRotationMatrixY(self.rotationOrigin[1])
# 一定要在最后给 isPlaying 赋值
Camera.globalCameraObj = self
self.isPlaying = True
if config.isDelayOneFrame:
# 延时一帧再播放(为了确保上一个动画完全停止,使得下一次获取初始数据时不受上次动画影响)
compGame = compFactory.CreateGame(levelId)
self.playTimer = compGame.AddTimer(0.0, realPlay)
else:
realPlay()
def destroyListen(self):
"""
销毁内置监听
"""
self.client.UnListenForEvent(clientApi.GetEngineNamespace(), clientApi.GetEngineSystemName(),
"GameRenderTickEvent", self, self.GameRenderTickEvent)
self.client.UnListenForEvent(clientApi.GetEngineNamespace(), clientApi.GetEngineSystemName(),
"HealthChangeClientEvent", self, self.HealthChangeClientEvent)
def stop(self, isStopLoop=False):
"""
停止摄像机动画
Args:
isStopLoop (bool, optional): 是否停止循环动画(仅对 json 中 loop 配置为 True 的动画有效)。默认值 False
"""
if self.playTimer: compFactory.CreateGame(levelId).CancelTimer(self.playTimer)
# 取消监听
from .CameraListen import _enable
if not _enable:
self.destroyListen()
self.isPlaying = False
self.isPausing = False
Camera.globalCameraObj = None
# region 摄像机还原
# 第三人称
if not self.playConfig.isFirstPerson:
if self.playConfig.isLockPerspective:
compFactory.CreatePlayerView(clientApi.GetLocalPlayerId()).SetPerspective(0)
self.cameraCompFactory.SetCameraDistanceFixed(False)
self.cameraCompFactory.UnDepartCamera()
self.cameraCompFactory.SetCameraOffset((0, 0, 0))
# 根据 loop 的值进行设置
nowCameraRotation = self.rotationOrigin
nowCameraPosition = self.positionOrigin
if self.loop == 'hold_on_last_frame' or self.loop == True or self.playConfig.isAllowDrag:
nowCameraRotation = self.cameraCompFactory.GetCameraRotation()
if not self.playConfig.isAllowDrag: nowCameraPosition = self.cameraCompFactory.GetCameraAnchor()
# 是否强制回正 Z 轴
if self.playConfig.isZRotForceReturn:
self.cameraCompFactory.SetCameraRotation((nowCameraRotation[0], nowCameraRotation[1], 0))
else:
self.cameraCompFactory.SetCameraRotation(nowCameraRotation)
self.cameraCompFactory.SetCameraAnchor(nowCameraPosition)
# endregion
# 判断是否循环播放
if self.loop == True and not isStopLoop:
self.playWithConfig(self.playConfig)
return
operationComp = compFactory.CreateOperation(levelId)
# 恢复玩家移动
operationComp.SetCanMove(True)
operationComp.SetCanJump(True)
operationComp.SetCanInair(True)
operationComp.SetCanDrag(True)
# 允许玩家切换视角
operationComp.SetCanPerspective(True)
# 动画停止播放后执行传入的回调函数
if (self.loop == True and isStopLoop) or (self.loop != True):
if callable(self.playConfig.playAfterFunc):
self.playConfig.playAfterFunc()
def pause(self, resumeTime=0):
# type: (float) -> None
"""
暂停摄像机动画
"""
if self.isPausing:
return
self.isPausing = True
self.pauseStartTime = time.time()
# 多少秒后恢复动画播放
if resumeTime > 0:
compFactory.CreateGame(levelId).AddTimer(resumeTime, self.resume)
def resume(self):
"""
继续播放摄像机动画
"""
if not self.isPausing:
return
self.isPausing = False
pauseTime = time.time() - self.pauseStartTime
self.startAnimationTime += pauseTime
self.preAnimationTime += pauseTime
def GameRenderTickEvent(self, event):
"""
每 Tick 触发
"""
if not self.isPlaying:
return
nowAnimationTime = self._getNowTime()
startToNowTime = nowAnimationTime - self.startAnimationTime # 当前时间距离动画开始的时间
preToNowTime = nowAnimationTime - self.preAnimationTime # 当前时间距离上一帧的时间
self.preAnimationTime = nowAnimationTime # 更新上一帧的时间为当前时间,为下一次计算做准备
# 如果自动画开始以来经过的时间,大于动画总时长,则动画结束
if startToNowTime > self.animationLength:
self.stop()
return
self._indexOutEnd()
# 如果自动画开始以来经过的时间,大于当前关键帧时间,说明关键帧索引应该增加
while self.animationRotationArray and self.rotationIndex < len(
self.animationRotationArray) and startToNowTime > float(
self.animationRotationArray[self.rotationIndex][0]):
# 1.0.3 修复上个关键帧没执行到结尾就计算下个关键帧所带来的问题
self.preAnimationRotArgs = (self.preAnimationRotArgs[0], self.animationRotationArray[self.rotationIndex][1])
self.rotationIndex += 1
while self.animationPositionArray and self.positionIndex < len(
self.animationPositionArray) and startToNowTime > float(
self.animationPositionArray[self.positionIndex][0]):
# 1.0.3 修复上个关键帧没执行到结尾就计算下个关键帧所带来的问题
self.preAnimationPosArgs = (self.preAnimationPosArgs[0], self.animationPositionArray[self.positionIndex][1])
self.positionIndex += 1
self._indexOutEnd()
# 计算位置
if self.animationPositionArray:
keyframePosTime = float(self.animationPositionArray[self.positionIndex][0]) - self.preAnimationPosArgs[
0] # 位置关键帧下个关键帧与上个关键帧之间的时间
# 只有时间间隔大于 0,才计算当前这一刻的值
if keyframePosTime > 0.0:
positionX = 0
positionY = 0
positionZ = 0
if self.animationPositionArray[self.positionIndex][2] == 'catmullrom':
positionX = getCatmullromLerpFromKeyframe(self.animationPositionArray, self.positionIndex,
startToNowTime, 0)
positionY = getCatmullromLerpFromKeyframe(self.animationPositionArray, self.positionIndex,
startToNowTime, 1)
positionZ = getCatmullromLerpFromKeyframe(self.animationPositionArray, self.positionIndex,
startToNowTime, 2)
else:
positionX = ((float(self.animationPositionArray[self.positionIndex][1][0]) -
self.preAnimationPosArgs[1][0]) / keyframePosTime * preToNowTime) + \
self.preAnimationPosArgs[1][0]
positionY = ((float(self.animationPositionArray[self.positionIndex][1][1]) -
self.preAnimationPosArgs[1][1]) / keyframePosTime * preToNowTime) + \
self.preAnimationPosArgs[1][1]
positionZ = ((float(self.animationPositionArray[self.positionIndex][1][2]) -
self.preAnimationPosArgs[1][2]) / keyframePosTime * preToNowTime) + \
self.preAnimationPosArgs[1][2]
pos = (
(1 if self.playConfig.isFirstPerson else -1) * positionX / 16.0,
positionY / 16.0 - 1.62,
(-1 if self.playConfig.isFirstPerson else 1) * positionZ / 16.0
)
posMat = Matrix.Create([
[pos[0]],
[pos[1]],
[pos[2]]
])
if self.playConfig.isFirstPerson:
# 第一人称
rotMax = Matrix.CreateEye(3)
nowCameraRotation = self.cameraCompFactory.GetCameraRotation()
deltX = (nowCameraRotation[0] - self.preAnimationRotArgs[1][0])
deltY = (nowCameraRotation[1] - self.preAnimationRotArgs[1][1])
# 注意矩阵相乘顺序
if self.playConfig.isPosFollowXRot:
rotMax = self._leftRotationMatrixX(deltX)
if self.playConfig.isPosFollowYRot:
rotMax = self._leftRotationMatrixY(deltY - 180) * rotMax
posMat = rotMax * posMat
else:
# 第三人称位置只受初始绕 y 轴旋转的影响
posMat = self.leftRotYMat * posMat
pos = (
posMat[0, 0] + self.positionOrigin[0],
posMat[1, 0] + self.positionOrigin[1],
posMat[2, 0] + self.positionOrigin[2]
)
# 是否不设置
isNotSet = False
if callable(self.playConfig.tickPosFunc):
isNotSet = self.playConfig.tickPosFunc(startToNowTime, pos)
if not isNotSet:
self.cameraCompFactory.SetCameraAnchor(pos)
self.preAnimationPosArgs = (startToNowTime, [positionX, positionY, positionZ])
# 计算旋转
if self.animationRotationArray:
keyframeRotTime = float(self.animationRotationArray[self.rotationIndex][0]) - self.preAnimationRotArgs[
0] # 旋转关键帧下个关键帧与上个关键帧之间的时间
# 只有时间间隔大于 0,才计算当前这一刻的值
if keyframeRotTime > 0.0:
rotationX = 0
rotationY = 0
rotationZ = 0
if self.animationRotationArray[self.rotationIndex][2] == 'catmullrom':
rotationX = getCatmullromLerpFromKeyframe(self.animationRotationArray, self.rotationIndex,
startToNowTime, 0)
rotationY = getCatmullromLerpFromKeyframe(self.animationRotationArray, self.rotationIndex,
startToNowTime, 1)
rotationZ = getCatmullromLerpFromKeyframe(self.animationRotationArray, self.rotationIndex,
startToNowTime, 2)
else:
rotationX = ((float(self.animationRotationArray[self.rotationIndex][1][0]) -
self.preAnimationRotArgs[1][0]) / keyframeRotTime * preToNowTime) + \
self.preAnimationRotArgs[1][0]
rotationY = ((float(self.animationRotationArray[self.rotationIndex][1][1]) -
self.preAnimationRotArgs[1][1]) / keyframeRotTime * preToNowTime) + \
self.preAnimationRotArgs[1][1]
rotationZ = ((float(self.animationRotationArray[self.rotationIndex][1][2]) -
self.preAnimationRotArgs[1][2]) / keyframeRotTime * preToNowTime) + \
self.preAnimationRotArgs[1][2]
deltX = self.rotationOrigin[0] if self.playConfig.isFirstPerson else 0
deltY = self.rotationOrigin[1] - (180 if self.playConfig.isFirstPerson else 0)
if self.playConfig.isAllowDrag:
nowCameraRotation = self.cameraCompFactory.GetCameraRotation()
deltX = (nowCameraRotation[0] - self.preAnimationRotArgs[1][0])
deltY = (nowCameraRotation[1] - self.preAnimationRotArgs[1][1])
deltX += rotationX
deltY += rotationY
rot = (
deltX % (-360 if deltX < 0 else 360),
deltY % (-360 if deltY < 0 else 360),
# 军械库版本,因为摄像机动画直接使用 Java 版动画,所以 y 轴没有旋转 180 度,因此 z 轴旋转从 - 改为 +,以匹配 Blockbench 里的效果
self.rotationOrigin[2] + rotationZ
)
# 是否不设置
isNotSet = False
if callable(self.playConfig.tickRotFunc):
isNotSet = self.playConfig.tickRotFunc(startToNowTime, rot)
if not isNotSet:
self.cameraCompFactory.SetCameraRotation(rot)
self.beforeRot = rot
self.preAnimationRotArgs = (
startToNowTime, [rotationX, rotationY, rotationZ]) # 更新上一帧的参数为当前参数,为下一次计算做准备
def HealthChangeClientEvent(self, event):
"""
生物生命值发生变化时触发
"""
# 自己死亡时,停止当前摄像机动画
playerType = clientApi.GetMinecraftEnum().EntityType.Player
if event['entityId'] == clientApi.GetLocalPlayerId() and compFactory.CreateEngineType(
event['entityId']).GetEngineType() & playerType == playerType and event['to'] <= 0.0:
self.stop(True)
@@ -0,0 +1,101 @@
# -*- coding: utf-8 -*-
import time
import mod.client.extraClientApi as clientApi
from .Camera import Camera, compFactory
# noinspection PyMethodMayBeStatic
class CameraListen(object):
"""
摄像机监听类,创建此类的对象,以手动控制摄像机的监听与取消监听
"""
cameraListenObj = None # type: CameraListen
def __init__(self):
pass
def GameRenderTickEvent(self, event):
if Camera.globalCameraObj:
Camera.globalCameraObj.GameRenderTickEvent(event)
def HealthChangeClientEvent(self, event):
if Camera.globalCameraObj:
Camera.globalCameraObj.HealthChangeClientEvent(event)
# 单个实例
CameraListen.cameraListenObj = CameraListen()
# 取消监听定时器
_timer = None
# 是否开启了内置监听
_enable = False
def startListen(client):
"""
开始监听
"""
global _enable
cameraListenObj = CameraListen.cameraListenObj
# 销毁自身已有监听
stopListen(client)
# 销毁摄像机对象内置监听
if Camera.globalCameraObj:
Camera.globalCameraObj.destroyListen()
_enable = True
client.ListenForEvent(clientApi.GetEngineNamespace(), clientApi.GetEngineSystemName(), "GameRenderTickEvent",
cameraListenObj, cameraListenObj.GameRenderTickEvent, 10)
client.ListenForEvent(clientApi.GetEngineNamespace(), clientApi.GetEngineSystemName(), "HealthChangeClientEvent",
cameraListenObj, cameraListenObj.HealthChangeClientEvent)
def stopListen(client, delayTime=0):
"""
取消监听
"""
global _timer
cameraListenObj = CameraListen.cameraListenObj
comp = compFactory.CreateGame(clientApi.GetLevelId())
def _scheduleCancel():
global _timer, _enable
# 如果取消监听时,发现还存在未停止的摄像机动画,不取消监听,再次创建一个定时器
if Camera.globalCameraObj and delayTime > 0:
if _timer:
_timer = comp.CancelTimer(_timer)
_timer = comp.AddTimer(delayTime, _scheduleCancel)
return
_enable = False
client.UnListenForEvent(clientApi.GetEngineNamespace(), clientApi.GetEngineSystemName(), "GameRenderTickEvent",
cameraListenObj, cameraListenObj.GameRenderTickEvent)
client.UnListenForEvent(clientApi.GetEngineNamespace(), clientApi.GetEngineSystemName(),
"HealthChangeClientEvent", cameraListenObj,
cameraListenObj.HealthChangeClientEvent)
if delayTime > 0:
_timer = comp.AddTimer(delayTime, _scheduleCancel)
else:
if _timer:
_timer = comp.CancelTimer(_timer)
_scheduleCancel()
def stopListenKeepLastAnim(client):
"""
取消监听,并且自动保留取消监听前最后一个摄像机动画的完整播放
"""
if Camera.globalCameraObj and Camera.globalCameraObj.animationLength is not None and Camera.globalCameraObj.startAnimationTime is not None:
t = Camera.globalCameraObj.animationLength - (time.time() - Camera.globalCameraObj.startAnimationTime)
stopListen(client, t)
else:
stopListen(client)
@@ -0,0 +1,146 @@
# -*- coding: utf-8 -*-
import math
import time
import mod.client.extraClientApi as clientApi
from .Camera import Camera, levelId, compFactory
from .exception.NotClientSystemException import NotClientSystemException
from .utils.CatmullRom import getCatmullromLerpFromKeyframe
class Interpolation(object):
# 全局对象
globalObj = None
def __init__(self, client, keyframes, isReturn=False):
if not isinstance(client, clientApi.GetClientSystemCls()):
raise NotClientSystemException("传入 Interpolation 内的 client 不是一个 ClientSystem")
self.client = client
# 开始时间
self.startTime = time.time()
# 更新时间(再次调用 setInterpolation 时的时间)
self.updateTime = self.startTime
# 关键帧数组
self.keyframes = keyframes
# 关键帧数组索引
self.index = 0
# 前一帧索引,用于关键帧循环
self.preIndex = 0
# 前一帧计算出的旋转插值
self.preRot = (0, 0)
# 用于回正插值的缓存
self.tempRot = [0, 0, 0]
# 当前是否是回正插值
self.isReturn = isReturn
# 前一帧的摄像机角度
self.beforeRot = None
# 是否取消回正插值
self.cancelReturn = False
# 摄像机接口对象
self.cameraComponentFactory = compFactory.CreateCamera(levelId)
self.client.ListenForEvent(clientApi.GetEngineNamespace(), clientApi.GetEngineSystemName(),
"GameRenderTickEvent", self, self.onGameRenderTick, 0)
def onGameRenderTick(self, event):
"""
每 Tick 触发
"""
nowTime = time.time()
# 回正超时后自动停止
if self.isReturn and nowTime - self.startTime > self.keyframes[-1][0]:
self.stop()
# 当发现关键帧循环时,清空特定变量
if self.index < self.preIndex:
self.preRot = (0, 0)
startToNowTime = min(
nowTime - self.updateTime + self.keyframes[min(max(self.index - 1, 0), len(self.keyframes) - 1)][0],
self.keyframes[self.index][0]
)
rotX = getCatmullromLerpFromKeyframe(self.keyframes, self.index, startToNowTime, 0)
rotY = getCatmullromLerpFromKeyframe(self.keyframes, self.index, startToNowTime, 1)
nowRot = self.cameraComponentFactory.GetCameraRotation()
# 比较摄像机前后帧是否有较大的偏差,如果有,认为是玩家拖动屏幕造成的,取消回正
if self.beforeRot:
tolerance = 0.3
# y 轴角度是 -180 到 180 的循环,所以在计算角度差时要特殊处理
y = abs(nowRot[1] - self.beforeRot[1])
if abs(nowRot[0] - self.beforeRot[0]) > tolerance or min(y, 360 - y) > tolerance:
self.cancelReturn = True
# 混合之前的事件中设置的摄像机旋转
beforeEventRot = (0, 0, 0)
if Camera.globalCameraObj and Camera.globalCameraObj.beforeRot:
beforeEventRot = Camera.globalCameraObj.beforeRot
tempRot = (
rotX - self.preRot[0],
rotY - self.preRot[1],
0
)
deltX = nowRot[0] + tempRot[0]
deltY = nowRot[1] + tempRot[1]
rot = (
deltX % (-360 if deltX < 0 else 360),
deltY % (-360 if deltY < 0 else 360),
tempRot[2] + beforeEventRot[2]
)
self.cameraComponentFactory.SetCameraRotation(rot)
self.beforeRot = rot
self.preRot = (rotX, rotY)
self.preIndex = self.index
self.tempRot[0] -= tempRot[0]
self.tempRot[1] -= tempRot[1]
self.tempRot[2] -= tempRot[2]
def stop(self):
self.client.UnListenForEvent(clientApi.GetEngineNamespace(), clientApi.GetEngineSystemName(),
"GameRenderTickEvent", self, self.onGameRenderTick)
Interpolation.globalObj = None
if not self.isReturn and not self.cancelReturn:
# 启动回正
endTime = math.sqrt(
self.tempRot[0] * self.tempRot[0] + self.tempRot[1] * self.tempRot[1] + self.tempRot[2] * self.tempRot[
2])
endTime *= 0.3
keyframes = [
(0.0, [0, 0, 0]),
(endTime, self.tempRot)
]
setInterpolation(self.client, keyframes, 1, isReturn=True)
def setInterpolation(client, keyframes, index, speed=10.0, isReturn=False):
"""
设置插值
Args:
client (ClientSystem): 客户端对象
keyframes (list): 关键帧数组,格式:[(0.0, [1.0, 2.5, 3.3]),(1.0, [-3.0, 2.5, 3.3])]
index (int): 关键帧数组索引
speed (float, optional): 速度。默认值 10.0
isReturn (bool, optional): 是否是回正插值。默认值 False
"""
if not Interpolation.globalObj:
keyframes = [(t / speed, i) for t, i in keyframes]
Interpolation.globalObj = Interpolation(client, keyframes, isReturn)
elif Interpolation.globalObj.isReturn:
# 正在回正中,强制停止
Interpolation.globalObj.stop()
keyframes = [(t / speed, i) for t, i in keyframes]
Interpolation.globalObj = Interpolation(client, keyframes, isReturn)
Interpolation.globalObj.updateTime = time.time()
Interpolation.globalObj.index = index
def stopInterpolation():
"""
停止插值
"""
if Interpolation.globalObj:
Interpolation.globalObj.stop()
@@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
class PlayConfig(object):
def __init__(self, isFirstPerson=False, isAllowDrag=False, isAllowMove=False, isPosFollowXRot=True,
isPosFollowYRot=True, isZRotForceReturn=True, isZRotStartOrigin=False, isDelayOneFrame=True,
isLockPerspective=True, tickPosFunc=None, tickRotFunc=None, playAfterFunc=None):
"""
play 方法的参数对象
Args:
isFirstPerson (bool, optional): 是否是第一人称动画。如果是,那么 Y 轴旋转会减 180 度。默认值 False
isAllowDrag (bool, optional): 是否允许玩家拖拽视角。默认值 False
isAllowMove (bool, optional): 是否允许玩家移动。默认值 False
isPosFollowXRot (bool, optional): 摄像机空间位置坐标是否受 X 轴旋转影响(仅在第一人称下生效)。默认值 True
isPosFollowYRot (bool, optional): 摄像机空间位置坐标是否受 Y 轴旋转影响(仅在第一人称下生效)。默认值 True
isZRotForceReturn (bool, optional): 是否在动画播放完成后强制归零 Z 轴的旋转值。默认值 True
isZRotStartOrigin (bool, optional): 是否将动画播放前的 Z 轴旋转值作为初始的 Z 轴旋转值。置为 False 可防止玩家在受伤瞬间播放摄像机动画造成的 Z 轴旋转异常(json 配置 override_previous_animation 值为 True 时无效)。默认值 False
isDelayOneFrame (bool, optional): 是否延迟一帧再播放摄像机动画。默认值 True
isLockPerspective (bool, optional): 是否锁定并强制切换玩家视角。默认值 True
tickPosFunc (function, optional): 摄像机动画播放时,在每次设置摄像机位置前执行的函数。默认值 None
tickRotFunc (function, optional): 摄像机动画播放时,在每次设置摄像机旋转前执行的函数。默认值 None
playAfterFunc (function, optional): 摄像机动画播放结束后的回调函数。默认值 None
"""
self.isFirstPerson = isFirstPerson
self.isAllowDrag = isAllowDrag
self.isAllowMove = isAllowMove
self.isPosFollowXRot = isPosFollowXRot
self.isPosFollowYRot = isPosFollowYRot
self.isZRotForceReturn = isZRotForceReturn
self.isZRotStartOrigin = isZRotStartOrigin
self.isDelayOneFrame = isDelayOneFrame
self.isLockPerspective = isLockPerspective
self.tickPosFunc = tickPosFunc
self.tickRotFunc = tickRotFunc
self.playAfterFunc = playAfterFunc
@@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
class NotClientSystemException(Exception):
"""
不是客户端系统异常
"""
def __init__(self, errorMessage):
super(NotClientSystemException, self).__init__(errorMessage)
@@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
class NotPlayConfigException(Exception):
"""
不是播放配置对象异常
"""
def __init__(self, errorMessage):
super(NotPlayConfigException, self).__init__(errorMessage)
@@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
class SafeEvalException(Exception):
"""
安全计算异常
"""
def __init__(self, errorMessage):
super(SafeEvalException, self).__init__(errorMessage)
@@ -0,0 +1,110 @@
# -*- coding: utf-8 -*-
class SplineCurve:
"""
Catmull-Rom 样条曲线类
"""
def __init__(self, points):
"""
初始化样条曲线
points: 值列表
"""
self.points = points
def getPoint(self, t):
"""根据参数 t 获取曲线上的点"""
if len(self.points) < 2:
return 0
# 处理边界情况
if t <= 0:
return self.points[0]
if t >= 1:
return self.points[-1]
# 找到包含 t 的线段
numSegments = len(self.points) - 1
segmentIndex = int(t * numSegments)
segmentIndex = max(0, min(segmentIndex, numSegments - 1))
# 计算线段内的局部参数
localT = t * numSegments - segmentIndex
# 获取四个控制点用于 Catmull-Rom 插值
p0Index = max(0, segmentIndex - 1)
p1Index = segmentIndex
p2Index = segmentIndex + 1
p3Index = min(len(self.points) - 1, segmentIndex + 2)
p0 = self.points[p0Index]
p1 = self.points[p1Index]
p2 = self.points[p2Index]
p3 = self.points[p3Index]
# Catmull-Rom 插值公式
t2 = localT * localT
t3 = t2 * localT
# 计算插值点的 y 坐标
y = 0.5 * (
(2 * p1) +
(-p0 + p2) * localT +
(2 * p0 - 5 * p1 + 4 * p2 - p3) * t2 +
(-p0 + 3 * p1 - 3 * p2 + p3) * t3
)
return y
def getCatmullromLerp(beforePlus, before, after, afterPlus, axis, alpha):
"""
Catmull-Rom 插值函数
Args:
beforePlus: 前一个关键帧的前一个关键帧
before: 前一个关键帧
after: 后一个关键帧
afterPlus: 后一个关键帧的后一个关键帧
axis: 坐标轴 (x: 0, y: 1, z: 2)
alpha: 插值参数 (0-1)
Returns:
插值结果
"""
vectors = []
# 构建控制点向量
if beforePlus:
vectors.append(beforePlus[1][axis])
if before:
vectors.append(before[1][axis])
if after:
vectors.append(after[1][axis])
if afterPlus:
vectors.append(afterPlus[1][axis])
# 创建样条曲线
curve = SplineCurve(vectors)
# 计算时间参数
timeParam = (alpha + (1 if beforePlus else 0)) / (len(vectors) - 1) if len(vectors) > 1 else 0
# 获取插值点并返回 y 坐标
point = curve.getPoint(timeParam)
return point
def getCatmullromLerpFromKeyframe(keyframeArray, index, nowTime, axis):
"""
通过关键帧数组获取当前时间点的平滑插值
"""
if not keyframeArray:
return 0
endIndex = len(keyframeArray) - 1
kf0 = keyframeArray[max(index - 2, 0)]
kf1 = keyframeArray[max(index - 1, 0)]
kf2 = keyframeArray[min(index, endIndex)]
kf3 = keyframeArray[min(index + 1, endIndex)]
den = (kf2[0] - kf1[0])
alpha = (nowTime - kf1[0]) / den if den != 0 else 0
return getCatmullromLerp(kf0, kf1, kf2, kf3, axis, alpha)
@@ -0,0 +1,96 @@
# -*- coding: utf-8 -*-
from ..exception.SafeEvalException import SafeEvalException
class SafeEval:
def __init__(self):
self.operators = {
'+': lambda x, y: x + y,
'-': lambda x, y: x - y,
'*': lambda x, y: x * y,
'/': lambda x, y: x / y if y != 0 else self.raiseException("发生了除以 0 错误")
}
def tokenize(self, expression):
"""将表达式分割成数字和运算符"""
tokens = []
number = ''
negative = False # 用于标记负号
for char in expression:
if char.isspace():
continue
elif char.isdigit() or char == '.':
number += char
elif char == '-':
if not number: # 如果前面没有数字,则表示负号
negative = True
else:
if number:
tokens.append(float(number) if not negative else -float(number))
number = ''
negative = False
tokens.append(char)
else:
if number:
tokens.append(float(number) if not negative else -float(number))
number = ''
negative = False
tokens.append(char)
if number:
tokens.append(float(number) if not negative else -float(number))
return tokens
def evaluate(self, expression):
"""计算表达式的值"""
tokens = self.tokenize(expression)
return self.parse_expression(tokens)
def parse_expression(self, tokens):
"""解析表达式"""
return self.parse_additive(tokens)
def parse_additive(self, tokens):
"""解析加法和减法"""
result = self.parse_multiplicative(tokens)
while tokens and tokens[0] in ('+', '-'):
op = tokens.pop(0)
if op == '+':
right = self.parse_multiplicative(tokens)
result = self.operators[op](result, right)
elif op == '-':
right = self.parse_multiplicative(tokens)
result = self.operators[op](result, right)
return result
def parse_multiplicative(self, tokens):
"""解析乘法和除法"""
result = self.parse_primary(tokens)
while tokens and tokens[0] in ('*', '/'):
op = tokens.pop(0)
if op == '*':
right = self.parse_primary(tokens)
result = self.operators[op](result, right)
elif op == '/':
right = self.parse_primary(tokens)
result = self.operators[op](result, right)
return result
def parse_primary(self, tokens):
"""解析基本表达式"""
if not tokens:
return None
token = tokens.pop(0)
if isinstance(token, float):
return token
elif token == '(':
result = self.parse_expression(tokens)
if not tokens or tokens[0] != ')':
return self.raiseException("括号不匹配")
tokens.pop(0) # Pop the closing parenthesis
return result
else:
return self.raiseException("无效的语法")
def raiseException(self, message):
"""抛出异常"""
SafeEvalException(message)
@@ -0,0 +1,68 @@
# coding=utf-8
from ..ToolsConfig import *
class ResultTimer(object):
"""
结果定时器, 在返回True时结束循环
"""
def __init__(self, func, delay=0.0, maxTimes=10, *args, **kwargs):
"""
:param func: 执行函数
:param delay: 执行延迟
:param maxTimes: 最大执行次数
"""
self.func = func
self.delay = delay
self.args = args
self.kwargs = kwargs
self.maxTimes = maxTimes
self.runTimes = 0
self.gameComp = serverCompFactory.CreateGame(LevelId) or clientCompFactory.CreateGame(LevelId)
def __timerFunc__(self):
result = self.func(*self.args, **self.kwargs)
if not result and self.runTimes < self.maxTimes: self.Start()
def Start(self):
"""
开始执行定时器
:return:
"""
self.gameComp.AddTimer(self.delay, self.__timerFunc__)
self.runTimes += 1
class TimesTimer(object):
"""
次数定时器
"""
def __init__(self, times, func, delay=0.0, *args, **kwargs):
"""
:param times: 循环次数
:param func: 执行函数
:param delay: 执行延迟
"""
self.func = func
self.delay = delay
self.times = times
self.args = args
self.kwargs = kwargs
self.gameComp = serverCompFactory.CreateGame(LevelId) or clientCompFactory.CreateGame(LevelId)
def __func(self, *args, **kwargs):
self.times -= 1
self.func(*args, **kwargs)
if self.times >= 0:
self.gameComp.AddTimer(self.delay, self.__func, *self.args, **self.kwargs)
def Start(self):
"""
开始执行定时器
:return:
"""
if self.times > 0:
self.times -= 1
self.gameComp.AddTimer(self.delay, self.__func, *self.args, **self.kwargs)
@@ -0,0 +1,155 @@
# coding=utf-8
from ToolsConfig import *
CreatedTools = {}
def RegisterSystemFastly(api, cls):
"""
快速注册系统
:param api:serverApi or clientApi
:param cls:系统类
:return:
"""
api.RegisterSystem(
MOD_NAME, cls.__name__,
'{}.{}'.format(cls.__module__, cls.__name__)
)
class EventFilter(object):
def __init__(self, func, systemInstance, *args):
self.fiterArgs = args
self.systemFuncName = func.__name__
self.systemInstance = systemInstance
def Filter(self, *args):
key = True
for _ in self.fiterArgs:
key = key and _(*args)
if not key: break
return getattr(self.systemInstance, self.systemFuncName)(*args) if key else None
class SystemTools(object):
"""
系统工具箱,以不同类进行分辨
"""
Systems = {}
def __init__(self):
self.bindingInstance = None
self.systemListenEvent = set()
self.filterEvent = set()
self.filter = list()
self.lastListened = set()
@classmethod
def GetTools(cls, tag):
"""
创建系统工具
:param tag:工具标签
:return:SystemTools
"""
tag = tag + MOD_NAME + MOD_VERSION
instance = CreatedTools.get(tag)
if instance is None:
instance = cls()
CreatedTools[tag] = instance
else:
CreatedTools[tag].Relisten()
return instance
def GetBlueFoxServerListener(self):
"""
获取BlueFoxServer系统的监听器
"""
return self.ListenForEvent(namespace=MOD_NAME, systemName='BlueFoxServerSystem')
def GetBlueFoxClientListener(self):
"""
获取BlueFoxClient系统的监听器
"""
return self.ListenForEvent(namespace=MOD_NAME, systemName='BlueFoxClientSystem')
def ListenForEvent(self, namespace=MC_SYSTEM_NAMESPACE, systemName=MC_SYSTEM_NAME, eventName=None, priority=0):
"""
监听事件,binding的服务端可监听服务端事件,binding的客户端则可监听客户端事件
:param eventName: 可选:事件名 or 不传入参数.若不传入参数,则将函数名称视为事件名称
:param namespace:如果需要监听其他系统内的事件,需要传入系统的namespace
:param systemName:如果需要监听其他系统内的事件,需要传入系统的systemName
:param priority:执行优先级
:return:
"""
def Listen(func):
EventName = (
namespace, systemName, eventName if eventName else func.__name__,
self.bindingInstance if self.bindingInstance else 0, func, priority
)
self.systemListenEvent.add(EventName)
# 热更新重监听
self.__listen(EventName)
return func
return Listen
def __listen(self, eventName):
if self.bindingInstance:
self.bindingInstance.ListenForEvent(*eventName)
self.lastListened.add(eventName)
def FilterListener(self, namespace=MC_SYSTEM_NAMESPACE, systemName=MC_SYSTEM_NAME, eventName=None, priority=0):
"""
滤过监听器, 通常用于优化代码可读性
"""
def Filter(*args):
# type:(Callable) -> Callable
def Listener(func):
_filter = EventFilter(func, self.bindingInstance, *args)
EventName = (namespace, systemName, eventName, _filter, _filter.Filter, priority)
self.filterEvent.add(EventName)
self.__listen(EventName)
return func
return Listener
return Filter
def Binding(self, systemInstance):
"""
绑定系统类
:param systemInstance:系统实例
:return:
"""
self.bindingInstance = systemInstance
self.FirstListen()
self.Systems[self.bindingInstance.__class__.__name__] = self.bindingInstance
@classmethod
def GetSystemByClsName(cls, systemName):
# type:(str) -> Union[clientBaseSystem,serverBaseSystem]
return cls.Systems.get(systemName, None)
def Relisten(self):
# 重监听之前需要取消监听监听过的事件
for args in self.lastListened:
self.bindingInstance.UnListenForEvent(*args)
self.lastListened = set()
self.filterEvent = set()
def FirstListen(self):
# 初次监听
for args in self.systemListenEvent:
args = list(args)
args[3] = self.bindingInstance
if hasattr(self.bindingInstance, args[4].__name__):
self.bindingInstance.ListenForEvent(*args)
self.lastListened.add(tuple(args))
for args in self.filterEvent:
args = list(args)
args[3].systemInstance = self.bindingInstance
self.bindingInstance.ListenForEvent(*args)
self.lastListened.add(tuple(args))
@@ -0,0 +1,162 @@
# coding=utf-8
import logging
import mod.client.extraClientApi as clientApi
import mod.server.extraServerApi as serverApi
if False:
from typing import *
IsDebug = 1
def modDebug(msg):
"""输出debug信息"""
if IsDebug: logging.debug('[DN-MJ] {}'.format(msg))
def modInfo(msg):
"""输出信息Log"""
logging.info('[DN-MJ] {}'.format(msg))
def modWarning(msg):
"""输出警告Log"""
logging.warning('[DN-MJ] {}'.format(msg))
class ButtonTouchTypeEnum(object):
TouchDown = 0
TouchUp = 1
TouchCancel = 2
MoveOut = 3
class ItemOperate(object):
"""服务端操作用数据集"""
EXCHANGE_FLYING = 0
EXCHANGE = 1
MERGE = 2
DROP = 3
DROP_FLYING = 4
@staticmethod
def DropFlying():
"""
丢弃飞行物品
:return:
"""
return {'type': ItemOperate.DROP_FLYING}
@staticmethod
def ExchangeFlying(slot, slotName):
"""
和飞行物交换物品
:param slot: 槽位索引
:param slotName: 槽位名称
:return:
"""
return {
'slot': slot, 'slotName': slotName, 'type': ItemOperate.EXCHANGE_FLYING
}
@staticmethod
def Exchange(fromSlot, fromSlotName, toSlot, toSlotName):
"""
交换槽位物品
:param fromSlot: 交换起点槽位相对索引
:param fromSlotName: 交换起点槽位名称
:param toSlot:交换重点槽位相对索引
:param toSlotName:交换终点槽位名称
:return:
"""
return {
'fromSlot': fromSlot, 'fromSlotName': fromSlotName, 'toSlot': toSlot, 'toSlotName': toSlotName,
'type': ItemOperate.EXCHANGE
}
@staticmethod
def Drop(slot, slotName):
"""
丢弃槽位物品
:param slot:槽位相对索引
:param slotName:槽位名称
:return:
"""
return {
'slot': slot, 'slotName': slotName, 'type': ItemOperate.DROP
}
@staticmethod
def Merge(fromSlot, fromSlotName, toSlot, toSlotName):
"""
合并槽位物品
:param fromSlot: 合并起点槽位相对索引
:param fromSlotName: 合并起点槽位名称
:param toSlot:合并重点槽位相对索引
:param toSlotName:合并终点槽位名称
:return:
"""
return {
'fromSlot': fromSlot, 'fromSlotName': fromSlotName, 'toSlot': toSlot, 'toSlotName': toSlotName,
'type': ItemOperate.MERGE
}
class PlayerBehaviorTree(object):
Sleep = 0b00000001
Sprinting = 0b00000010
Sneaking = 0b00000100
Gliding = 0b00001000
Swimming = 0b00010000
Crawling = 0b00100000
Flying = 0b01000000
SpinAttack = 0b10000000
__pat = clientApi.GetMinecraftEnum().PlayerActionType
PlayerBehaviors = {
__pat.StartSleeping: (Sleep, True),
__pat.StopSleeping: (Sleep, False),
__pat.StartSprinting: (Sprinting, True),
__pat.StopSprinting: (Sprinting, False),
__pat.StartSneaking: (Sneaking, True),
__pat.StopSneaking: (Sneaking, False),
__pat.StartGliding: (Gliding, True),
__pat.StopGliding: (Gliding, False),
__pat.StartSwimming: (Swimming, True),
__pat.StopSwimming: (Swimming, False),
__pat.StartCrawling: (Crawling, True),
__pat.StopCrawling: (Crawling, False),
__pat.StartFlying: (Flying, True),
__pat.StopFlying: (Flying, False),
__pat.StartSpinAttack: (SpinAttack, True),
__pat.StopSpinAttack: (SpinAttack, False)
}
from .ModConfig import MOD_NAME, MOD_VERSION
LevelId = serverApi.GetLevelId() or clientApi.GetLevelId()
Platform = serverApi.GetPlatform() or clientApi.GetPlatform()
# Platform = 1 # 测试用
MC_SYSTEM_NAME = serverApi.GetEngineSystemName()
MC_SYSTEM_NAMESPACE = serverApi.GetEngineNamespace()
# ToolsConfig
CREATED_TOOLS = {}
CREATED_SCREEN_TOOLS = {}
# CompFactory
clientCompFactory = clientApi.GetEngineCompFactory()
serverCompFactory = serverApi.GetEngineCompFactory()
# System
serverBaseSystem = serverApi.GetServerSystemCls()
clientBaseSystem = clientApi.GetClientSystemCls()
# UI
if not serverApi.IsInServer():
bookManager = clientApi.GetBookManager()
nativeScreenManager = clientApi.GetNativeScreenManagerCls().instance() # type:clientApi.GetNativeScreenManagerCls()
viewBinder = clientApi.GetViewBinderCls()
baseScreen = clientApi.GetScreenNodeCls()
customUIScreenProxy = clientApi.GetUIScreenProxyCls()
RootScreenPathHead = (
'variables_button_mappings_and_controls/safezone_screen_matrix/'
'inner_matrix/safezone_screen_panel/root_screen_panel'
)
@@ -0,0 +1,113 @@
# coding=utf-8
from ..ToolsConfig import *
def RegisterSystemFastly(api, cls):
"""
快速注册系统
:param api:serverApi or clientApi
:param cls:系统类
:return:
"""
api.RegisterSystem(
MOD_NAME, cls.__name__,
'{}.{}'.format(cls.__module__, cls.__name__)
)
class SystemTools(object):
"""
系统工具箱,以不同类进行分辨
"""
classFunc = []
@classmethod
def Binding(cls, ModName='', ModVersion=''):
"""
绑定系统类
:param ModName:模组名称
:param ModVersion:模组版本
:return:
"""
def binding(systemCls):
tag = systemCls.__name__ + ModName + ModVersion
if tag not in CREATED_TOOLS:
CREATED_TOOLS[tag] = cls(systemCls)
else:
CREATED_TOOLS[tag].Relisten()
return systemCls
return binding
@staticmethod
def CreateListener(namespace, systemName):
"""
创建固定系统的监听函数.拿到函数后使用方法与ListenForEvent相同但不需要传入namespace和systemName参数
:param namespace:需要监听的系统namespace
:param systemName:需要监听的系统systemName
:return:
"""
def Listener(eventName=None, priority=0):
def Listen(func):
funcName = func.__name__
EventName = [namespace, systemName, eventName if eventName else funcName, 0, func, priority]
if EventName not in SystemTools.classFunc:
SystemTools.classFunc.append(EventName)
return func
return Listen
return Listener
@staticmethod
def ListenForEvent(eventName=None, namespace=MC_SYSTEM_NAMESPACE, systemName=MC_SYSTEM_NAME, priority=0):
"""
监听事件,binding的服务端可监听服务端事件,binding的客户端则可监听客户端事件
:param eventName: 可选:事件名 or 不传入参数.若不传入参数,则将函数名称视为事件名称
:param namespace:如果需要监听其他系统内的事件,需要传入系统的namespace
:param systemName:如果需要监听其他系统内的事件,需要传入系统的systemName
:param priority:执行优先级
:return:
"""
def Listen(func):
funcName = func.__name__
EventName = [namespace, systemName, eventName if eventName else funcName, 0, func, priority]
if EventName not in SystemTools.classFunc:
SystemTools.classFunc.append(EventName)
return func
return Listen
def __init__(self, cls):
self.systemCls = cls
self.InitSystemOriginal = self.systemCls.__init__
setattr(self.systemCls, '__init__', self.InitInstead())
self.bindingInstance = None
def Relisten(self):
for args in SystemTools.classFunc:
args[3] = self.bindingInstance
self.bindingInstance.UnListenForEvent(*args)
self.bindingInstance.ListenForEvent(*args)
args[3] = 0
def __Listen(self):
if self.bindingInstance:
for args in SystemTools.classFunc:
args[3] = self.bindingInstance
if hasattr(self.bindingInstance, args[4].__name__):
self.bindingInstance.ListenForEvent(*args)
args[3] = 0
def InitInstead(self):
def __init__(systemInstance, namespace, systemName, self=self):
self.InitSystemOriginal(systemInstance, namespace, systemName)
if systemInstance.__class__ is self.systemCls:
self.bindingInstance = systemInstance
self.__Listen()
return __init__
Whitespace-only changes.
@@ -0,0 +1,39 @@
# Blue Fox Tools Plus
### 使用前须知
1. 在使用工具之前,需要修改以下变量:*BlueFoxTools.ToolsConfig.MOD_NAME*以及*BlueFoxTools.ToolsConfig.MOD_VERSION*
.变量分别为模组名称,模组版本
### 自动注册
1. 使用Load函数自动注册系统时则需要`from BlueFoxTools.ModLoader import *`
2. 自动注册系统时,需要在LoadMod之前声明所需要注册的类
3. 自动注册系统时,仅注册BlueFox系统及其最终继承子类
4. 自动注册的系统,nameSpace为MOD_NAME,systemName为类名
### SystemTools
1. 使用时,需要在类__init__函数下调用`SystemTools.Binding`方法
2. 使用时,需要在类内生成SystemTools,使用`SystemTools.GetTools`方法获取
### 背包引擎
1. 物品数据,确保空物品为{}
2. 使用时需要将gird节点名称,collection_name与slotDict内的槽位偏移键同名
3. 需要结合背包引擎Json包使用
4. 创建界面时,Parm需要传入两个参数:items-物品,posDim-工作方块坐标及维度
5. 如需物品跟手功能,需要在界面添加`"bindings":[{"binding_name":"#main.gametick","binding_condition":"always"}],`字段
### ScreenTools
1. 如果自动注册界面,会在类内添加属性`cls.blueFoxClient = BlueFoxClientSystem`
2. 使用自动注册界面时则需要声明类的位置
3. 使用时,需要在类Create函数下调用`ScreenTools.Binding`方法
4. 使用时,需要在类内生成ScreenTools,使用`ScreenTools.GetTools`方法获取
### 方块块
1. 模型具有一定的制作规则:界面朝北,以模型左下角为原点,移动到建模软件原点
2. 方块具有一定规则:必须为四面向方块,必须为方块实体
3. 创建之前应使用Able方法确认是否可以创建
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
@@ -0,0 +1,8 @@
# coding=utf-8
from BlueFoxTools.ModLoader import *
if not serverApi.IsInServer():
import System.Client
import System.Server
LoadMod()
Whitespace-only changes.