# 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: """ itemDict1, itemDict2 = DeclareItemAnyNum(self.items[originSlot], 64 - 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)