commit 916551c239faa15d1ec14987913c10d8a9b16ba7 Author: Toger Date: Sun May 11 13:27:46 2025 +0000 上传文件至 / diff --git a/Iceboat.py b/Iceboat.py new file mode 100644 index 0000000..b7498e8 --- /dev/null +++ b/Iceboat.py @@ -0,0 +1,3940 @@ +# coding: utf-8 +# 冰船系统 by DreamCityToger +import time +import pyspigot as ps +from org.bukkit import Bukkit, Sound, Location, Material, Particle, SoundCategory, GameMode +from org.bukkit.util import Vector +from org.bukkit.block import BlockFace +from org.bukkit.entity import Player, EntityType, Boat, ArmorStand +from org.bukkit.Bukkit import getServer +from org.bukkit.inventory import ItemStack +from org.bukkit.inventory.EquipmentSlot import HAND +from org.bukkit.scoreboard import Criteria +from org.bukkit.event.block import Action +from org.bukkit.event.vehicle import VehicleExitEvent +from org.bukkit.event.world import EntitiesLoadEvent +from org.bukkit.event.entity import EntityDamageEvent +from org.bukkit.event.entity import EntityDamageByEntityEvent +from org.bukkit.event.inventory import InventoryClickEvent +from org.bukkit.event.inventory import InventoryDragEvent +from org.bukkit.event.inventory import InventoryCloseEvent +from org.bukkit.event.player import PlayerQuitEvent +from org.bukkit.event.player import PlayerJoinEvent +from org.bukkit.event.player import PlayerInteractEvent +from org.bukkit.event.player import PlayerInteractEntityEvent +from org.bukkit.event.player import PlayerSwapHandItemsEvent +from org.bukkit.event.player import PlayerDropItemEvent +from org.bukkit.event.player import PlayerTeleportEvent +from org.bukkit.event.player import PlayerArmorStandManipulateEvent +from org.bukkit.potion import PotionEffect, PotionEffectType +from org.bukkit.NamespacedKey import fromString + +#from com.destroystokyo.paper.event.player import PlayerStopSpectatingEntityEvent +from net.milkbowl.vault.economy import Economy +from net.md_5.bungee.api.chat import TextComponent +from net.md_5.bungee.api.ChatMessageType import ACTION_BAR + +from util2.plugins.head import PlayerHeadsAPI +from util2.develop.gui.builder import initializeItemStack +from util2.develop.gui.builder import isGUI +from util2.develop.gui.builder import getGUIValue +from util2.develop.gui.bugfixer import closeGuiForAll +from util2.develop.gui.bugfixer import cancelShiftClickFromPlayerInventory +from util2.develop.gui.bugfixer import denyAllInventoryDrag +from util2.develop.gui.manager import GUIManager +from util2.develop.gui.manager import GUIController + +# 插件加载 ------------------------------------------------------------------- +vaultEconomy = None +def setupEconomy(): + global vaultEconomy + if Economy is None: + return False + plugin = getServer().getPluginManager().getPlugin("Vault") + if plugin is None: + return False + registeredServices = getServer().getServicesManager().getRegistrations(Economy) + if not registeredServices: + return False + vaultEconomy = registeredServices[0].getProvider() + return True +setupEconomy() # 初始化Vault Economy +playerHeadsAPI = PlayerHeadsAPI() # 初始化Player Heads + + + + +# 全局常量变量 ------------------------------------------------------------------- +MAX_PLAYERS = 8 #单房间最多可以加入的玩家数量 +AVAILABLE_MAPNUM = 39 #可用地图数量 +WORLD_NAME = u'iceboat' #世界名 +LEAVE_INTERVAL = 12 #玩家离开房间操作要求(刻) +COLLECTION_CONFIG_PATH = u"iceboat/collection.yml" +ROOMS_CONFIG_PATH = u'iceboat/rooms.yml' #房间配置文件路径 +HOLOGRAME_CONFIG_PATH = u'iceboat/hologram.yml' +START_LINE_MATERIAL = Material.GLASS #启动线材料(默认玻璃) +ENDING_INTERVAL = 60 #结束时间间隔(刻) +ARMOR_STAND_INTERVAL = 0.26 #盔甲架间距 +LOBBY_POSITION = 0, 64, 0 #大厅位置 +LOBBY_RADIUS = 300 #大厅半径 +DEFAULT_BOAT_MATERIAL = u'OAK_BOAT' #默认船类型 +DEFAULT_BOAT_DISPLAY = u'橡木船' #默认船展示名 +DEFAULT_PARTICLE_MATERIAL = u'END_ROD' #默认粒子材质 +DEFAULT_PARTICLE_TYPE = u'END_ROD' #默认粒子类型 +DEFAULT_PARTICLE_DISPLAY = u'烛光' #默认粒子展示名 +DEFAULT_TITLE_DISPLAY = u'新手' #默认称号展示名 +DEFAULT_TITLE_MATERIAL = u'NAME_TAG' #默认称号材质 +REWARD_AMOUNT = 100 #默认奖励金额基准 +SIGNS = [ #告示牌类型 + Material.OAK_SIGN, Material.OAK_WALL_SIGN, + Material.SPRUCE_SIGN, Material.SPRUCE_WALL_SIGN, + Material.BIRCH_SIGN, Material.BIRCH_WALL_SIGN, + Material.JUNGLE_SIGN, Material.JUNGLE_WALL_SIGN, + Material.ACACIA_SIGN, Material.ACACIA_WALL_SIGN, + Material.DARK_OAK_SIGN, Material.DARK_OAK_WALL_SIGN, + Material.CRIMSON_SIGN, Material.CRIMSON_WALL_SIGN, + Material.WARPED_SIGN, Material.WARPED_WALL_SIGN, + Material.MANGROVE_SIGN, Material.MANGROVE_WALL_SIGN, + Material.CHERRY_SIGN, Material.CHERRY_WALL_SIGN, + Material.BAMBOO_SIGN, Material.BAMBOO_WALL_SIGN, + Material.PALE_OAK_SIGN, Material.PALE_OAK_WALL_SIGN + ] + +singleGames = {} #单人游戏房间 +multiGames = {} #多人游戏房间 +playersInParticlePreview = {} #预览玩家 +playersInSpectate = {} #旁观者 +playerPage = {} #玩家页数 + +# 调用管理类 ------------------------------------------------------------------- +class PlayerManager: #玩家管理 + def __init__(self, player): + self.player = player + self.playerName = player.getName() + def removeBoat(self): #移除玩家的船 + boat = self.player.getVehicle() #进服时如果坐船则删船 + if boat and isinstance(boat, Boat): + boat.remove() + def teleportToJoinLoc(self): #传送到加入位置 + playerData = PlayerDataManager(self.playerName) #获取玩家数据 + joinLoc = playerData.getJoinLoc() #获取玩家加入位置 + loc = Location(worldManager.getWorld(), joinLoc[0], joinLoc[1], joinLoc[2]) + self.player.teleport(loc) + def teleportToLobby(self): #传送到大厅 + lobbyLoc = Location(worldManager.getWorld(), LOBBY_POSITION[0], LOBBY_POSITION[1], LOBBY_POSITION[2]) + self.player.teleport(lobbyLoc) + def outGame(self): #玩家异常离开游戏处理 + objective = getScoreboardObjective(u'InGame') + playerScore = objective.getScore(self.playerName) #获取玩家分数 + score = playerScore.getScore() #获取分数 + if score == 1: #1为玩家在游戏中 + self.teleportToJoinLoc() + playerScore.setScore(0) + def leaveRoom(self): #让玩家离开房间 + if self.leaveSingleRoom(): + return True + if self.leaveMultiRoom(): + return True + self.player.sendTitle(u"§f", u"§c你不在任何游戏房间中", 5, 60, 10) + self.player.playSound(self.player, Sound.ENTITY_VILLAGER_NO, 1.0, 1.0) + return False + def leaveSingleRoom(self): + singleRoom = self.inSingleRoom() + if singleRoom: #如果在单人房间内 + singleRoom.closeRoom() + self.player.sendTitle(u"§f", u"§6离开成功", 5, 60, 10) + self.player.playSound(self.player, Sound.BLOCK_NOTE_BLOCK_BASS, 1.0, 1.0) + return True + return False + def leaveMultiRoom(self): + multiRoom = self.inMultiRoom() + if multiRoom: #如果在多人房间内 + multiRoom.delPlayer(self.playerName) + self.player.sendTitle(u"§f", u"§6离开成功", 5, 60, 10) + self.player.playSound(self.player, Sound.BLOCK_NOTE_BLOCK_BASS, 1.0, 1.0) + return True + return False + def delayExit(self): #延迟离开载具判定 + objective = getScoreboardObjective(u'IBLeaveAction') + playerScore = objective.getScore(self.playerName) #获取玩家 + score = playerScore.getScore() + num = score + 1 + playerScore.setScore(num) + if num == 1: + ps.scheduler.runTaskLater(lambda : playerScore.setScore(0), LEAVE_INTERVAL) #重置计分板 + if num < LEAVE_INTERVAL: + remainTime = round(LEAVE_INTERVAL - num) / 20 #剩余时间 + if num > 2: #前两下不反馈 + self.player.sendTitle("", u"§c{:.2f} §4秒后离开船只".format(remainTime), 0, 2, 10) + return True + else: + playerScore.setScore(0) + return False + def updateInventory(self): # 更新玩家背包 + worldName = self.player.getWorld().getName() + if worldName != WORLD_NAME: #只修改冰船世界的玩家背包 + return + inv = self.player.getInventory() # 清空玩家背包 + inv.clear() + def getItemstacks(): #异步获取物品 + itemstacks = {} + itemstacks[3] = initializeItemStack(Material.SLIME_BALL, u"§e>> §a单人模式 §e<<") + itemstacks[5] = initializeItemStack(Material.MAGMA_CREAM, u"§6>> §c快速离开 §6<<") + itemstacks[19] = initializeItemStack(Material.SPYGLASS, u"§6>> §b打开粒子预览页面 §6<<", u"§7仅在大厅可预览粒子效果") + itemstacks[21] = initializeItemStack(Material.CHEST, u"§6>> §b打开收集品菜单 §6<<", u'§7设置船/粒子/称号等') + itemstacks[23] = initializeItemStack(Material.PAPER, u"§6>> §b打开驾照等级页面 §6<<", u"§7查看驾照等级要求和升级") + itemstacks[25] = initializeItemStack(Material.SKULL_BANNER_PATTERN, u"§6>> §b打开排名查询页面 §6<<", u'§7查询全服玩家各数据排名') + itemstacks[27] = initializeItemStack(Material.BEACON, u"§6>> §e切换成绩广播模式 §6<<", u'§7将成绩广播给同世界玩家') + itemstacks[29] = initializeItemStack(Material.FIREWORK_STAR, u"§6>> §e切换粒子模式 §6<<", u'§7使用自定义粒子效果') + itemstacks[31] = initializeItemStack(Material.OAK_HANGING_SIGN, u"§6>> §e切换详细播报模式 §6<<", u'§7在聊天框播报详细信息') + itemstacks[33] = initializeItemStack(Material.NOTE_BLOCK, u"§6>> §e切换音乐模式 §6<<", u'§7需安装冰船音乐资源包') + itemstacks[35] = initializeItemStack(Material.JUKEBOX, u"§6>> §c关闭当前音乐 §6<<", u'§7需安装冰船音乐资源包') + return itemstacks + def setItemstacks(itemstacks): #同步设置物品 + for slot, itemstack in itemstacks.items(): + inv.setItem(slot, itemstack) + ps.scheduler.runSyncCallbackTask(getItemstacks, setItemstacks) #同步回调 + self.updateInventoryPlayerInfo() #更新玩家背包玩家信息显示 + self.updateInventoryPlayAgain() #更新玩家背包再来一把显示 + def updateInventoryPlayerInfo(self): #更新玩家背包玩家信息显示 + worldName = self.player.getWorld().getName() + if worldName != WORLD_NAME: #只修改冰船世界的玩家背包 + return + inv = self.player.getInventory() + def getItemstackPlayerInfo(): #异步获取物品 + playerName = self.player.getName() + playerData = PlayerDataManager(playerName) + itemstack = initializeItemStack(playerHeadsAPI.getPlayerHead(playerName), u"§6〓 §a玩家信息 §6〓", + u'§eDC币余额: §f{}'.format(playerData.getBalance()), + u'§e完成次数: §f{}'.format(playerData.getTotalMapTimes()), + u'§e当季分数: §f{}'.format(playerData.getSeasonScore()), + u'§e驾照等级: {} §7[{}]'.format(playerData.getLevelTitle(), playerData.getLevel()), + u'§f', + u'§6>> §a点击更新 §6<<' + ) + return itemstack + def setItemstackPlayerInfo(itemstack): #同步设置物品 + inv.setItem(13, itemstack) + ps.scheduler.runSyncCallbackTask(getItemstackPlayerInfo, setItemstackPlayerInfo) + def updateInventoryPlayAgain(self): + worldName = self.player.getWorld().getName() + if worldName != WORLD_NAME: #只修改冰船世界的玩家背包 + return + inv = self.player.getInventory() + def getItemstackPlayAgain(): #异步获取物品 + playerData = PlayerDataManager(self.playerName) + lastMap = playerData.getLastMap() + mapDisplay = MapManager(lastMap).getDisplay() + itemstack = initializeItemStack(Material.NETHER_STAR, u"§6>> §a再来一把 §e{} §6<<".format(mapDisplay), u"§7快速进入上次游玩的地图(单人模式)", data=str(lastMap)) + return itemstack + def setItemstackPlayAgain(itemstack): #同步设置物品 + inv.setItem(1, itemstack) + ps.scheduler.runSyncCallbackTask(getItemstackPlayAgain, setItemstackPlayAgain) + def removePlayerFromRoom(self): #将玩家从任一房间移除 + for roomId, room in multiGames.items(): + if self.playerName in room.playerList: + room.delPlayer(self.playerName) + if self.playerName in singleGames: + del singleGames[self.playerName] + def joinSingleRoom(self, mapNum): + available = roomsManager.getAvailableRoomCount(mapNum) + if available > 0: + mapNum = int(mapNum) + roomNum = roomsManager.getAvailableRoom(mapNum) # 获取可用的副本编号 + if roomNum is None: + self.player.sendTitle(u"§f", u"§c暂无空房", 5, 60, 10) + self.player.playSound(self.player, Sound.ENTITY_VILLAGER_NO, 1.0, 1.0) + return False + if self.playerName in singleGames: + self.player.sendMessage(u"§c你已经在单人模式中了") + self.player.playSound(self.player, Sound.ENTITY_VILLAGER_NO, 1.0, 1.0) + return False + else: + singleGames[self.playerName] = SingleGameRoom(self.playerName, mapNum, roomNum) + roomsManager.setAvailableRoom(mapNum, roomNum, True) # 标记房间为已使用 + singleGames[self.playerName].enterRunning() + return True + else: + self.player.playSound(self.player, Sound.ENTITY_VILLAGER_NO, 1.0, 1.0) + self.player.sendMessage(u"§c该地图暂无空房,请稍后再试") + return False + #玩家判定 + def inAnyRoom(self): #检查玩家是否在任一房间内 + if self.inMultiRoom() or self.inSingleRoom(): + return True + return False + def inSingleRoom(self): + if self.playerName in singleGames: + return singleGames[self.playerName] + return False + def inMultiRoom(self): #检测玩家是否在多人房间内 + for room in multiGames.values(): + if room.isPlayerInRoom(self.playerName): + return room #返回房间实例 + return False + def inLobby(self): #检查玩家是否在大厅内 + playerLocation = self.player.getLocation() + playerX = playerLocation.getX() + playerZ = playerLocation.getZ() + lobbyXMax = LOBBY_POSITION[0] + LOBBY_RADIUS + lobbyXMin = LOBBY_POSITION[0] - LOBBY_RADIUS + lobbyZMax = LOBBY_POSITION[2] + LOBBY_RADIUS + lobbyZMin = LOBBY_POSITION[2] - LOBBY_RADIUS + if lobbyXMin < playerX < lobbyXMax and lobbyZMin < playerZ < lobbyZMax: + return True + else: + return False +class PlayerDataManager: #玩家数据管理 + def __init__(self, playerName): + self.name = playerName + self.player = Bukkit.getPlayer(self.name) + self.playerData = ps.config.loadConfig("iceboat/playerData/{}.yml".format(self.name)) + def getJoinLoc(self): #获取玩家进入前位置 + x = self.playerData.getDouble("info.joinLoc.x", LOBBY_POSITION[0]) + y = self.playerData.getDouble("info.joinLoc.y", LOBBY_POSITION[1]) + z = self.playerData.getDouble("info.joinLoc.z", LOBBY_POSITION[2]) + joinLoc = x, y, z + return joinLoc + def getBoatMaterial(self): #获取玩家船类型 + index = self.playerData.getString("setting.boatMaterial", None) + if index is None: + return DEFAULT_BOAT_MATERIAL + return collectionManager.getBoatMaterial(index) + def getBoatDisplay(self): #获取玩家船显示名 + index = self.playerData.getString("setting.boatMaterial", None) + if index is None: + return DEFAULT_BOAT_DISPLAY + return collectionManager.getBoatDisplay(index) + def getParticleMaterial(self): #获取玩家粒子类型 + index = self.playerData.getString("setting.particleType", None) + if index is None: + return DEFAULT_PARTICLE_MATERIAL + return collectionManager.getParticleMaterial(index) #获取玩家粒子类型 + def getParticleType(self): #获取玩家粒子类型 + index = self.playerData.getString("setting.particleType", None) + if index is None: + return DEFAULT_PARTICLE_TYPE + return collectionManager.getParticleType(index) + def getParticleInfo(self): #获取玩家粒子信息 + index = self.playerData.getString("setting.particleType", None) + if index is None: + return 1, 0.0, 0.0, 0.0, 0.0, 0.0 + return collectionManager.getParticleInfo(index) + def getParticleDisplay(self): #获取玩家粒子显示名 + index = self.playerData.getString("setting.particleType", None) + if index is None: + return DEFAULT_PARTICLE_DISPLAY + return collectionManager.getParticleDisplay(index) + def getTitleMaterial(self): #获取玩家称号材质 + index = self.playerData.getString("setting.title", None) + if index is None: + return DEFAULT_TITLE_MATERIAL + return collectionManager.getTitleMaterial(index) + def getTitleDisplay(self): #获取玩家称号 + index = self.playerData.getString("setting.title", None) + if index is None: + return DEFAULT_TITLE_DISPLAY + return collectionManager.getTitleDisplay(index) + def getLevel(self): #获取玩家等级 + self.level = self.playerData.getDouble("info.level", 0) + return self.level + def getLevelTitle(self): #获取玩家等级标识 + level = self.getLevel() + if level < 1: + title = u'§7N' + elif level < 2: + title = u'§fL5' + elif level < 3: + title = u'§aL4' + elif level < 4: + title = u'§bL3' + elif level < 5: + title = u'§dL2' + elif level < 6: + title = u'§eL1' + else: + title = u'§cPRO' + return title + def getLevelColor(self): #获取玩家等级颜色 + level = self.getLevel() + if level < 1: + colorCode = u"§7" + elif level < 2: + colorCode = u"§f" + elif level < 3: + colorCode = u"§a" + elif level < 4: + colorCode = u"§b" + elif level < 5: + colorCode = u"§d" + elif level < 6: + colorCode = u"§e" + else: + colorCode = u"§c" + return colorCode + def getBalance(self): #获取玩家余额 + if vaultEconomy: + return vaultEconomy.getBalance(self.player) + return 0 + def getParticleMode(self): #获取玩家粒子模式 + mode = self.playerData.getBoolean("setting.particleMode", True) + return mode + def getMusicMode(self): #获取玩家音乐模式 + mode = self.playerData.getBoolean("setting.musicMode", False) + return mode + def getDetailMode(self): #获取玩家详细信息模式 + mode = self.playerData.getBoolean("setting.detailMode", False) + return mode + def getBroadcastMode(self): #获取玩家广播模式 + mode = self.playerData.getBoolean("setting.broadcastMode", True) + return mode + def getOwnedItems(self, type='boats'): + self.ownedItems = self.playerData.getList("info.{}".format(type), []) + return self.ownedItems + + def getTotalMapTimes(self): #获取玩家总完成次数 + self.totalMapTimes = self.playerData.getInt("info.totalMapTimes", 0) + return self.totalMapTimes + def getSingleBestMapRecord(self, mapNum): #获取玩家单人最好成绩 + self.singleBestMapRecord = self.playerData.getDouble("record.{}.singleBestMapRecord".format(mapNum), 0) + return self.singleBestMapRecord + def getSingleBestMapRecordTimestamp(self, mapNum): #获取玩家单人最好成绩时间戳 + self.singleBestMapRecordTimestamp = self.playerData.getDouble("record.{}.singleBestMapRecordTimestamp".format(mapNum), 0) + return self.singleBestMapRecordTimestamp + def getSingleBestMapRecordDay(self, mapNum): #获取玩家单人最好成绩日期 + timestamp = self.getSingleBestMapRecordTimestamp(mapNum) + if timestamp == 0: + return u'暂无' + day = time.strftime("%Y/%m/%d", time.localtime(timestamp)) + return day + def getSingleBestMapRecordPointTimer(self, mapNum, lap, point): #获取单人最佳纪录某个点位的时间 + return self.playerData.getDouble('record.{}.singleBestMapRecordPointsTimer.{}.{}'.format(mapNum, lap, point), 0) + def getMultiBestMapRecord(self, mapNum): #获取玩家多人最好成绩 + self.multiBestMapRecord = self.playerData.getDouble("record.{}.multiBestMapRecord".format(mapNum), 0) + return self.multiBestMapRecord + def getMultiBestMapRecordTimestamp(self, mapNum): #获取玩家多人最好成绩时间戳 + self.multiBestMapRecordTimestamp = self.playerData.getDouble("record.{}.multiBestMapRecordTimestamp".format(mapNum), 0) + return self.multiBestMapRecordTimestamp + def getMultiBestMapRecordDay(self, mapNum): #获取玩家多人最好成绩日期 + timestamp = self.getMultiBestMapRecordTimestamp(mapNum) + if timestamp == 0: + return u'暂无' + day = time.strftime("%Y/%m/%d", time.localtime(timestamp)) + return day + def getMultiBestMapRecordPointTimer(self, mapNum, lap, point): #获取多人最佳纪录某个点位的事件 + return self.playerData.getDouble('record.{}.multiBestMapRecordPointsTimer.{}.{}'.format(mapNum, lap, point), 0) + def getSeasonBestMapRecord(self, mapNum): #获取玩家当前季节最好成绩 + self.seasonBestMapRecord = self.playerData.getDouble("record.{}.seasonBestMapRecord".format(mapNum), 0) + return self.seasonBestMapRecord + def getSeasonMapScore(self, mapNum): #获取玩家当前季节成绩 + self.seasonMapScore = self.playerData.getDouble("record.{}.seasonMapScore".format(mapNum), 0) + return self.seasonMapScore + def getSeasonScore(self): + self.seasonScore = self.playerData.getDouble("info.seasonScore", 0.0) + return self.seasonScore + def getMapTimes(self, mapNum): #获取玩家完成地图次数 + self.mapTimes = self.playerData.getInt("record.{}.mapTimes".format(mapNum), 0) + return self.mapTimes + def getLastMap(self): #获取玩家上一次完成的地图 + self.lastMap = self.playerData.getInt("info.lastMap", 1) + return self.lastMap + + def setJoinLoc(self, x=0, y=64, z=0): #设置玩家的进入位置 + self.playerData.set('info.joinLoc.x', x) + self.playerData.set('info.joinLoc.y', y) + self.playerData.set('info.joinLoc.z', z) + self.playerData.save() + def setBoatMaterial(self, boatMaterial): #设置玩家船类型 + self.playerData.set("setting.boatMaterial", boatMaterial) + self.playerData.save() + def setParticleType(self, particleType): #设置玩家粒子类型 + self.playerData.set("setting.particleType", particleType) + self.playerData.save() + def setTitle(self, title): #设置玩家称号 + self.playerData.set("setting.title", title) + self.playerData.save() + ps.scheduler.runTaskLater(lambda h=hallOfFameManager:hallOfFameManager.updateHallOfFameByName(self.name), 40) + def setLevel(self, level): #设置玩家等级 + self.playerData.set("info.level", level) + self.playerData.save() + + playerName = self.player.getName() + rankManager.updateRankingAsyn(playerName, level, None, recordType=u'level') + + ps.scheduler.runTaskLater(lambda h=hallOfFameManager:hallOfFameManager.updateHallOfFameByName(self.name), 40) + + def setTotalMapTimes(self, totalMapTimes): #设置玩家总完成次数 + self.playerData.set("info.totalMapTimes", totalMapTimes) + self.playerData.save() + def setMapTimes(self, mapNum, mapTimes): #设置玩家完成地图次数 + self.playerData.set("record.{}.mapTimes".format(mapNum), mapTimes) + self.playerData.save() + def setLastMap(self, mapNum): #设置玩家上一次完成的地图 + self.playerData.set("info.lastMap", int(mapNum)) + self.playerData.save() + playerManager = PlayerManager(self.player) + playerManager.updateInventoryPlayAgain() + def setSingleBestMapRecord(self, mapNum, singleBestMapRecord): #设置玩家单人最好成绩 + self.playerData.set("record.{}.singleBestMapRecord".format(mapNum), singleBestMapRecord) + self.playerData.save() + def setSingleBestMapRecordTimestamp(self, mapNum, singleBestMapRecordTimestamp): #设置玩家单人最好成绩时间戳 + self.playerData.set("record.{}.singleBestMapRecordTimestamp".format(mapNum), singleBestMapRecordTimestamp) + self.playerData.save() + def setSingleBestMapRecordPointsTimer(self, mapNum, singleBestMapRecordPointsTimer): #设置玩家单人最好成绩检查点 + self.playerData.set("record.{}.singleBestMapRecordPointsTimer".format(mapNum), singleBestMapRecordPointsTimer) + self.playerData.save() + def setMultiBestMapRecord(self, mapNum, multiBestMapRecord): #设置玩家多人最好成绩 + self.playerData.set("record.{}.multiBestMapRecord".format(mapNum), multiBestMapRecord) + self.playerData.save() + def setMultiBestMapRecordTimestamp(self, mapNum, multiBestMapRecordTimestamp): #设置玩家多人最好成绩时间戳 + self.playerData.set("record.{}.multiBestMapRecordTimestamp".format(mapNum), multiBestMapRecordTimestamp) + self.playerData.save() + def setMultiBestMapRecordPointsTimer(self, mapNum, multiBestMapRecordPointsTimer): #设置玩家多人最好成绩检查点 + self.playerData.set("record.{}.multiBestMapRecordPointsTimer".format(mapNum), multiBestMapRecordPointsTimer) + self.playerData.save() + def setSeasonBestMapRecord(self, mapNum, seasonBestMapRecord): #设置玩家当前季节最好成绩 + self.playerData.set("record.{}.seasonBestMapRecord".format(mapNum), seasonBestMapRecord) + self.playerData.save() + def setSeasonMapScore(self, mapNum, seasonMapScore): #设置玩家当前季节成绩 + self.playerData.set("record.{}.seasonMapScore".format(mapNum), seasonMapScore) + self.playerData.save() + seasonMaps = seasonManager.getSeasonMaps() + score = 0.0 + for mapNum in seasonMaps: + mapScore = self.getSeasonMapScore(int(mapNum)) + score += mapScore + self.playerData.set("info.seasonScore", score) + self.playerData.save() + rankManager.updateRankingAsyn(self.name, score, u"seasonScore") #更新季节分数排名 + ps.scheduler.runTaskLater(lambda h=hallOfFameManager:hallOfFameManager.updateHallOfFameByName(self.name), 40) + def setOwnedItems(self, type='boats', items=[]): #设置玩家拥有的收集品 + if type not in ['boats', 'particles', 'titles']: + return False + self.playerData.set("info.{}".format(type), [str(item) for item in items]) + self.playerData.save() + return True + def toggleModeBoolean(self, mode='detailMode'): #切换模式布尔值 + if mode not in ['particleMode', 'musicMode', 'detailMode', 'broadcastMode']: + return False + currentValue = self.playerData.getBoolean(u"setting.{}".format(mode), False) + newValue = not currentValue + self.playerData.set(u"setting.{}".format(mode), newValue) + self.playerData.save() + return newValue +class MapManager: #地图管理 + def __init__(self, mapNum): + self.mapNum = mapNum + self.mapData = ps.config.loadConfig("iceboat/maps/{}.yml".format(mapNum)) + def getLap(self): #获取地图圈数 + self.lap = self.mapData.getInt("lap", 1) #地图圈数 + return self.lap + def getYaw(self): #获取地图偏航角 + self.yaw = self.mapData.getDouble("yaw", 0.0) #地图偏航角 + return self.yaw + def getPoint(self): #获取地图点位数 + self.point = self.mapData.getInt("point", 2) #地图点位数 + return self.point + def getDisplay(self): + self.display = self.mapData.getString("display", u'未命名') #地图名字 + return self.display + def getMaterial(self): #获取地图显示材质 + self.material = self.mapData.getString("material", "PAPER") #地图显示材质 + return self.material + def getDifficulty(self): #获取地图难度 + self.difficulty = float(self.mapData.getString("difficulty", "0.0")) #地图难度 + return self.difficulty + def getDifficultySuffix(self): #获取地图难度后缀 + self.difficulty = float(self.mapData.getString("difficulty", "0.0")) #地图难度 + if 1 <= self.difficulty < 2: + suffix = u"§a★" + elif 2 <= self.difficulty < 3: + suffix = u"§b★★" + elif 3 <= self.difficulty < 4: + suffix = u"§d★★★" + elif 4 <= self.difficulty < 5: + suffix = u"§e★★★★" + elif 5 <= self.difficulty: + suffix = u"§c★★★★★" + else: + suffix = u"§f★" + return suffix + def getCreator(self): #获取地图创建者 + self.creator = self.mapData.getString("creator", "DreamCityToger") #地图创建者 + return self.creator + def getPoints(self): #获取地图所有点位坐标 + points = self.mapData.getConfigurationSection('points') + return points + + +# 全局管理类 ------------------------------------------------------------------- +class WorldManager: #世界管理 + def __init__(self): + self.world = getServer().getWorld(WORLD_NAME) + def getWorld(self): #获取世界对象 + return self.world +class CollectionManager: #收集品管理 + def __init__(self): + self.collectionData = ps.config.loadConfig(COLLECTION_CONFIG_PATH) + self.boats = self.collectionData.get("boats", {}) + self.particles = self.collectionData.get("particles", {}) + self.titles = self.collectionData.get("titles", {}) + def getBoatMaterial(self, index): #获取对应的船的材质 + return self.boats.getString(u"{}.material".format(index), DEFAULT_BOAT_MATERIAL) + def getBoatDisplay(self, index): #获取对应的船的显示名 + return self.boats.getString(u'{}.display'.format(index), DEFAULT_BOAT_DISPLAY) + def getParticleType(self, index): #获取对应的粒子类型 + return self.particles.getString(u'{}.type'.format(index), DEFAULT_PARTICLE_TYPE) + def getParticleInfo(self, index): #获取对应的粒子信息 + count = self.particles.getInt(u'{}.count'.format(index), 1) + rx = self.particles.getDouble(u'{}.rx'.format(index), 0.0) + ry = self.particles.getDouble(u'{}.ry'.format(index), 0.0) + rz = self.particles.getDouble(u'{}.rz'.format(index), 0.0) + speed = self.particles.getDouble(u'{}.speed'.format(index), 0.0) + oy = self.particles.getDouble(u'{}.oy'.format(index), 0.0) + return count, rx, ry, rz, speed, oy + def getParticleDisplay(self, index): #获取对应的粒子的显示名 + return self.particles.getString(u'{}.display'.format(index), DEFAULT_PARTICLE_DISPLAY) + def getParticleMaterial(self, index): #获取对应的粒子的材质 + return self.particles.getString(u'{}.material'.format(index), DEFAULT_PARTICLE_MATERIAL) + def getTitleMaterial(self, index): #获取对应的称号的材质 + return self.titles.getString(u'{}.material'.format(index), DEFAULT_TITLE_MATERIAL) + def getTitleDisplay(self, index): #获取对应的称号 + return self.titles.getString(u'{}.display'.format(index), DEFAULT_TITLE_DISPLAY) + def getAllItems(self, itemType='boats'): + if itemType == 'boats': + return self.getAllBoats() + elif itemType == 'particles': + return self.getAllParticles() + elif itemType == 'titles': + return self.getAllTitles() + else: + return None # 无效的 itemType + def getAllBoats(self): #获取所有船 + return self.boats.getKeys(False) + def getAllParticles(self): #获取所有粒子 + return self.particles.getKeys(False) + def getAllTitles(self): #获取所有称号 + return self.titles.getKeys(False) + def getCollectionCondition(self, type, index): #获取收集品价格 + itemSection = self.collectionData.get(type) + if itemSection is not None: + iteminfo = itemSection.get(str(index)) + if iteminfo is not None: + notForSale = iteminfo.get("notForSale", False) + if notForSale: + return ["notForSale", notForSale] + + price = iteminfo.get("cost", None) + if price is not None: + return ["cost", price] + + level = iteminfo.get("level", None) + if level is not None: + return ["level", level] + + totalTimes = iteminfo.get("totalTimes", None) + if totalTimes is not None: + return ["totalTimes", totalTimes] + + record = iteminfo.get("record", None) + if record is not None: + maps = list(record.getKeys(False)) + if maps is not None: + recordDict = {} + for mapNum in maps: + mapRecord = record.getDouble(u"{}.record".format(str(mapNum))) + if mapRecord is not None: + recordDict[mapNum] = mapRecord + return ["record", recordDict] + + times = iteminfo.get("times", None) + if times is not None: + maps = list(times.getKeys(False)) + if maps is not None: + timesDict = {} + for mapNum in maps: + mapTimes = times.getInt(u"{}.times".format(str(mapNum))) + if mapTimes is not None: + timesDict[mapNum] = mapTimes + return ["times", timesDict] + return ["cost", 0] # 默认类型为cost,价格为 0 + def getCollectionLore(self, type, index): #获取收集品描述 + itemSection = self.collectionData.get(type) + if itemSection is not None: + iteminfo = itemSection.get(str(index)) + if iteminfo is not None: + lore = iteminfo.get("lore", []) + if lore is not None: + return lore + return None + def getDisplay(self, itemType, itemName): #获取收集品显示名 + itemSection = self.collectionData.get(itemType) + if itemSection is not None: + iteminfo = itemSection.get(str(itemName)) + if iteminfo is not None: + display = iteminfo.get("display") + if display is not None: + return display + return itemName # 默认显示名为 itemName + def getMaterial(self, itemType, itemName): #获取收集品显示材质 + itemSection = self.collectionData.get(itemType) + if itemSection is not None: + iteminfo = itemSection.get(str(itemName)) + if iteminfo is not None: + material = iteminfo.get("material") + if material is not None: + return material + return u'BARRIER' +class PlayersManager: #全服玩家名单管理 + def __init__(self): + self.playersConfig = ps.config.loadConfig("iceboat/players.yml") + def getPlayers(self): #获取所有玩家 + self.players = list(self.playersConfig.getKeys(False)) + return self.players + def inPlayers(self, playerName): #判断玩家是否存在于全服名单 + players = self.getPlayers() #获取所有玩家 + if playerName in players: #如果玩家已存在于玩家列表 + return True + return False + def addPlayer(self, playerName): #新增玩家到全服名单 + if self.inPlayers(playerName): #如果玩家已存在于玩家列表 + return + self.playersConfig.set(playerName, {}) #新增玩家到全服名单 + self.playersConfig.save() + def addPlayerAsync(self, playerName): #异步执行新增玩家到全服名单 + ps.scheduler.runTaskAsync(lambda s=self,pN=playerName: s.addPlayer(pN)) +class SeasonManager: #季节管理 + def __init__(self): + self.seasonData = ps.config.loadConfig("iceboat/season.yml") + def getSeasonMaps(self): #获取当前季节地图列表 + self.seasonMaps = list(self.seasonData.get("maps").getKeys(False)) + return self.seasonMaps + def inSeasonMaps(self, mapNum): #判断地图是否存在于当前季节 + seasonMaps = self.getSeasonMaps() #获取当前季节地图列表 + if str(mapNum) in seasonMaps: #如果地图已存在于当前季节 + return True + return False + def getSeasonStandard(self, mapNum): + self.seasonStandard = self.seasonData.get("maps").getDouble(str(mapNum), 0.0) + return self.seasonStandard + def resetSeasonRecords(self): + playersPath = u"iceboat/players.yml" + playersConfig = ps.config.loadConfig(playersPath) + players = list(playersConfig.getKeys(False)) + for playerName in players: + playerDataPath = u"iceboat/playerData/{}.yml".format(playerName) + playerData = ps.config.loadConfig(playerDataPath) + playerData.set("info.seasonScore", 0) + recordSection = playerData.get("record", {}) + if recordSection is not None: + if hasattr(recordSection, 'getKeys'): + maps = list(recordSection.getKeys(False)) + else: + maps = list(recordSection.keys()) + print(playerName) + print(maps) + for mapNum in maps: + mapSection = recordSection.get(mapNum) + if mapSection is not None: + mapSection.set("seasonBestMapRecord", 0) + mapSection.set("seasonMapScore", 0) + playerData.save() + + seasonRankPath = u'iceboat/ranking/seasonScore.yml' + seasonRankConfig = ps.config.loadConfig(seasonRankPath) + players = list(seasonRankConfig.getKeys(False)) + players.remove("0") + for playerName in players: + seasonRankConfig.set("{}.score".format(playerName), 0.0) + seasonRankConfig.save() +class MapsManager: #全地图管理 + def __init__(self): + self.mapNumbers = [] + mapNum = 1 #从1号图开始检索 + while True: + mapFilePath = u"iceboat/maps/{}.yml".format(mapNum) + if ps.config.doesConfigExist(mapFilePath): + self.mapNumbers.append(str(mapNum)) + mapNum += 1 + else: + break + + mapNum = 2999 #从2999号图开始反着检索 + while True: + mapFilePath = u"iceboat/maps/{}.yml".format(mapNum) + if ps.config.doesConfigExist(mapFilePath): + self.mapNumbers.append(str(mapNum)) + mapNum -= 1 + else: + break + def getMapsNum(self): + return self.mapNumbers +class RoomsManager: #可用房间管理 + def __init__(self): + self.roomsData = ps.config.loadConfig(ROOMS_CONFIG_PATH) + def getAvailableRoomCount(self, mapNum): #获取指定地图的可用房间数量 + mapRooms = self.roomsData.get(str(mapNum), {}) + if hasattr(mapRooms, 'getKeys'): + keys = mapRooms.getKeys(False) # 获取所有子键 + availableRooms = sum(1 for key in keys if mapRooms.get(key) is False) + return availableRooms + def getTotalRoomCount(self, mapNum): #获取指定地图的总房间数量 + mapRooms = self.roomsData.get(str(mapNum), {}) + if hasattr(mapRooms, 'getKeys'): + keys = mapRooms.getKeys(False) # 获取所有子键 + totalRooms = len(keys) + return totalRooms + def getAvailableRoom(self, mapNum): #获取指定地图的可用房间编号 + mapRooms = self.roomsData.get(str(mapNum), {}) + if hasattr(mapRooms, 'getKeys'): + keys = mapRooms.getKeys(False) # 获取所有子键 + for key in keys: + if mapRooms.get(key) is False: + return key + return None + def setAvailableRoom(self, mapNum, roomNum, using=True): #设置指定地图的指定房间为可用 + mapRooms = self.roomsData.get(str(mapNum), {}) + if hasattr(mapRooms, 'getKeys'): + keys = mapRooms.getKeys(False) # 获取所有子键 + if roomNum in keys: + mapRooms.set(roomNum, using) + self.roomsData.save() + return True + return False + def resetAvailableRoom(self, mapNum): #重置指定地图的可用房间 + mapRooms = self.roomsData.get(str(mapNum), {}) + if hasattr(mapRooms, 'getKeys'): + keys = mapRooms.getKeys(False) # 获取所有子键 + for key in keys: + mapRooms.set(key, False) + return True + return False + def resetAvailableRooms(self): #重置所有可用房间 + self.roomsNum = self.roomsData.getKeys(False) #所有房间编号 + for mapNum in self.roomsNum: + self.resetAvailableRoom(mapNum) +class LevelManager: #驾照等级管理 + def __init__(self): + self.levelData = ps.config.loadConfig("iceboat/level.yml") + self.levels = self.levelData.getKeys(False) + def getAllLevels(self): #获取所有等级 + return self.levels + def getAvailableLevels(self, playerLevel): #获取可解锁的等级 + availableLevels = [] + for level in self.levels: + if self.getLevelNeed(level) <= playerLevel: + if self.getLevelTarget(level) > playerLevel: + availableLevels.append(level) + return availableLevels + def getLevelDisplay(self, index): #获取等级显示名 + return self.levelData.getString(u"{}.display".format(index), u'未命名') + def getLevelNeed(self, index): #获取需要达到的等级 + return self.levelData.getDouble(u"{}.need".format(index), 0.0) + def getLevelTarget(self, index): #获取目标等级 + return self.levelData.getDouble(u"{}.target".format(index), 0.0) + def getLevelMaterial(self, index): #获取等级显示材质 + return self.levelData.getString(u"{}.material".format(index), u'BARRIER') + def getLevelRecords(self, index): #获取等级成绩要求 + record = self.levelData.getConfigurationSection(u"{}.record".format(index)) + if record is not None: + maps = list(record.getKeys(False)) + if maps is not None: + recordDict = {} + for mapNum in maps: + mapRecord = record.getDouble(u"{}.record".format(str(mapNum))) + if mapRecord is not None: + recordDict[mapNum] = mapRecord + return recordDict + return None +class RankManager: #排名管理 + def __init__(self): + pass + def getSingleMapRecordRank(self, playerName, mapNum=None): #获取单人最好成绩排名 + if mapNum is None: + return 0 + config = ps.config.loadConfig("iceboat/ranking/singleMapRecord.yml") + playerData = config.get(u"{}.{}".format(mapNum, playerName)) + if playerData is not None: + mapRankData = config.get(str(mapNum)) + if mapRankData: + currentKey = playerName + rank = 0 + while currentKey != '0': + prevKey = mapRankData.get(currentKey, {}).get('prev') + if prevKey == '0': + rank += 1 + break + elif prevKey: + currentKey = prevKey + rank += 1 + return rank + return 0 + def getMultiMapRecordRank(self, playerName, mapNum=None): #获取多人最好成绩排名 + if mapNum is None: + return 0 + config = ps.config.loadConfig("iceboat/ranking/multiMapRecord.yml") + playerData = config.get(u"{}.{}".format(mapNum, playerName)) + if playerData is not None: + mapRankData = config.get(str(mapNum)) + if mapRankData: + currentKey = playerName + rank = 0 + while currentKey!= '0': + prevKey = mapRankData.get(currentKey, {}).get('prev') + if prevKey == '0': + rank += 1 + break + elif prevKey: + currentKey = prevKey + rank += 1 + return rank + return 0 + def getMapTimesRank(self, playerName, mapNum=None): #获取完成次数排名 + if mapNum is None: + return 0 + config = ps.config.loadConfig("iceboat/ranking/mapTimes.yml") + playerData = config.get(u"{}.{}".format(mapNum, playerName)) + if playerData is not None: + mapRankData = config.get(str(mapNum)) + if mapRankData: + currentKey = playerName + rank = 0 + while currentKey!= '0': + prevKey = mapRankData.get(currentKey, {}).get('prev') + if prevKey == '0': + rank += 1 + break + elif prevKey: + currentKey = prevKey + rank += 1 + return rank + return 0 + def getSeasonScoreRank(self, playerName): #获取季节分数排名 + config = ps.config.loadConfig("iceboat/ranking/seasonScore.yml") + playerData = config.get(u"{}".format(playerName)) + if playerData is not None: + currentKey = playerName + rank = 0 + while currentKey!= '0': + prevKey = config.get(currentKey, {}).get('prev') + if prevKey == '0': + rank += 1 + break + elif prevKey: + currentKey = prevKey + rank += 1 + return rank + return 0 + def getLevelRank(self, playerName): #获取等级排名 + config = ps.config.loadConfig("iceboat/ranking/level.yml") + playerData = config.get(u"{}".format(playerName)) + if playerData is not None: + currentKey = playerName + rank = 0 + while currentKey!= '0': + prevKey = config.get(currentKey, {}).get('prev') + if prevKey == '0': + rank += 1 + break + elif prevKey: + currentKey = prevKey + rank += 1 + return rank + return 0 + def getTotalMapTimesRank(self, playerName): #获取总完成次数排名 + config = ps.config.loadConfig("iceboat/ranking/totalMapTimes.yml") + playerData = config.get(u"{}".format(playerName)) + if playerData is not None: + currentKey = playerName + rank = 0 + while currentKey!= '0': + prevKey = config.get(currentKey, {}).get('prev') + if prevKey == '0': + rank += 1 + break + elif prevKey: + currentKey = prevKey + rank += 1 + return rank + return 0 + def updateRankingAsyn(self, playerName, newScore, mapNum=None, recordType=u"seasonScore"): #异步更新 + ps.scheduler.runTaskAsync(lambda: self.updateRanking(playerName, newScore, mapNum, recordType)) + def updateRanking(self, playerName, newScore, mapNum=None, recordType=u"seasonScore"): + if recordType == u"seasonScore": + filePath = u'iceboat/ranking/seasonScore.yml' + smallToBig = False + mapNum = None # 不需要地图编号 + elif recordType == u"single": + filePath = u'iceboat/ranking/singleMapRecord.yml' + smallToBig = True + elif recordType == u"multi": + filePath = u'iceboat/ranking/multiMapRecord.yml' + smallToBig = True + elif recordType == u"mapTimes": + filePath = u'iceboat/ranking/mapTimes.yml' + smallToBig = False + elif recordType == u"level": + filePath = u'iceboat/ranking/level.yml' + smallToBig = False + mapNum = None # 不需要地图编号 + elif recordType == u"totalMapTimes": + filePath = u'iceboat/ranking/totalMapTimes.yml' + smallToBig = False + mapNum = None # 不需要地图编号 + else: + return + + config = ps.config.loadConfig(filePath) + + if mapNum is None: #处理非地图类排名 + players = list(config.getKeys(False)) + + if '0' not in players: # 文件初始化 + initial = { + 'score': 0, + 'prev': '0', + 'next': '0' + } + config.set("0", initial) + players.append('0') #为玩家列表增加头指针 + config.save() + config = ps.config.loadConfig(filePath) #重新加载 + if playerName in players: # 纪录刷新 + score = config.get(u"{}.score".format(playerName)) + if smallToBig: + if newScore >= score: + return + else: + if newScore <= score: + return + config.set(u"{}.score".format(playerName), newScore) #更新成绩 + else: # 玩家数据初始化 + playerData = { + 'score': newScore, + 'prev': playerName, + 'next': playerName + } + config.set(u"{}".format(playerName), playerData) + config.save() + config = ps.config.loadConfig(filePath) #重新加载 + players.append(playerName) + + ranking = {} + for key in players: # 获取字典 + ranking[key] = config.get(key).getValues(True) + + prevPlayer = ranking[playerName]['prev'] + nextPlayer = ranking[playerName]['next'] + ranking[prevPlayer]['next'] = nextPlayer + ranking[nextPlayer]['prev'] = prevPlayer + config.set(u"{}.next".format(prevPlayer), nextPlayer) + config.set(u"{}.prev".format(nextPlayer), prevPlayer) + #从排名靠后不断往前检查 + currentPlayer = prevPlayer if prevPlayer != playerName else ranking['0']['prev'] + + # 找到合适的插入位置 + if smallToBig: + while currentPlayer != '0' and ranking[currentPlayer]['score'] > newScore: + currentPlayer = ranking[currentPlayer]['prev'] + else: + while currentPlayer!= '0' and ranking[currentPlayer]['score'] < newScore: + currentPlayer = ranking[currentPlayer]['prev'] + prevPlayer = currentPlayer + nextPlayer = ranking[currentPlayer]['next'] + config.set(u"{}.prev".format(playerName), prevPlayer) + config.set(u"{}.next".format(playerName), nextPlayer) + config.set(u"{}.next".format(prevPlayer), playerName) + config.set(u"{}.prev".format(nextPlayer), playerName) + config.save() + else: #处理地图类排名 + maps = list(config.getKeys(False)) + mapNum = str(mapNum) + if mapNum not in maps: #地图初始化 + initial = { + 'score': 0, + 'prev': '0', + 'next': '0' + } + config.set("{}.0".format(mapNum), initial) + config.save() + config = ps.config.loadConfig(filePath) #重新加载 + maps.append(mapNum) + mapSection = config.get(u"{}".format(mapNum)) + players = list(mapSection.getKeys(False)) #获取所有玩家名 + if playerName in players: # 纪录没有刷新 + score = config.get(u"{}.{}.score".format(mapNum, playerName)) + if smallToBig: + if newScore >= score: + return + else: + if newScore <= score: + return + config.set(u"{}.{}.score".format(mapNum, playerName), newScore) # 更新玩家分数 + else: + playerData = { + 'score': newScore, + 'prev': str(playerName), + 'next': str(playerName) + } + config.set(u"{}.{}".format(mapNum, str(playerName)), playerData) + config.save() + config = ps.config.loadConfig(filePath) #重新加载 + players.append(playerName) + + mapRanking = {} # 获取字典 + for key in players: + value = mapSection.get(key) + if hasattr(value, 'getValues'): + value = value.getValues(False) + mapRanking[key] = value + prevPlayer = mapRanking[playerName]['prev'] + nextPlayer = mapRanking[playerName]['next'] + mapRanking[prevPlayer]['next'] = nextPlayer + mapRanking[nextPlayer]['prev'] = prevPlayer + config.set(u"{}.{}.next".format(mapNum, prevPlayer), nextPlayer) + config.set(u"{}.{}.prev".format(mapNum, nextPlayer), prevPlayer) + currentPlayer = prevPlayer if prevPlayer != playerName else mapRanking['0']['prev'] + + + # 找到合适的插入位置 + if smallToBig: + while currentPlayer!= '0' and mapRanking[currentPlayer]['score'] > newScore: + currentPlayer = mapRanking[currentPlayer]['prev'] + else: + while currentPlayer!= '0' and mapRanking[currentPlayer]['score'] < newScore: + currentPlayer = mapRanking[currentPlayer]['prev'] + prevPlayer = currentPlayer + nextPlayer = mapRanking[currentPlayer]['next'] + config.set(u"{}.{}.prev".format(mapNum, playerName), prevPlayer) + config.set(u"{}.{}.next".format(mapNum, playerName), nextPlayer) + config.set(u"{}.{}.next".format(mapNum, prevPlayer), playerName) + config.set(u"{}.{}.prev".format(mapNum, nextPlayer), playerName) + config.save() +class HallOfFameManager: + def __init__(self): + self.fameConfig = ps.config.loadConfig("iceboat/fame.yml") + self.world = Bukkit.getWorld(WORLD_NAME) + self.armorStandEntities = [] + def updateHallOfFame(self): + self.clearHallOfFame() # 清除旧的名人堂实体 + def getList(): + config = ps.config.loadConfig("iceboat/ranking/seasonScore.yml") + if config.get('0') is None: #初始化 + config.set("0", { + 'score': 0, + 'prev': '0', + 'next': '0' + }) + config.save() + currentPlayer = config.get('0').get('next') + players = [] + for _ in range(16): + if currentPlayer == '0': + break + players.append(currentPlayer) + currentPlayer = config.get(currentPlayer).get('next') + + # 获取展示坐标 + positions = [] + for rank in range(1, len(players) + 1): + positionConfig = self.fameConfig.get(str(rank)) + if positionConfig: + position = ( + positionConfig.getDouble('x'), + positionConfig.getDouble('y'), + positionConfig.getDouble('z'), + positionConfig.getDouble('yaw'), + positionConfig.getDouble('pitch') + ) + positions.append(position) + pack = { + u'players': players, + u'positions': positions, + u'config': config, + } + return pack + + def spawnArmorStands(pack): + players = pack.get(u'players') + positions = pack.get(u'positions') + config = pack.get(u'config') + for index, playerName in enumerate(players): + if index >= len(positions): + break + + position = positions[index] + x = position[0] + y = position[1] + z = position[2] + yaw = position[3] + pitch = position[4] + loc = Location(self.world, x, y, z, yaw, pitch) + seasonScore = config.get(playerName).get('score') + playerDataManager = PlayerDataManager(playerName) + color = playerDataManager.getLevelColor() + titleDisplay = playerDataManager.getTitleDisplay() + playerHeadItem = playerHeadsAPI.getPlayerHead(playerName) + + def createArmorStand(customName): + # 创建 ArmorStand 实体 + armorStand = self.world.spawn(loc, ArmorStand) #先在底部创造 + armorStand.setGravity(False) #设置无重力 + armorStand.setVisible(False) #设置不可见 + armorStand.setInvulnerable(True) + armorStand.setCollidable(False) #设置无碰撞箱 + armorStand.setCustomNameVisible(True) #设置显示名可见 + armorStand.setCustomName(customName) + self.armorStandEntities.append(armorStand) #将展示框添加到房间 + return armorStand + + loc.add(Vector(0, 0.26, 0)) + customName = u"§a§l第 §e§l{} §a§l名".format(index+1) + createArmorStand(customName) + loc.add(Vector(0, -0.26, 0)) + customName = u"{}[{}]§f{}".format(color, titleDisplay, playerName) + createArmorStand(customName) + loc.add(Vector(0, -0.26, 0)) + customName = u"§f{:.2f} 分".format(seasonScore) + armorStand = createArmorStand(customName) + armorStand.getEquipment().setHelmet(playerHeadItem) + + ps.scheduler.runSyncCallbackTask(getList, spawnArmorStands) + def updateHallOfFameByName(self, playerName): + isInHallOfFame = hallOfFameManager.isInHallOfFame(playerName) + if isInHallOfFame: #如果玩家在名人堂则更新 + hallOfFameManager.updateHallOfFame() + def clearHallOfFame(self): + for armorStand in self.armorStandEntities: + if armorStand and not armorStand.isDead(): + armorStand.remove() + self.armorStandEntities = [] + def isInHallOfFame(self, playerName): + config = ps.config.loadConfig("iceboat/ranking/seasonScore.yml") + if config.get('0') is None: #初始化 + config.set("0", { + 'score': 0, + 'prev': '0', + 'next': '0' + }) + config.save() + + currentPlayer = config.get('0').get('next') + for _ in range(16): + if currentPlayer == '0': + break + if currentPlayer == playerName: + return True + else: + currentPlayer = config.get(currentPlayer).get('next') + return False +worldManager = WorldManager() #初始化世界管理 +collectionManager = CollectionManager() #收集品管理 +playersManager = PlayersManager() #玩家管理 +seasonManager = SeasonManager() #季节管理 +mapsManager = MapsManager() #全地图管理 +roomsManager = RoomsManager() #可用房间管理 +levelManager = LevelManager() #驾照等级管理 +rankManager = RankManager() #排名管理 +hallOfFameManager = HallOfFameManager() #名人堂管理 +guiManager = GUIManager() #菜单管理 + + +# 房间相关 ------------------------------------------------------------------- +class MultiGameRoom: #多人游戏房间类 + def __init__(self, roomId): + self.roomId = roomId + self.mapNum = 1 #默认1号地图 + self.roomNum = 1 #默认1号房 + self.map = None + self.playerCount = 0 # 房间内玩家数量 + self.finishCount = 0 # 完成玩家数量 + + self.playerList = {} # PlayerInGame对象的字典,键为玩家名称 + self.activeTasks = {} # 统一管理所有任务 + + self.rankList = [] #实时排名 + self.finishList = [] #完成列表 + self.mapDisplayArmorStands = [] # 存储该房间的盔甲架实体 + self.mapDisplayItemFrame = None # 存储该房间的展示框实体 + + self.isRunning = False #运行状态 包含Preparing和Gaming + self.isPreparing = False #准备状态 + self.isGaming = False #游戏状态 包含Starting和Ending + self.isStarting = False #开始状态 + self.isEnding = False #结束状态 + self.isClosed = False #是否关闭 + self.allFinished = False #是否所有玩家都完成 + + self.playerMapTimes = {} + self.playerTotalMapTimes = {} + # 房间管理 ---------------------------------------------- + def isEmpty(self): #判断房间是否为空 + return self.playerCount == 0 + def setIceboatMap(self, mapNum, roomNum): #设置房间地图对象 + self.mapNum = mapNum + self.roomNum = roomNum + self.map = IceboatMap(self.mapNum, self.roomNum) + def setMapNum(self, mapNum): #设置房间地图编号 + self.mapNum = int(mapNum) + def setRoomNum(self, roomNum): #设置房间房号 + self.roomNum = roomNum + def save(self): # 更新房间对象 + multiGames[self.roomId] = self + # 玩家管理 ---------------------------------------------- + def addPlayer(self, playerName): #玩家进入房间 + if self.isRunning: #若房间已开启 + return False, u"房间已启动,无法加入" + + if self.playerCount >= MAX_PLAYERS: #若房间已满 + return False, u"房间已满,无法加入" + + if playerName in self.playerList: #若玩家已经在房间内 + return False, u"你已在该房间内" + + initial = False + if self.playerCount == 0: #若房间为空的时候进行初始化 + initial = True + + self.playerList[playerName] = PlayerInGame(playerName) #创建玩家对象 + if initial: + mapNum = self.playerList[playerName].playerData.getLastMap() #获取玩家上一次玩的地图 + self.setMapNum(mapNum) #设置房间地图编号 + self.map = IceboatMap(self.mapNum, self.roomNum) #设置地图 + self.createMapDisplayItemFrame() # 地图显示功能 + + self.playerCount += 1 #玩家数量+1 + + self.createMapDisplayArmorStands() # 更新全息显示 + self.save() # 保存房间状态 + + return True, u"成功加入房间" + def delPlayer(self, playerName): #玩家在游戏中离开房间 + if playerName in self.playerList: #若玩家在房间内 + # 清理玩家关联的循环任务 + if playerName in self.activeTasks: + for taskId in self.activeTasks[playerName]: + if taskId is not None: + ps.scheduler.stopTask(taskId) + del self.activeTasks[playerName] + #玩家设置 + playerObj = self.playerList[playerName] #获取玩家对象 + if self.isRunning: #启动后才执行的内容 + playerObj.teleportToJoinLoc() #传送回进入房间前的位置 + self.broadcastMessage( + u"§b[§e{}§b] {}[{}]§f{} §a离开了房间".format( + self.map.display, playerObj.playerData.getLevelColor(), + playerObj.playerData.getTitleDisplay() ,playerObj.name + ) + ) #游戏内播报退出 + if self.isPreparing: #准备中才执行的内容 + playerObj.stopReadyParticle() #停止准备粒子效果 + playerObj.unsetInvisible() #取消隐身 + if self.isStarting: #游戏开始中才执行的内容 + playerObj.stopActionBarUpdates() #停止ActionBar更新 + playerObj.stopParticle() #停止粒子效果 + if self.isGaming: #游戏中才执行的内容 + playerObj.clearBoat() #离开时清理绑定的船 + del self.playerList[playerName] #删除玩家对象 + if not self.isClosed: # 房间未关闭时执行的内容 + self.createMapDisplayArmorStands() # 更新全息显示 + #房间设置 + self.playerCount -= 1 #玩家数量-1 + self.save() #保存房间状态 + if self.isEmpty(): #最后一个玩家离开 + self.closeRoom() #关闭房间 + def rankPlayerInGame(self): #更新房间内玩家排名 + players = list(self.playerList.values()) # 获取所有玩家对象 + # 多级排序:圈数(降) -> 检查点(降) -> 单圈时间戳() -> 名字(升) + players.sort(key=lambda p: ( + -p.lap, + -p.point, + p.passMoment, + p.name.lower() + )) + players = list(filter(lambda x: x not in self.finishList, players)) #过滤掉完成的玩家 + self.rankList = players # 存储排名好的对象列表 + for rank, player in enumerate(players, start=self.finishCount+1): # 更新未完成的玩家排名 + player.updateRank(rank) + def isPlayerInRoom(self, playerName): #判断玩家是否在房间内 + if playerName in self.playerList: + return True + return False + def settleFinishedPlayer(self, playerObj): #完成玩家结算 + self.finishCount += 1 + self.finishList.append(playerObj) + finishTime = playerObj.timer #完成时间戳 + playerObj.finishTime = finishTime + # 为所有玩家广播消息 + message = u"§b[§e{}§b] §a第 §e{} §a名 {}[{}]§f{} §a总用时 §e{:.2f} §a秒".format( + self.map.display, playerObj.rank, playerObj.playerData.getLevelColor(), + playerObj.playerData.getTitleDisplay() ,playerObj.name, finishTime) + if playerObj.broadcastMode: #如果开启了成绩广播模式 + broadcast(message) + else: + self.broadcastMessage(message) + playerObj.hasFinished = True # 标记玩家已完成 + playerObj.playSound(Sound.ENTITY_FIREWORK_ROCKET_LAUNCH, 1.0, 1.0) + playerObj.playSound(Sound.ENTITY_FIREWORK_ROCKET_TWINKLE, 1.0, 1.0) + playerObj.stopActionBarUpdates() + playerObj.stopParticle() + playerObj.finishAddVault() + record = playerObj.checkMultiRecord(self.map.mapNum) + if record: #纪录更新 + record = float(record) + playerObj.playSound(Sound.ENTITY_PLAYER_LEVELUP, 1.0, 1.0) + self.broadcastMessage( + u"§b[§e{}§b] {}[{}]§f{} §a刷新了多人纪录!§7(旧纪录:{:.2f})".format( + self.map.display, playerObj.playerData.getLevelColor(), + playerObj.playerData.getTitleDisplay() ,playerObj.name, record + ) + ) + if playerObj.detailMode: #如果玩家启用了详细模式 + playerObj.sendMessage(u"§b[§e{}§b] §a最佳纪录 §e{:.2f} §a秒 | 第 §e{} §a次完成该地图".format( + self.map.display, + playerObj.playerData.getMultiBestMapRecord(self.mapNum), + self.playerMapTimes[playerObj.name] + 1)) + if seasonManager.inSeasonMaps(self.mapNum): #如果是赛季地图 + playerObj.sendMessage(u"§b[§e{}§b] §a该地图当季分数 §e{:.2f} §a| 当季总分 §e{:.2f}".format( + self.map.display, + playerObj.playerData.getSeasonMapScore(self.mapNum), + playerObj.playerData.getSeasonScore())) + else: + playerObj.sendMessage(u"§b[§e{}§b] §a该地图不是当季地图".format(self.map.display)) + def bindPlayerInPreparingArea(self, playerObj): #绑定玩家在准备区域 + def checkTask(): + #print('Looping') + if not playerObj.inArea(self.map.getPreparingArea()): + actionBar(playerObj.player, u'§c请勿离开起点区域') + playerObj.teleport(self.map.getJoinPosition()) + taskId = ps.scheduler.scheduleRepeatingTask(checkTask, 0, 20) + self.trackTask(playerObj.name, taskId) + def updatePlayerInfo(self, playerName): #更新玩家信息 + if playerName in self.playerList: + self.playerList[playerName].updatePlayerData() #更新玩家对象数据 + self.save() #保存房间状态 + # 信息显示 ---------------------------------------------- + def createHologram(self): #创建/更新全息显示 + self.createMapDisplayArmorStands() + self.createMapDisplayItemFrame() + self.save() #保存房间状态 + def createMapDisplayArmorStands(self): #创造盔甲架显示 + config = ps.config.loadConfig(HOLOGRAME_CONFIG_PATH) + hologramLoc = config.get(str(self.roomId)) + if hologramLoc is not None: + x = hologramLoc.get("x") + y = hologramLoc.get("y") + z = hologramLoc.get("z") + if x is not None and y is not None and z is not None: + world = getServer().getWorld(WORLD_NAME) + self.clearMapDisplayArmorStands() # 清除旧的盔甲架 + sortedPlayers = sorted(self.playerList.keys(), reverse=True) # 按玩家名字首字母排序 + + # 设置名字之间的垂直偏移量 + xOffset = 2 + yOffset = 0.13 - (ARMOR_STAND_INTERVAL / 2) * (len(sortedPlayers) + 2) + zOffset = - 1.1 + + bottomLoc = Location(world, x, world.getMinHeight(), z) #预处理位置 + + # 为每个玩家创建盔甲架显示名字 + for playerName in sortedPlayers: + # 加载玩家数据 + playerData = PlayerDataManager(playerName) + customName = u"{}[{}]§f{}".format(playerData.getLevelColor(), playerData.getTitleDisplay(), playerName) + self.createArmorStand(world, bottomLoc, x + xOffset, y + yOffset, z + zOffset, customName) + yOffset += ARMOR_STAND_INTERVAL + + # 地图信息盔甲架 + customName = u"§a难度 §e{} §a圈数 §e{}".format(self.map.difficulty, self.map.lap) + self.createArmorStand(world, bottomLoc, x + xOffset, y + yOffset, z + zOffset, customName) + yOffset += ARMOR_STAND_INTERVAL + + # 地图名称盔甲架 + if self.isRunning: #房间状态判定 + roomStatus = u"§c[启动]" + else: + roomStatus = u"§a[等待]" + customName = u"§b§l【§e§l{}§b§l】{}".format(self.map.display, roomStatus) + self.createArmorStand(world, bottomLoc, x + xOffset, y + yOffset, z + zOffset, customName) + yOffset += ARMOR_STAND_INTERVAL + def createMapDisplayItemFrame(self): #创造展示框显示 + config = ps.config.loadConfig(HOLOGRAME_CONFIG_PATH) + hologramLoc = config.get(str(self.roomId)) + if hologramLoc is not None: + x = hologramLoc.get("x") + y = hologramLoc.get("y") + z = hologramLoc.get("z") + if x is not None and y is not None and z is not None: + world = getServer().getWorld(WORLD_NAME) + if self.mapDisplayItemFrame: # 删除并覆盖已有展示框 + self.mapDisplayItemFrame.remove() + displayLoc = Location(world, x - 1, y + 1, z) #最终展示框位置,偏移可修改 + bottomLoc = Location(world, x, world.getMinHeight(), z) # 预处理位置 + itemFrame = bottomLoc.getWorld().spawnEntity(bottomLoc, EntityType.ITEM_FRAME)#生成展示框 + mapItem = ItemStack(Material.MAP) + meta = mapItem.getItemMeta() + if int(self.mapNum) > int(AVAILABLE_MAPNUM): + num = 0 + else: + num = self.mapNum + customModelKey = fromString("template:map{}".format(int(num))) + meta.setItemModel(customModelKey) + mapItem.setItemMeta(meta) + itemFrame.setItem(mapItem) + itemFrame.setVisible(False) + self.mapDisplayItemFrame = itemFrame # 存储展示框实体 + + ps.scheduler.runTaskLater(lambda: itemFrame.teleport(displayLoc), 1) #延迟传送 + ps.scheduler.runTaskLater(lambda: itemFrame.setFacingDirection(BlockFace.SOUTH), 1) #调整面朝方向 + def clearHologram(self): #清理全息显示 + self.clearMapDisplayArmorStands() + self.clearMapDisplayItemFrame() + def clearMapDisplayArmorStands(self): #清理盔甲架显示 + for armorStand in self.mapDisplayArmorStands: + if armorStand and not armorStand.isDead(): + armorStand.remove() + self.mapDisplayArmorStands = [] + def clearMapDisplayItemFrame(self): #清理展示框显示 + if self.mapDisplayItemFrame: + self.mapDisplayItemFrame.remove() + self.mapDisplayItemFrame = None + # 流程管理 ---------------------------------------------- + def enterRunning(self): #尝试运行 + # 运行前检查是否有可用副本 + roomNum = roomsManager.getAvailableRoom(self.mapNum) + if roomNum is None: #无可用房间 + for playerObj in self.playerList.values(): + player = playerObj.player + if player: + player.sendTitle(u"", u"§c剩余空房不足", 5, 50, 20) + player.playSound(player, Sound.BLOCK_NOTE_BLOCK_BASS, 1, 1) + return False + + # 运行成功设置 + self.isRunning = True # 开启成功 + self.roomNum = roomNum # 更新房间号 + roomsManager.setAvailableRoom(self.mapNum, self.roomNum, True) #更新房间状态 + self.map = IceboatMap(self.mapNum, self.roomNum) # 更新地图 + self.map.initialSpecialMap() #初始化特殊地图 + self.createMapDisplayArmorStands() # 更新全息显示 + # 为房间内每个玩家对象执行 + for playerObj in self.playerList.values(): + if playerObj.player.isInsideVehicle(): #如果玩家在船内则删除船 + playerObj.player.getVehicle().remove() + playerObj.updatePlayerData() #更新玩家数据 + playerObj.updateJoinLoc() #更新玩家进入房间前的位置 + joinPos = self.map.getJoinPosition() #获取玩家进入房间的位置 + playerObj.teleport(joinPos) #传送玩家到房间内 + playerObj.updateLastMap(self.mapNum) #更新玩家上一次游玩的地图 + playerObj.setInvisible() #设置玩家隐身 + playerObj.startReadyParticle() #开始准备粒子效果 + + if playerObj.musicMode: #如果玩家启用了音乐模式 + playerObj.stopRecordSounds() #停止可能在播放的音乐 + playerObj.playIceboatMapSound(self.mapNum) + + self.playerMapTimes[playerObj.name] = playerObj.playerData.getMapTimes(self.mapNum) #获取每个玩家的完成次数 + self.playerTotalMapTimes[playerObj.name] = playerObj.playerData.getTotalMapTimes() #获取每个玩家的总完成次数 + if playerObj.detailMode: #如果玩家启用了详细模式 + self.mapManager = MapManager(self.mapNum) + playerObj.sendMessage(u"§b[§e{}§b] §a本地图由 §f{} §a等人参与制作".format(self.map.display, self.mapManager.getCreator())) + self.bindPlayerInPreparingArea(playerObj) #为玩家绑定检查点任务 + + self.enterPrepaing() #进入准备阶段 + def enterPrepaing(self): + self.isPreparing = True #进入准备阶段 + # 准备阶段设置 + self.map.replaceBlock(START_LINE_MATERIAL) # 启动线设置 + # 准备阶段流程 + def countdownTask1(): + if self.isEmpty(): # 检查房间内是否还有玩家 + self.closeRoom() #没有玩家则关闭房间 + return + self.countdownToAll(u'§f〓 5 〓', u'§7●●●●●', 1.0) + ps.scheduler.runTaskLater(countdownTask2, 20) + def countdownTask2(): + if self.isEmpty(): + self.closeRoom() + return + self.countdownToAll(u'§a〓 4 〓', u'§c●§7●●●●', 1.0) + ps.scheduler.runTaskLater(countdownTask3, 20) + def countdownTask3(): + if self.isEmpty(): + self.closeRoom() + return + self.countdownToAll(u'§b〓 3 〓', u'§c●●§7●●●', 1.0) + ps.scheduler.runTaskLater(countdownTask4, 20) + def countdownTask4(): + if self.isEmpty(): + self.closeRoom() + return + self.countdownToAll(u'§d〓 2 〓', u'§c●●●§7●●', 1.0) + ps.scheduler.runTaskLater(countdownTask5, 20) + def countdownTask5(): + if self.isEmpty(): + self.closeRoom() + return + self.countdownToAll(u'§e〓 1 〓', u'§c●●●●§7●', 1.0) + ps.scheduler.runTaskLater(countdownTask6, 20) + def countdownTask6(): + if self.isEmpty(): + self.closeRoom() + return + self.countdownToAll(u'§c§l〓 GO 〓', u'§a●●●●●', 2.0) + self.isPreparing = False #准备阶段结束 + for playerObj in self.playerList.values(): #获取玩家对象 + playerObj.stopReadyParticle() #结束准备粒子展示 + + self.enterStarting() + # 准备阶段开始 + ps.scheduler.runTaskLater(countdownTask1, 1) + def enterStarting(self): # 进入开始阶段 包括Starting和Gaming + self.isStarting = True # 进入开始阶段 + # 开始阶段设置 + for key in list(self.activeTasks.keys()): #清理非系统任务 + if key != 'system': + self.clearTasks(key) + # 各玩家设置 + for playerObj in self.playerList.values(): #获取玩家对象 + playerObj.spawnAndRideBoat() #生成船并绑定 + playerObj.setStartMoment() #设置开始时间戳 + playerObj.startActionBarUpdates(False) + playerObj.startParticle() #开始粒子展示 + # 开始阶段设置 + self.map.replaceBlock(Material.TRIPWIRE) # 启动线移除 + self.isGaming = True # 进入游戏阶段 + self.rankPlayerInGame() # 更新排名 + self.trackTask('system', ps.scheduler.scheduleRepeatingTask(lambda: self.updateGamingState(), 0, 1)) + def updateGamingState(self): # 游戏阶段每游戏刻更新状态 + #print('Looping') # 调试启用,用于检测循环任务是否停止 + if self.isEmpty(): #如果房间为空则关闭房间 + self.closeRoom() + return + + # 循环检测玩家状态 + self.allFinished = True #默认所有玩家都完成 + for playerObj in self.playerList.values(): + if not playerObj.inVehicle(): #如果玩家不在载具内 + self.delPlayer(playerObj.name) #移出玩家 + if self.isEnding: #若进入结束阶段则跳过 + return + + # 检查点位进度 + if not playerObj.hasFinished: #玩家还没完成 + self.allFinished = False #存在未完成玩家 + mapNum = self.map.mapNum #获取地图编号 + pointArea = self.map.getPointArea(playerObj.point) #获取玩家当前检查点区域 + if playerObj.arrivePoint(pointArea): #玩家到达检查点 + self.rankPlayerInGame() #通过检查点时更新全局排名 + playerObj.updateTimer() #更新玩家时间戳 + if playerObj.point >= self.map.point: #完成一圈 + playerObj.addLap() #圈数增加 + playerObj.resetPoint() #重置检查点 + if playerObj.lap <= self.map.lap: #未完成所有圈的情况 + playerObj.addLapTitle() + playerObj.addLapSound() + else: + self.settleFinishedPlayer(playerObj) #对完成玩家的结算 + playerObj.setPointsTimer() #设置玩家检查点时间戳 + playerObj.updateMultiBestMapRecordPointTimer(mapNum, playerObj.lap, playerObj.point) #更新玩家多人最好成绩点位 + playerObj.updateDValueText() + playerObj.showMultiTimerInActionBar() #更新玩家动态栏显示 + if playerObj.detailMode: #详细模式 + if playerObj.lap <= self.map.lap: + playerObj.showTimerByMessage(self.map.display) #显示详细信息 + + if self.allFinished: #所有玩家完成则在3秒后关闭房间 + self.isStarting = False #结束启动状态 + self.enterEnding() #进入结束阶段 + def enterEnding(self): #游戏结束阶段 + self.isEnding = True + ps.scheduler.runTaskLater(lambda: self.closeRoom(), ENDING_INTERVAL) #阶段 + def closeRoom(self): #关闭房间操作 + if self.isClosed: #不重复关闭 + return + self.isClosed = True + roomId = self.roomId + players = list(self.playerList.keys()) + + self.clearHologram() #清理全息显示 + roomsManager.setAvailableRoom(self.mapNum, self.roomNum, False) #房间可用 + for playerObj in self.playerList.values(): + self.delPlayer(playerObj.name) #执行玩家退出 + + for playerName, info in playerPage.items(): #关闭打开中的房间地图选择菜单 #待更新 + if "roomId" in info and info["roomId"] == self.roomId: + player = getServer().getPlayer(playerName) + if player and player.getOpenInventory().getTitle().startswith(u"§0多人模式地图选择 - 房间 {} - ".format(self.roomId)): + player.closeInventory() + + self.clearTasks('system')# 清理系统任务 + self.isRunning = False + self.isGaming = False + self.isEnding = False + + del multiGames[roomId] #删除房间 + + multiGames[roomId] = MultiGameRoom(roomId) #重新创建房间 + for playerName in players: #将玩家重新添加 + multiGames[roomId].addPlayer(playerName) + # 任务管理 -------------------------------- + def trackTask(self, owner, taskId): #仅循环任务需要任务管理 + if owner not in self.activeTasks: + self.activeTasks[owner] = [] + self.activeTasks[owner].append(taskId) + def clearTasks(self, owner): #停止指定循环任务 + if owner in self.activeTasks: + taskIds = list(self.activeTasks[owner]) + for taskId in taskIds: + if taskId is not None: + ps.scheduler.stopTask(taskId) + del self.activeTasks[owner] + # 辅助方法 -------------------------------- + def countdownToAll(self, title, subtitle, pitch): #为所有人播放倒计时反馈 + for playerObj in self.playerList.values(): + playerObj.sendTitle(title, subtitle, 0, 20, 10) + playerObj.playSound(Sound.BLOCK_NOTE_BLOCK_PLING, 1.0, pitch) + def broadcastMessage(self, message): #游戏内广播信息 + for playerObj in self.playerList.values(): + playerObj.player.sendMessage(message) + def createArmorStand(self, world, bottomLoc, x, y, z, customName): #生成盔甲架 + armorStand = world.spawn(bottomLoc, ArmorStand) #先在底部创造 + armorStand.setGravity(False) #设置无重力 + armorStand.setVisible(False) #设置不可见 + armorStand.setCollidable(False) #设置无碰撞箱 + armorStand.setCustomName(customName) #设置显示名 + armorStand.setCustomNameVisible(True) #设置显示名可见 + displayLoc = Location(world, x, y, z) #设置展示位置 + armorStand.teleport(displayLoc) #传送展示框 + self.mapDisplayArmorStands.append(armorStand) #将展示框添加到房间 +class SingleGameRoom: #单人游戏房间类 + def __init__(self, playerName, mapNum, roomNum): #玩家名:单人房间实例 + self.playerName = playerName + self.player = getServer().getPlayer(playerName) + self.playerObj = PlayerInGame(playerName) + + self.activeTasks = {} + + self.mapNum = mapNum + self.roomNum = roomNum + self.map = IceboatMap(self.mapNum, self.roomNum) + self.map.initialSpecialMap() #初始化特殊地图 + + self.isRunning = False + self.isPreparing = False + self.isStarting = False + self.isClosed = False + + self.mapManager = MapManager(self.mapNum) + # 房间管理 -------------------------------------------------- + def save(self): #更新状态 + singleGames[self.playerName] = self + # 玩家管理 -------------------------------------------------- + def playerLeave(self): #玩家离开 + # 清理玩家任务 + if hasattr(self, 'activeTasks') and self.playerName in self.activeTasks: + for taskId in self.activeTasks[self.playerName]: + if taskId is not None: + ps.scheduler.stopTask(taskId) + del self.activeTasks[self.playerName] + # 清理玩家自身状态并执行操作 + playerObj = self.playerObj + if self.isRunning: + playerObj.teleportToJoinLoc() #传送回进入房间前的位置 + if self.isStarting: + playerObj.clearBoat() #离开时清理绑定的船 + playerObj.stopActionBarUpdates() + playerObj.stopParticle() + self.save() + # 流程管理 -------------------------------------------------- + def enterRunning(self): #游戏初始化阶段 + self.isRunning = True + if self.player.isInsideVehicle(): #如果玩家在船内则删除船 + self.player.getVehicle().remove() + self.playerObj.updatePlayerData() #更新玩家数据 + self.playerObj.updateJoinLoc() #更新玩家进入房间前的位置 + joinPos = self.map.getJoinPosition() #获取玩家进入房间的位置 + self.playerObj.teleport(joinPos) #传送玩家到房间内 + + self.playerObj.updateLastMap(self.map.mapNum) #更新玩家上一次游玩的地图 + if self.playerObj.musicMode: #如果玩家启用了音乐模式 + self.playerObj.stopRecordSounds() #停止可能在播放的音乐 + self.playerObj.playIceboatMapSound(self.map.mapNum) + + if self.playerObj.detailMode: #如果玩家启用了详细模式 + self.playerObj.sendMessage(u"§b[§e{}§b] §a本地图由 §f{} §a等人参与制作".format(self.map.display, self.mapManager.getCreator())) + self.bindPlayerInPreparingArea() #为玩家绑定检查点任务 + + self.enterPreparing() #进入准备阶段 + def enterPreparing(self): + self.isPreparing = True #进入准备阶段 + # 准备阶段设置 + self.map.replaceBlock(START_LINE_MATERIAL) # 启动线设置 + # 准备阶段流程 + def countdownTask1(): + if self.playerName in singleGames: + self.countdownToPlayer(u'§f〓 5 〓', u'§7●●●●●', 1.0) + ps.scheduler.runTaskLater(countdownTask2, 20) + def countdownTask2(): + if self.playerName in singleGames: + self.countdownToPlayer(u'§a〓 4 〓', u'§c●§7●●●●',1.0) + ps.scheduler.runTaskLater(countdownTask3, 20) + def countdownTask3(): + if self.playerName in singleGames: + self.countdownToPlayer(u'§b〓 3 〓', u'§c●●§7●●●', 1.0) + ps.scheduler.runTaskLater(countdownTask4, 20) + def countdownTask4(): + if self.playerName in singleGames: + self.countdownToPlayer(u'§d〓 2 〓', u'§c●●●§7●●', 1.0) + ps.scheduler.runTaskLater(countdownTask5, 20) + def countdownTask5(): + if self.playerName in singleGames: + self.countdownToPlayer(u'§e〓 1 〓', u'§c●●●●§7●', 1.0) + ps.scheduler.runTaskLater(countdownTask6, 20) + def countdownTask6(): + if self.playerName in singleGames: + self.countdownToPlayer(u'§c§lGO', u'§a●●●●●', 2.0) + self.isPreparing = False + self.enterStarting() + # 准备阶段开始 + ps.scheduler.runTaskLater(countdownTask1, 1) # 待更新 + def enterStarting(self): #开始阶段 + self.isStarting = True + # 开始阶段设置 + self.clearTasks(self.playerObj.name) #清理玩家任务:循环检测区域 + self.playerObj.spawnAndRideBoat() #生成船并绑定 + self.playerObj.setStartMoment() #设置开始时间戳 + self.playerObj.startActionBarUpdates(True) + self.playerObj.startParticle() #开始粒子展示 + self.map.replaceBlock(Material.TRIPWIRE) #启动线移除 + self.trackTask('system', ps.scheduler.scheduleRepeatingTask(lambda: self.updateGamingState(), 0, 1)) + def updateGamingState(self): + #print('Looping') + if not self.playerObj.isOnline(): #玩家不在线则关闭房间 + self.closeRoom() + return + if not self.playerObj.inVehicle(): #玩家不在载具内则关闭房间 + self.closeRoom() + return + mapNum = self.map.mapNum + pointArea = self.map.getPointArea(self.playerObj.point) + if self.playerObj.arrivePoint(pointArea): + self.playerObj.updateTimer() + if self.playerObj.point >= self.map.point: + self.playerObj.addLap() + self.playerObj.resetPoint() + if self.playerObj.lap <= self.map.lap: + self.playerObj.addLapTitle() #没有到最后一圈则展现标题 + self.playerObj.addLapSound() #没有到最后一圈则播放音效 + else: + self.enterEnding() + self.playerObj.setPointsTimer() + self.playerObj.updateSingleBestMapRecordPointTimer(mapNum, self.playerObj.lap, self.playerObj.point) #更新玩家单人最好成绩点位 + self.playerObj.updateDValueText() #更新D值文本 + self.playerObj.showSingleTimerInActionBar() + if self.playerObj.detailMode: #详细模式下显示详细信息 + if self.playerObj.lap <= self.map.lap: #仅在未完成前播报 + self.playerObj.showTimerByMessage(self.map.display) + def enterEnding(self): + finishTime = self.playerObj.timer + self.playerObj.finishTime = finishTime + message = u"§b[§e{}§b] {}[{}]§f{} §a总用时 §e{:.2f} §a秒".format( + self.map.display, self.playerObj.playerData.getLevelColor(), self.playerObj.playerData.getTitleDisplay(), self.playerObj.name, finishTime) + if self.playerObj.broadcastMode: #如果开启了成绩广播模式 + broadcast(message) + else: + self.playerObj.sendMessage(message) + self.playerObj.hasFinished = True # 标记玩家已完成 + self.playerObj.playSound(Sound.ENTITY_FIREWORK_ROCKET_LAUNCH, 1.0, 1.0) + self.playerObj.playSound(Sound.ENTITY_FIREWORK_ROCKET_TWINKLE, 1.0, 1.0) + self.playerObj.stopActionBarUpdates() + self.playerObj.stopParticle() + self.playerObj.finishAddVault() + record = self.playerObj.checkSingleRecord(self.map.mapNum) + if record: #纪录更新 + record = float(record) + self.playerObj.playSound(Sound.ENTITY_PLAYER_LEVELUP, 1.0, 1.0) + self.playerObj.sendMessage( + u"§b[§e{}§b] {}[{}]§f{} §a刷新了个人纪录!§7(旧纪录:{:.2f})".format( + self.map.display, self.playerObj.playerData.getLevelColor(), self.playerObj.playerData.getTitleDisplay(), self.playerObj.name, record + ) + ) + if self.playerObj.detailMode: #如果玩家启用了详细模式 + self.playerObj.sendMessage(u"§b[§e{}§b] §a最佳纪录 §e{:.2f} §a秒 | 第 §e{} §a次完成该地图".format( + self.map.display, + self.playerObj.playerData.getSingleBestMapRecord(self.mapNum), + self.playerObj.playerData.getMapTimes(self.mapNum))) + if seasonManager.inSeasonMaps(self.mapNum): #如果是赛季地图 + self.playerObj.sendMessage(u"§b[§e{}§b] §a该地图当季分数 §e{:.2f} §a| 当季总分 §e{:.2f}".format( + self.map.display, + self.playerObj.playerData.getSeasonMapScore(self.mapNum), + self.playerObj.playerData.getSeasonScore())) + else: + self.playerObj.sendMessage(u"§b[§e{}§b] §a该地图不是当季地图".format(self.map.display)) + self.closeRoom() + def closeRoom(self): #游戏结束 + if self.isClosed: #不重复关闭 + return + self.isClosed = True + roomsManager.setAvailableRoom(self.mapNum, self.roomNum, False) + self.playerLeave() + self.clearTasks('system') # 清理系统任务 + self.isStarting = False + self.isRunning = False + del singleGames[self.playerName] + # 任务管理 -------------------------------- + def trackTask(self, owner, taskId): + if not hasattr(self, 'activeTasks'): + self.activeTasks = {} + if owner not in self.activeTasks: + self.activeTasks[owner] = [] + self.activeTasks[owner].append(taskId) + def clearTasks(self, owner): + if hasattr(self, 'activeTasks') and owner in self.activeTasks: + taskIds = list(self.activeTasks[owner]) + for taskId in taskIds: + if taskId is not None: + ps.scheduler.stopTask(taskId) + del self.activeTasks[owner] + # 辅助方法 -------------------------------- + def countdownToPlayer(self, title, subtitle, pitch): + player = self.playerObj.player + player.sendTitle(title, subtitle, 0, 20, 10) + player.playSound(player, Sound.BLOCK_NOTE_BLOCK_PLING, 1.0, pitch) + def bindPlayerInPreparingArea(self): #准备阶段循环检测 + def checkTask(): + #print('Looping') + if not self.playerObj.inArea(self.map.getPreparingArea()): + actionBar(self.playerObj.player, u'§c请勿离开起点区域') + self.playerObj.teleport(self.map.getJoinPosition()) + + taskId = ps.scheduler.scheduleRepeatingTask(checkTask, 0, 20) + self.trackTask(self.playerObj.name, taskId) +class PlayerInGame: #游戏内玩家类 + def __init__(self, playerName): + self.name = playerName + self.player = Bukkit.getPlayer(playerName) + self.world = getServer().getWorld(WORLD_NAME) + self.boat = None #玩家绑定的船实体 + # 计时相关 + self.point = 0 # 当前通过检查点 + self.lap = 1 # 当前圈数 + self.timer = 0 #个人计时器 + self.rank = 1 #当前排名 + self.startMoment = 0 # 游戏开始时间戳 + self.passMoment = 0 # 通过点位时间戳 + self.lastMoment = 0 # 上一点位时间戳 + self.hasFinished = False + self.pointsTimer = {} #圈数对应记录点 [lap] point : Timer + self.bestMapRecordPointTimer = 0 #点位最佳用时 + self.dValueText = '' + # 任务 + self.actionBarTaskId = None # 动态栏任务ID + self.particleTaskId = None # 船粒子任务ID + self.readyParticleTaskId = None #开局粒子任务ID + # 玩家配置文件 + self.playerData = PlayerDataManager(playerName) + self.updatePlayerData() + # 信息反馈 -------------------------------- + def playSound(self, sound, volume, pitch): #为自己播放音效 + self.player.playSound(self.player, sound, volume, pitch) + def sendTitle(self, title, subtitle, fadeIn, stay, fadeOut): #为自己发送标题 + self.player.sendTitle(title, subtitle, fadeIn, stay, fadeOut) + def sendMessage(self, message): #为自己发送消息 + self.player.sendMessage(message) + def addLapSound(self): #完成一圈后播放音效 + self.playSound(Sound.BLOCK_NOTE_BLOCK_PLING, 1.0, 2.0) + def addLapTitle(self): #完成一圈后显示标题 + self.sendTitle(u'§f', u'§a进入第 §e{} §a圈'.format(self.lap), 0, 30, 10) + def spawnBoatParticles(self): #生成船的粒子 + #print('Looping') + if self.particleMode: #自定义粒子模式 + leftLoc = self.getRelativeOffset(0.5, -1.0) + rightLoc = self.getRelativeOffset(-0.5, -1.0) + # 直接生成左右两边的粒子(完全对应原版命令) + worldManager.getWorld().spawnParticle( + Particle.valueOf(self.particleType), # 粒子类型 + leftLoc.add(Vector(0, self.particleInfo[5], 0)), # 相对坐标 + self.particleInfo[0], # 粒子数量 + self.particleInfo[1], # 偏移量x + self.particleInfo[2], # 偏移量y + self.particleInfo[3], # 偏移量z + self.particleInfo[4], # 速度 + None, # 数据 + True # force模式 + ) + worldManager.getWorld().spawnParticle( + Particle.valueOf(self.particleType), + rightLoc.add(Vector(0, self.particleInfo[5], 0)), # 相对坐标 + self.particleInfo[0], # 粒子数量 + self.particleInfo[1], # 偏移量x + self.particleInfo[2], # 偏移量y + self.particleInfo[3], # 偏移量z + self.particleInfo[4], # 速度 + None, # 数据 + True + ) + return True + else: #单粒子模式 + worldManager.getWorld().spawnParticle( + Particle.DRAGON_BREATH, + self.boat.getLocation().add(Vector(0, 0.1, 0)), + 0,0,0,0,0, + None, # 数据 + True # force模式 + ) + def spawnPlayerParticles(self): #生成玩家开局展示粒子 + #print('Looping') + self.player.getWorld().spawnParticle( + Particle.valueOf(DEFAULT_PARTICLE_TYPE), + self.player.getLocation().add(Vector(0, 0.1, 0)), + 1, # 粒子数量 + 0, 0, 0, # 偏移量 + 0, # 速度 + None, # 数据 + True # force模式 + ) + def playIceboatMapSound(self, mapNum): #音乐模式播放自定义音乐 + if int(mapNum) > (AVAILABLE_MAPNUM): + mapNum = 0 + soundName = u"iceboatmap{}".format(mapNum) + self.player.playSound(self.player, soundName, SoundCategory.RECORDS, 1.0, 1.0) + def stopRecordSounds(self): #停止自定义音乐播放 + self.player.stopSound(SoundCategory.RECORDS) + def showSingleTimerInActionBar(self): #在动态栏为自己显示单人时间信息 + #print('Looping') + text = u"§fPoint {}-{} {} §f| 用时 §e§l{:.2f} §f秒".format( + self.lap, self.point, self.dValueText, self.timer) + actionBar(self.player, text) + def showMultiTimerInActionBar(self): #在动态栏为自己显示多人时间信息 + #print('Looping') + text = u"§fPoint {}-{} {} §f| 排名 §b§l{} §f| 用时 §e§l{:.2f} §f秒".format( + self.lap, self.point, self.dValueText, self.rank, self.timer) + actionBar(self.player, text) + def showTimerByMessage(self, mapDisplay): #在聊天框为自己显示时间信息 详细信息模式 + interval = round((self.passMoment - self.lastMoment) * 20) / 20 + self.player.sendMessage(u"§b[§e{}§b] §fPoint {}-{} 用时 §e{:.2f} §f秒 {} §7(点间用时 {:.2f} 秒)".format( + mapDisplay, self.lap, self.point, self.timer, self.dValueText, interval)) + # 信息判断 -------------------------------- + def inVehicle(self): #判断玩家是否在载具内 + if self.player.isInsideVehicle(): + return True + return False + def isOnline(self): #判断玩家是否在线 + return self.player.isOnline() + def inArea(self, area): #判断玩家是否在指定区域 + loc = self.player.getLocation() + x = loc.getX() + y = loc.getY() + z = loc.getZ() + x1, y1, z1, x2, y2, z2 = area + if (x1 <= x <= x2 and + y1 <= y <= y2 and + z1 <= z <= z2): + return True + return False + # 信息更新 -------------------------------- + def arrivePoint(self, pointArea): #通过检查点 + if self.inArea(pointArea): #是否在点位区域 + if self.lastMoment < self.passMoment: #保存上一点位时间 + self.lastMoment = self.passMoment + self.passMoment = time.time() # 记录新通过点位时间 + self.point += 1 + return True + return False + def resetPoint(self): #重置检查点 + self.point = 0 + def addLap(self): #玩家完成一圈 + self.lap += 1 + def updateTimer(self): #设置计时器时间 + self.timer = round((self.passMoment - self.startMoment) * 20) / 20 + def updateRank(self, rank): #更新排名 + self.rank = rank + def updateJoinLoc(self): #更新玩家进入房间前位置到配置和实例 + loc = self.player.getLocation() + x = loc.getX() + y = loc.getY() + z = loc.getZ() + self.joinLoc = x, y, z + self.playerData.setJoinLoc(x, y, z) + def setInGame(self, score=1): #在记分板中标记玩家在游戏中,1为在,0为不在 + playerName = self.player.getName() + objective = getScoreboardObjective(u'InGame') + playerScore = objective.getScore(playerName) #获取玩家分数 + playerScore.setScore(score) #设置玩家分数 + def checkMultiRecord(self, mapNum): #检测多人纪录并更新完成次数 + self.newRecord = round(self.timer, 2) #记录新纪录 + totalMapTimes = self.playerData.getTotalMapTimes() #获取完成总数 + mapTimes = self.playerData.getMapTimes(mapNum) #获取地图完成次数 + rankManager.updateRankingAsyn(self.name, totalMapTimes + 1, None, u"totalMapTimes") #更新总完成次数排行榜 + rankManager.updateRankingAsyn(self.name, mapTimes + 1, mapNum, u"mapTimes") #更新地图完成次数排行榜 + self.playerData.setTotalMapTimes(totalMapTimes + 1) #更新总完成次数 + self.playerData.setMapTimes(mapNum, mapTimes + 1) #更新地图完成次数 + multiBestMapRecord = self.playerData.getMultiBestMapRecord(mapNum) #获取最好成绩 + + self.updateSeasonBestMapRecord(mapNum, self.newRecord) #更新赛季最好纪录 + + def updateMultiBestMapRecordData(): #更新最好成绩 + multiBestMapRecord = self.newRecord #刷新最佳纪录 + multiBestMapRecordTimestamp = time.time() #记录多人最好成绩获得时间 + self.playerData.setMultiBestMapRecord(mapNum, multiBestMapRecord) #更新最好成绩 + self.playerData.setMultiBestMapRecordTimestamp(mapNum, multiBestMapRecordTimestamp) #更新最好成绩时间戳 + self.playerData.setMultiBestMapRecordPointsTimer(mapNum, self.pointsTimer) #更新最好成绩时间戳 + rankManager.updateRankingAsyn(self.name, self.newRecord, mapNum, u"multi") #更新地图多人纪录排行榜 + + if multiBestMapRecord == 0: #如果尚未在多人模式玩过该地图 + updateMultiBestMapRecordData() + return '0' + else: #有过该地图多人记录 + oldRecord = multiBestMapRecord + if oldRecord > self.newRecord: #刷新记录 + updateMultiBestMapRecordData() + return oldRecord #刷新记录返回旧记录 + return False #未刷新记录返回False + def checkSingleRecord(self, mapNum): + self.newRecord = round(self.timer, 2) #记录新纪录 + totalMapTimes = self.playerData.getTotalMapTimes() #获取完成总数 + mapTimes = self.playerData.getMapTimes(mapNum) #获取地图完成次数 + rankManager.updateRankingAsyn(self.name, totalMapTimes + 1, None, u"totalMapTimes") #更新总完成次数排行榜 + rankManager.updateRankingAsyn(self.name, mapTimes + 1, mapNum, u"mapTimes") #更新地图完成次数排行榜 + self.playerData.setTotalMapTimes(totalMapTimes + 1) #更新总完成次数 + self.playerData.setMapTimes(mapNum, mapTimes + 1) #更新地图完成次数 + singleBestMapRecord = self.playerData.getSingleBestMapRecord(mapNum) #获取最好成绩 + + self.updateSeasonBestMapRecord(mapNum, self.newRecord) #更新赛季最好纪录 + + def updatesingleBestMapRecordData(): #更新最好成绩 + singleBestMapRecord = self.newRecord #刷新最佳纪录 + singleBestMapRecordTimestamp = time.time() #记录多人最好成绩获得时间 + self.playerData.setSingleBestMapRecord(mapNum, singleBestMapRecord) #更新最好成绩 + self.playerData.setSingleBestMapRecordTimestamp(mapNum, singleBestMapRecordTimestamp) #更新最好成绩时间戳 + self.playerData.setSingleBestMapRecordPointsTimer(mapNum, self.pointsTimer) #更新最好成绩时间戳 + rankManager.updateRankingAsyn(self.name, self.newRecord, mapNum, u"single") #更新地图多人纪录排行榜 + + if singleBestMapRecord == 0: #如果尚未在多人模式玩过该地图 + updatesingleBestMapRecordData() + return '0' + else: #有过该地图多人记录 + oldRecord = singleBestMapRecord + if oldRecord > self.newRecord: #刷新记录 + updatesingleBestMapRecordData() + return oldRecord #刷新记录返回旧记录 + return False #未刷新记录返回False + def updateSeasonBestMapRecord(self, mapNum, newRecord): #更新赛季最好纪录(多人/单人 + seasonBestMapRecord = self.playerData.getSeasonBestMapRecord(mapNum) #获取最好成绩 + if seasonBestMapRecord == 0: #如果尚未在赛季模式玩过该地图 + self.playerData.setSeasonBestMapRecord(mapNum, newRecord) #更新最好成绩 + else: + oldRecord = seasonBestMapRecord + if oldRecord > newRecord: #刷新记录 + self.playerData.setSeasonBestMapRecord(mapNum, newRecord) #更新最好成绩 + else: + return False #未刷新记录则不更新纪录分数 + + seasonMaps = seasonManager.getSeasonMaps() + if str(mapNum) in seasonMaps: #如果该地图在赛季中 + scoreStandard = seasonManager.getSeasonStandard(mapNum) + score = 100.0 - (float(newRecord) - float(scoreStandard)) + if score > 0: + seasonMapScore = round(score, 2) + else: + seasonMapScore = 0.0 + self.playerData.setSeasonMapScore(mapNum, seasonMapScore) #更新地图当季分数 + def updatePlayerData(self): #更新玩家可设置的配置数据 + self.playerData = PlayerDataManager(self.name) #重新读取 + self.joinLoc = self.playerData.getJoinLoc() + self.level = self.playerData.getLevel() + self.boatMaterial = self.playerData.getBoatMaterial() + self.particleType = self.playerData.getParticleType() + self.particleInfo = self.playerData.getParticleInfo() + self.titleDisplay = self.playerData.getTitleDisplay() + self.particleMode = self.playerData.getParticleMode() + self.musicMode = self.playerData.getMusicMode() + self.detailMode = self.playerData.getDetailMode() + self.broadcastMode = self.playerData.getBroadcastMode() + def updateSingleBestMapRecordPointTimer(self, mapNum, lap, point): #更新单人最佳纪录时间 + self.bestMapRecordPointTimer = self.playerData.getSingleBestMapRecordPointTimer(mapNum, lap, point) + def updateMultiBestMapRecordPointTimer(self, mapNum, lap, point): #更新多人最佳纪录时间 + self.bestMapRecordPointTimer = self.playerData.getMultiBestMapRecordPointTimer(mapNum, lap, point) + def updateDValueText(self): #更新与最佳纪录的差值 + dValue = self.timer - self.bestMapRecordPointTimer #获取与最佳纪录的差值 + value = abs(dValue) #取绝对值 + if dValue < 0: + color = u'§a' + mark = u'-' + else: + color = u'§c' + mark = u'+' + self.dValueText = u"{}[{}{:.2f}]".format(color, mark, value) + def updateLastMap(self, mapNum): #更新上一次游玩的地图 + self.playerData.setLastMap(mapNum) + # 玩家操作 -------------------------------- + def teleport(self, position): #传送玩家到指定坐标 + if self.isOnline(): #如果玩家在线 + playerLoc = self.player.getLocation() + loc = Location( + self.world, + position[0], + position[1], + position[2], + position[3], + position[4] + ) + self.player.teleport(loc) + self.setInGame(1) #标记进入游戏 + def teleportToJoinLoc(self): #传送玩家到进入房间前的位置 + if self.isOnline(): #如果玩家在线 + playerLoc = self.player.getLocation() + loc = Location( + self.world, + self.joinLoc[0], + self.joinLoc[1], + self.joinLoc[2], + playerLoc.getYaw(), + playerLoc.getPitch() + ) + self.player.teleport(loc) + self.setInGame(0) #标记离开游戏 + def spawnAndRideBoat(self): #根据玩家自己的船的类型自动上船 + loc = self.player.getLocation() + boat = self.world.spawnEntity(loc, EntityType.valueOf(self.boatMaterial.upper())) #生成船 + boat.addPassenger(self.player) #自动骑乘 + self.boat = boat + def clearBoat(self): #清理与自己绑定了的船 + if self.boat: #如果有绑定船 + if not self.boat.isDead(): #如果船还存在 + self.boat.remove() #清除船 + self.boat = None + def setInvisible(self): #让自己隐身(5秒) + self.player.addPotionEffect(PotionEffect( + PotionEffectType.INVISIBILITY, + 100, + 0, + False, #显示粒子 + False #显示图标 + )) + def unsetInvisible(self): #解除自己隐身 + self.player.removePotionEffect(PotionEffectType.INVISIBILITY) + def finishAddVault(self, vault=REWARD_AMOUNT, mag=1.0): #完成游戏并增加Vault,默认奖励金额 + if vaultEconomy: + if self.timer <= 180: #如果小于180秒则百分比减少 + vault = round(vault * (self.timer / 180), 1) + vault = round(vault * mag, 1) + vaultEconomy.depositPlayer(self.player, vault) + self.player.sendTitle(u'§a§l完成', u'§f获得 §e{:.1f} §fDC币'.format(vault), 0, 50, 10) + else: + self.player.sendTitle(u'§a§l完成', u'§f', 0, 50, 10) + # 时间操作 -------------------------------- + def setStartMoment(self): #设置游戏开始时间戳 + self.startMoment = time.time() + self.lastMoment = self.startMoment #设置上一点位时间戳为开始时间戳 + def setPassMoment(self): #设置通过点位时间戳 + self.passMoment = time.time() + def setLastMoment(self): #设置上一点位时间戳 + self.lastMoment = time.time() + def setPointsTimer(self): #设置玩家通过检查点的时间戳 + if self.lap not in self.pointsTimer: + self.pointsTimer[self.lap] = {} + self.pointsTimer[self.lap][self.point] = self.timer + # 辅助方法 -------------------------------- + def getRelativeOffset(self, dx=0, dz=0): #获取相对偏移坐标 + direction = self.boat.getLocation().getDirection().normalize().clone() + directionX = self.boat.getLocation().getDirection().normalize().clone() + right = directionX.crossProduct(Vector(0, 1, 0)).normalize() # 计算垂直于方向的右侧向量 + offset = (right.multiply(dx) # 水平偏移 + .add(direction.multiply(dz))) # 前后偏移 + offset.setY(0.1) #粒子固定抬升0.1 + return self.boat.getLocation().add(offset) + # 循环任务 -------------------------------- + def startActionBarUpdates(self, isSingle=False): #开始玩家动态栏显示 + if isSingle: + self.actionBarTaskId = ps.scheduler.scheduleRepeatingTask( + lambda: self.showSingleTimerInActionBar(), + 0, # 延迟 + 20 # 间隔(ticks) + ) + else: + self.actionBarTaskId = ps.scheduler.scheduleRepeatingTask( + lambda: self.showMultiTimerInActionBar(), + 0, # 延迟 + 20 # 间隔(ticks) + ) + def stopActionBarUpdates(self): #结束玩家个人动态栏显示 + taskId = self.actionBarTaskId + if taskId is not None: + self.actionBarTaskId = None + ps.scheduler.stopTask(taskId) + def startParticle(self): #开始更新玩家船的粒子 + if self.boat: #如果船存在的话 + self.particleTaskId = ps.scheduler.scheduleRepeatingTask( + lambda: self.spawnBoatParticles(), + 0, # 延迟 + 1 # 间隔(ticks) + ) + def stopParticle(self): #停止更新玩家船的粒子 + taskId = self.particleTaskId + if taskId is not None: + self.particleTaskId = None + ps.scheduler.stopTask(taskId) + def startReadyParticle(self): #准备阶段隐形的粒子显示 仅多人 + if self.player: + self.readyParticleTaskId = ps.scheduler.scheduleRepeatingTask( + lambda: self.spawnPlayerParticles(), + 0, # 延迟 + 1 # 间隔(ticks) + ) + def stopReadyParticle(self): #结束准备阶段隐形的粒子显示 仅多人 + taskId = self.readyParticleTaskId + if taskId is not None: + self.readyParticleTaskId = None + ps.scheduler.stopTask(taskId) +class PlayerInParticlePreview: #粒子预览类 + def __init__(self, playerName, index): + self.name = playerName + self.player = Bukkit.getPlayer(playerName) + self.playerData = PlayerDataManager(self.name) #重新读取 + self.boatMaterial = self.playerData.getBoatMaterial() #获取玩家船类型 + self.particleMode = self.playerData.getParticleMode() #获取玩家粒子模式 + self.particleType = collectionManager.getParticleType(index) #获取粒子类型 + self.particleInfo = collectionManager.getParticleInfo(index) #获取粒子信息 + self.boat = None + playersInParticlePreview[self.name] = self + self.particleTaskId = None #存储任务ID + #创造类后直接开始预览 + self.spawnAndRideBoat() + self.startPreviewing() + def getRelativeOffset(self, dx=0, dz=0): #获取相对偏移坐标 + direction = self.boat.getLocation().getDirection().normalize().clone() + directionX = self.boat.getLocation().getDirection().normalize().clone() + right = directionX.crossProduct(Vector(0, 1, 0)).normalize() # 计算垂直于方向的右侧向量 + offset = (right.multiply(dx) # 水平偏移 + .add(direction.multiply(dz))) # 前后偏移 + offset.setY(0.1) #粒子固定抬升0.1 + return self.boat.getLocation().add(offset) + def startPreviewing(self): #开始预览 + self.particleTaskId = ps.scheduler.scheduleRepeatingTask(lambda: self.previewing(),0,1) + def previewing(self): #生成船的粒子 + #print('Looping') + if self.particleMode: #自定义粒子模式 + leftLoc = self.getRelativeOffset(0.5, -1.0) + rightLoc = self.getRelativeOffset(-0.5, -1.0) + # 直接生成左右两边的粒子(完全对应原版命令) + worldManager.getWorld().spawnParticle( + Particle.valueOf(self.particleType), # 粒子类型 + leftLoc.add(Vector(0, self.particleInfo[5], 0)), # 相对坐标 + self.particleInfo[0], # 粒子数量 + self.particleInfo[1], # 偏移量x + self.particleInfo[2], # 偏移量y + self.particleInfo[3], # 偏移量z + self.particleInfo[4], # 速度 + None, # 数据 + True # force模式 + ) + worldManager.getWorld().spawnParticle( + Particle.valueOf(self.particleType), + rightLoc.add(Vector(0, self.particleInfo[5], 0)), # 相对坐标 + self.particleInfo[0], # 粒子数量 + self.particleInfo[1], # 偏移量x + self.particleInfo[2], # 偏移量y + self.particleInfo[3], # 偏移量z + self.particleInfo[4], # 速度 + None, # 数据 + True + ) + else: #单粒子模式 + worldManager.getWorld().spawnParticle( + Particle.DRAGON_BREATH, + self.boat.getLocation().add(Vector(0, 0.1, 0)), + 0,0,0,0,0, + None, # 数据 + True # force模式 + ) + if not self.player.isInsideVehicle(): #如果不在载具里 + self.stopPreviewing() #停止预览 + def stopPreviewing(self): #停止预览 + if self.particleTaskId is not None: + ps.scheduler.stopTask(self.particleTaskId) #停止粒子展示 + if self.boat: #如果有绑定船 + if not self.boat.isDead(): #如果船还存在 + self.boat.remove() #清除船 + if self.name in playersInParticlePreview: + del playersInParticlePreview[self.name] + def spawnAndRideBoat(self): #根据玩家自己的船的类型自动上船 + loc = self.player.getLocation() + boat = worldManager.getWorld().spawnEntity(loc, EntityType.valueOf(self.boatMaterial.upper())) #生成船 + boat.addPassenger(self.player) #自动骑乘 + self.boat = boat +class IceboatMap: #地图类 + def __init__(self, mapNum, maproomNum): + + self.mapNum = int(mapNum) + self.maproomNum = int(maproomNum) + self.world = getServer().getWorld(WORLD_NAME) + #地图配置数据 + self.mapData = MapManager(self.mapNum) + self.yaw = self.mapData.getYaw() + self.lap = self.mapData.getLap() + self.point = self.mapData.getPoint() + self.display = self.mapData.getDisplay() + self.difficulty = self.mapData.getDifficulty() + #地图坐标字典集 + self.pointPositionDict = {} + points = self.mapData.getPoints() + #计算坐标 join + dimensions = [] + for axis in ['x', 'y', 'z']: + a = points.getInt('join.{}'.format(axis)) + if axis == 'x': + a = a + (self.mapNum * 10000) + if axis == 'z': + a = a + (self.maproomNum * 10000) + dimensions.append(a) + self.pointPositionDict['join'] = list(dimensions) + self.pointPositionDict['join'].append(self.yaw) + self.pointPositionDict['join'].append(0) #pitch 0 + + #计算区间 glass ready points + for key in ['glass', 'ready'] + list(range(1, int(self.point) + 1)): + mins, maxs = [], [] + for axis in ['x', 'y', 'z']: + a, b = sorted([ + points.getInt('{}.pos1.{}'.format(key, axis)), + points.getInt('{}.pos2.{}'.format(key, axis)) + ]) + if axis == 'x': + a += self.mapNum * 10000 + b += self.mapNum * 10000 + if axis == 'z': + a += self.maproomNum * 10000 + b += self.maproomNum * 10000 + mins.append(a) + maxs.append(b) + self.pointPositionDict['{}'.format(key)] = tuple(mins + maxs) + def initialSpecialMap(self): #初始化特殊地图 + if self.mapNum == 9: #西南克克十字城 + world = getServer().getWorld(WORLD_NAME) + x = 95000 + y = 54 + z = 5000 + (self.maproomNum * 10000) + block = world.getBlockAt(x, y, z) + blockType = block.getType() + if blockType != Material.REDSTONE_BLOCK: + ps.scheduler.runTaskLater(lambda: block.setType(Material.REDSTONE_BLOCK), 20) + else: + ps.scheduler.runTaskLater(lambda: block.setType(Material.AIR), 10) + ps.scheduler.runTaskLater(lambda: block.setType(Material.REDSTONE_BLOCK), 20) + def getJoinPosition(self): #获取进入点坐标 + return self.pointPositionDict['join'] + def getPreparingArea(self): #获取准备区域 + return self.pointPositionDict['ready'] + def getPointArea(self, point): #获取检查点区域 + return self.pointPositionDict[str(point + 1)] + def replaceBlock(self, targetType): #开始区域线替换为目标材质 + if targetType == START_LINE_MATERIAL: + type = Material.TRIPWIRE + else: + type = START_LINE_MATERIAL + x1, y1, z1, x2, y2, z2 = self.pointPositionDict['glass'] + for x in range(int(x1), int(x2) + 1): + for y in range(int(y1), int(y2) + 1): + for z in range(int(z1), int(z2) + 1): + block = self.world.getBlockAt(x, y, z) + if block.type == type: + block.setType(targetType) + + +# 通用函数 ------------------------------------------------------------------- +def actionBar(player, text): #为玩家设置动态栏 + if player: + player.spigot().sendMessage(ACTION_BAR, TextComponent.fromLegacyText(text)) +def createScoreboardObjective(name): #初始化计分板 + manager = getServer().getScoreboardManager() + scoreboard = manager.getMainScoreboard() + if not scoreboard.getObjective(name): + scoreboard.registerNewObjective(name, Criteria.DUMMY, name) +def getScoreboardObjective(scoreboardName): + scoreboard = getServer().getScoreboardManager().getMainScoreboard() + if not scoreboard.getObjective(scoreboardName): #初始化计分板 + createScoreboardObjective(scoreboardName) + objective = scoreboard.getObjective(scoreboardName) + return objective +def broadcast(message): #世界广播内容 + for player in worldManager.getWorld().getPlayers(): + player.sendMessage(message) + + +# 命令 ------------------------------------------------------------------- +def iceboatCommand(sender, label, args): # 房间命令 + if len(args) == 0: + return False + command = args[0] + if command == u'start': # 启动房间命令,不需要玩家名 + if len(args) < 2: + return False + roomId = args[1] + if roomId in multiGames: + room = multiGames[roomId] + if isinstance(sender, Player): + playerName = sender.getName() + player = sender + if not room.isPlayerInRoom(playerName): + player.sendTitle(u"§f", u"§c你不在该房间内,无法启动房间", 5, 60, 10) + player.playSound(player, Sound.ENTITY_VILLAGER_NO, 1.0, 1.0) + return False + room.enterRunning() + return True + else: + if isinstance(sender, Player): + sender.sendTitle(u"§f", u"§c房间未被创建,请先进入房间", 5, 60, 10) + sender.playSound(sender, Sound.ENTITY_VILLAGER_NO, 1.0, 1.0) + return False + elif command == u'seasonreset': + if isinstance(sender, Player): + if sender.isOp(): + def resetOver(): + sender.sendTitle(u"§f", u"§a所有玩家当季地图纪录已重置", 5, 60, 10) + sender.playSound(sender, Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0, 1.0) + ps.scheduler.runTaskLater(lambda h=hallOfFameManager:hallOfFameManager.updateHallOfFame(), 40) + ps.scheduler.runSyncCallbackTask(lambda sm=seasonManager: sm.resetSeasonRecords(), resetOver()) + if len(args) < 2: + return False + if command == 'multi': # 命令格式 /ib multi + if len(args) < 3: + return False + roomId = args[1] + targetPlayerName = args[2] + targetPlayer = Bukkit.getPlayer(targetPlayerName) + if targetPlayer is None: + return False + playerManager = PlayerManager(targetPlayer) + if playerManager.inAnyRoom(): #判断玩家是否在房间内 + targetPlayer.sendTitle(u"§f", u"§c你已经在其它房间内", 5, 60, 10) + targetPlayer.playSound(targetPlayer, Sound.ENTITY_VILLAGER_NO, 1.0, 1.0) + return False + if roomId not in multiGames: # 若该ID的房间未开启 则新增一个实例 + multiGames[roomId] = MultiGameRoom(roomId) + result = multiGames[roomId].addPlayer(targetPlayerName) + if isinstance(result, tuple) and len(result) == 2: + success, message = result + if success: + targetPlayer.sendTitle(u"§f", u"§a{}".format(message), 5, 60, 10) + targetPlayer.playSound(targetPlayer, Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0, 1.0) + else: + targetPlayer.sendTitle(u"§f", u"§c{}".format(message), 5, 60, 10) + targetPlayer.playSound(targetPlayer, Sound.ENTITY_VILLAGER_NO, 1.0, 1.0) + else: + targetPlayer.sendMessage(u"§c发生未知错误,请稍后重试。") + if command == 'leave': + if len(args) < 2: + return False + targetPlayerName = args[1] + targetPlayer = getServer().getPlayer(targetPlayerName) + if targetPlayer is None: + return False + playerManager = PlayerManager(targetPlayer) + if playerManager.leaveRoom(): + return True + return False +def iceboatMenuCommand(sender, label, args): # 菜单命令 + if not isinstance(sender, Player): #仅玩家可用 + return False + player = sender + worldName = player.getWorld().getName() + if worldName != WORLD_NAME: + player.sendMessage(u"§c请在冰船世界中使用该指令") + return False + if len(args) > 0: + if args[0].lower() == "single": + playerManager = PlayerManager(player) + if playerManager.inAnyRoom(): + player.sendMessage(u"§c你正在一个游戏房间内!") + player.playSound(player, Sound.ENTITY_VILLAGER_NO, 1.0, 1.0) + return False + MenuSingle(player).openWithRender() # 打开单人地图选择页面 + player.playSound(player, Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0, 1.0) + return True + elif args[0].lower() == "multi" and len(args) > 1: + roomId = args[1] + if roomId not in multiGames: + player.sendTitle(u"§f", u"§c房间未被创建,请先进入房间", 5, 60, 10) + player.playSound(player, Sound.ENTITY_VILLAGER_NO, 1.0, 1.0) + return False + room = multiGames.get(str(roomId)) + playerName = player.getName() + if not room.isPlayerInRoom(playerName): + player.sendTitle(u"§f", u"§c你不在该房间,请先进入房间", 5, 60, 10) + player.playSound(player, Sound.ENTITY_VILLAGER_NO, 1.0, 1.0) + return + MenuMulti(player, roomId).openWithRender() # 打开多人地图选择页面 + player.playSound(player, Sound.ITEM_BOOK_PAGE_TURN, 1.0, 1.0) + return True + elif args[0].lower() == "collection": + MenuCollection(player).openWithRender() # 打开收集品菜单 + player.playSound(player, Sound.ITEM_BOOK_PAGE_TURN, 1.0, 1.0) + return True + return False +ps.command.registerCommand(iceboatCommand, "ib") +ps.command.registerCommand(iceboatCommand, "iceboat") +ps.command.registerCommand(iceboatMenuCommand, "ibm") +ps.command.registerCommand(iceboatMenuCommand, "iceboatmenu") + + +# 菜单处理 ------------------------------------------------------------------- +class MenuSingle(GUIController):# 单人房间菜单 + def __init__(self, player, page=1, sortMode="default", isSeason=False): + super(MenuSingle, self).__init__(player) + self.page = page + self.sortMode = sortMode + self.isSeason = isSeason + self.setInfo("iceboat.single", u"§0单人模式地图选择 - 第 {} 页".format(page)) + self.setGUIManager(guiManager) + def render(self): + player = self.getPlayer() + playerName = player.getName() + mapNumbers = [] + if self.isSeason: + mapNumbers = seasonManager.getSeasonMaps() + else: + mapNumbers = mapsManager.getMapsNum() + # 根据排序模式排序地图 + if self.sortMode == "descDifficulty": + mapNumbers.sort(key=lambda num: float(ps.config.loadConfig(u"iceboat/maps/{}.yml".format(num)).get("difficulty", 0)), reverse=True) + elif self.sortMode == "ascDifficulty": + mapNumbers.sort(key=lambda num: float(ps.config.loadConfig(u"iceboat/maps/{}.yml".format(num)).get("difficulty", 0))) + elif self.sortMode == "descNumber": + mapNumbers.sort(key=lambda num: int(num), reverse=True) + elif self.sortMode == "ascNumber": + mapNumbers.sort(key=lambda num: int(num)) + + itemsPerPage = 45 # 每页显示 45 个物品,最后一行用于控制 + self.totalPages = (len(mapNumbers) + itemsPerPage - 1) // itemsPerPage + + startIndex = (self.page - 1) * itemsPerPage + endIndex = startIndex + itemsPerPage + currentPageItems = mapNumbers[startIndex:endIndex] + + playerDataManager = PlayerDataManager(playerName) + + #创建物品 + for index, mapNumber in enumerate(currentPageItems): # 为每个地图创建一个物品 + mapFilePath = u"iceboat/maps/{}.yml".format(mapNumber) + mapConfig = ps.config.loadConfig(mapFilePath) + displayName = mapConfig.get("display", u"§b§l【§e§l地图 {}§b§l】§f".format(mapNumber)) + material = mapConfig.get("material", "PAPER") + lap = mapConfig.get("lap", "未知") + + mapManager = MapManager(mapNumber) + difficulty = mapManager.getDifficulty() + difficultySuffix = mapManager.getDifficultySuffix() + + available = roomsManager.getAvailableRoomCount(mapNumber) + total = roomsManager.getTotalRoomCount(mapNumber) + + singleMapBestRecord = playerDataManager.getSingleBestMapRecord(mapNumber) + singleMapBestRecordDay = playerDataManager.getSingleBestMapRecordDay(mapNumber) + seasonMapBestRecord = playerDataManager.getSeasonBestMapRecord(mapNumber) + mapTimes = playerDataManager.getMapTimes(mapNumber) + + try: + material = getattr(Material, material) + except AttributeError: + material = Material.PAPER + + lores = [u"§e序号: §f{}".format(mapNumber), + u"§e难度: §f{} {}".format(difficulty, difficultySuffix), + u"§e圈数: §f{}".format(lap), + "", + u"§e剩余空房: §f{}/{}".format(available, total), + u"§e当季纪录: §f{:.2f}".format(seasonMapBestRecord) if seasonMapBestRecord else u"§e当季纪录: §f暂无" + ] + if self.isSeason: + lores.append(u"§e当季标准: §f{:.2f}".format(seasonManager.getSeasonStandard(mapNumber))) + lores.append(u"§e当季分数: §f{:.2f}".format(playerDataManager.getSeasonMapScore(mapNumber))) + + lores.extend([ + "", + u"§e完成次数: §f{}".format(mapTimes) if mapTimes else u"§e完成次数: §f0", + u"§e单人纪录: §f{:.2f}".format(singleMapBestRecord) if singleMapBestRecord else u"§e单人纪录: §f暂无", + u"§e纪录日期: §f{}".format(singleMapBestRecordDay) if singleMapBestRecordDay else u"§e纪录日期: §f暂无", + "", + u"§6>> §a点击进入 §6<<" + ]) + + self.set(index, material, + u"§b§l【§e§l{}§b§l】§f".format(displayName), + *lores, + data=str(mapNumber) + ) + self.spawnSeparators(45, 53) # 填充最后一行默认黑色染色玻璃板 + if self.page > 1: + self.set(45, Material.WHITE_STAINED_GLASS_PANE, u"§a上一页") # 上一页按钮 + self.set(47, Material.LIME_STAINED_GLASS_PANE, u"§a按序号升序") # 序号升序按钮 + self.set(48, Material.ORANGE_STAINED_GLASS_PANE, u"§c按序号降序") # 序号降序按钮 + buttonText = u"§e仅看当季图池" if not self.isSeason else u"§e显示所有地图" + self.set(49, Material.YELLOW_STAINED_GLASS_PANE, buttonText) # 当季图池按钮 + self.set(50, Material.GREEN_STAINED_GLASS_PANE, u"§a按难度升序")# 难度升序按钮 + self.set(51, Material.RED_STAINED_GLASS_PANE, u"§c按难度降序") # 难度降序按钮 + if self.page < self.totalPages: + self.set(53, Material.WHITE_STAINED_GLASS_PANE, u"§a下一页") # 下一页按钮 + # 记录玩家当前页码和排序模式 + playerPage[playerName] = { + "page": self.page, + "sortMode": self.sortMode, + "isSeason": self.isSeason + } + def onClick(self, e): + e.setCancelled(True) + clickInt = e.getSlot() + player = self.getPlayer() + if clickInt == 45 and self.page > 1: + MenuSingle(player, page=self.page - 1, sortMode=self.sortMode, isSeason=self.isSeason).openWithRender() + player.playSound(player, Sound.ITEM_BOOK_PAGE_TURN, 1.0, 1.0) + elif clickInt == 47: + MenuSingle(player, sortMode="ascNumber", isSeason=self.isSeason).openWithRender() + player.playSound(player, Sound.UI_CARTOGRAPHY_TABLE_TAKE_RESULT, 1.0, 1.0) + elif clickInt == 48: + MenuSingle(player, sortMode="descNumber", isSeason=self.isSeason).openWithRender() + player.playSound(player, Sound.UI_CARTOGRAPHY_TABLE_TAKE_RESULT, 1.0, 1.0) + elif clickInt == 49: + MenuSingle(player, sortMode=self.sortMode, isSeason=not self.isSeason).openWithRender() + player.playSound(player, Sound.ITEM_BOOK_PAGE_TURN, 1.0, 1.0) + elif clickInt == 50: + MenuSingle(player, sortMode="ascDifficulty", isSeason=self.isSeason).openWithRender() + player.playSound(player, Sound.UI_CARTOGRAPHY_TABLE_TAKE_RESULT, 1.0, 1.0) + elif clickInt == 51: + MenuSingle(player, sortMode="descDifficulty", isSeason=self.isSeason).openWithRender() + player.playSound(player, Sound.UI_CARTOGRAPHY_TABLE_TAKE_RESULT, 1.0, 1.0) + elif clickInt == 53 and self.page < self.totalPages: + MenuSingle(player, page=self.page + 1, sortMode=self.sortMode, isSeason=self.isSeason).openWithRender() + player.playSound(player, Sound.ITEM_BOOK_PAGE_TURN, 1.0, 1.0) + elif clickInt < 45: + item = e.getCurrentItem() + mapNumber = getGUIValue(item) + if mapNumber is not None: + playerManager = PlayerManager(player) + if not playerManager.joinSingleRoom(mapNumber): + MenuSingle(player, page=self.page, sortMode=self.sortMode, isSeason=self.isSeason).openWithRender() +class MenuMulti(GUIController): + def __init__(self, player, roomId, page=1, sortMode="default", isSeason=False): + super(MenuMulti, self).__init__(player) + self.roomId = roomId + self.page = page + self.sortMode = sortMode + self.isSeason = isSeason + self.setInfo("iceboat.multi", u"§0多人模式地图选择 - 房间 {} - 第 {} 页".format(roomId, page)) + self.setGUIManager(guiManager) + def render(self): + player = self.getPlayer() + playerName = player.getName() + + mapNumbers = [] + if self.isSeason: + mapNumbers = seasonManager.getSeasonMaps() + else: + mapNumbers = mapsManager.getMapsNum() + + if self.sortMode == "descDifficulty": + mapNumbers.sort(key=lambda num: float(ps.config.loadConfig(u"iceboat/maps/{}.yml".format(num)).get("difficulty", 0)), reverse=True) + elif self.sortMode == "ascDifficulty": + mapNumbers.sort(key=lambda num: float(ps.config.loadConfig(u"iceboat/maps/{}.yml".format(num)).get("difficulty", 0))) + elif self.sortMode == "descNumber": + mapNumbers.sort(key=lambda num: int(num), reverse=True) + elif self.sortMode == "ascNumber": + mapNumbers.sort(key=lambda num: int(num)) + + itemsPerPage = 45 + self.totalPages = (len(mapNumbers) + itemsPerPage - 1) // itemsPerPage + + startIndex = (self.page - 1) * itemsPerPage + endIndex = startIndex + itemsPerPage + currentPageItems = mapNumbers[startIndex:endIndex] + + playerDataManager = PlayerDataManager(playerName) + + for index, mapNumber in enumerate(currentPageItems): + mapFilePath = u"iceboat/maps/{}.yml".format(mapNumber) + mapConfig = ps.config.loadConfig(mapFilePath) + displayName = mapConfig.get("display", u"§b§l【§e§l地图 {}§b§l】§f".format(mapNumber)) + material = mapConfig.get("material", "PAPER") + lap = mapConfig.get("lap", "未知") + + mapManager = MapManager(mapNumber) + difficulty = mapManager.getDifficulty() + difficultySuffix = mapManager.getDifficultySuffix() + + available = roomsManager.getAvailableRoomCount(mapNumber) + total = roomsManager.getTotalRoomCount(mapNumber) + + multiMapBestRecord = playerDataManager.getMultiBestMapRecord(mapNumber) + multiMapBestRecordDay = playerDataManager.getMultiBestMapRecordDay(mapNumber) + seasonMapBestRecord = playerDataManager.getSeasonBestMapRecord(mapNumber) + mapTimes = playerDataManager.getMapTimes(mapNumber) + + try: + material = getattr(Material, material) + except AttributeError: + material = Material.PAPER + + lores = [ + u"§e序号: §f{}".format(mapNumber), + u"§e难度: §f{} {}".format(difficulty, difficultySuffix), + u"§e圈数: §f{}".format(lap), + "", + u"§e剩余空房: §f{}/{}".format(available, total), + u"§e当季纪录: §f{:.2f}".format(seasonMapBestRecord) if seasonMapBestRecord else u"§e当季纪录: §f暂无", + ] + if self.isSeason: + lores.append(u"§e当季标准: §f{:.2f}".format(seasonManager.getSeasonStandard(mapNumber))) + lores.append(u"§e当季分数: §f{:.2f}".format(playerDataManager.getSeasonMapScore(mapNumber))) + lores.extend([ + "", + u"§e完成次数: §f{}".format(mapTimes) if mapTimes else u"§e完成次数: §f0", + u"§e多人纪录: §f{:.2f}".format(multiMapBestRecord) if multiMapBestRecord else u"§e多人纪录: §f暂无", + u"§e纪录日期: §f{}".format(multiMapBestRecordDay) if multiMapBestRecordDay else u"§e纪录日期: §f暂无", + "", + u"§6>> §a点击设置地图 §6<<", + ]) + self.set(index, material, u"§b§l【§e§l{}§b§l】§f".format(displayName), + *lores, + data=str(mapNumber) + ) + + self.spawnSeparators(45, 53) + if self.page > 1: + self.set(45, Material.WHITE_STAINED_GLASS_PANE, u"§a上一页") + self.set(47, Material.LIME_STAINED_GLASS_PANE, u"§a按序号升序") + self.set(48, Material.ORANGE_STAINED_GLASS_PANE, u"§c按序号降序") + buttonText = u"§e仅看当季图池" if not self.isSeason else u"§e显示所有地图" + self.set(49, Material.YELLOW_STAINED_GLASS_PANE, buttonText) + self.set(50, Material.GREEN_STAINED_GLASS_PANE, u"§a按难度升序") + self.set(51, Material.RED_STAINED_GLASS_PANE, u"§c按难度降序") + if self.page < self.totalPages: + self.set(53, Material.WHITE_STAINED_GLASS_PANE, u"§a下一页") + + playerPage[playerName] = { + "page": self.page, + "sortMode": self.sortMode, + "isSeason": self.isSeason, + "roomId": self.roomId + } + def onClick(self, e): + e.setCancelled(True) + clickInt = e.getSlot() + player = self.getPlayer() + playerName = player.getName() + room = multiGames.get(str(self.roomId)) + if not room or not room.isPlayerInRoom(playerName): + player.closeInventory() + return + if clickInt == 45 and self.page > 1: + MenuMulti(player, self.roomId, page=self.page - 1, sortMode=self.sortMode, isSeason=self.isSeason).openWithRender() + player.playSound(player, Sound.ITEM_BOOK_PAGE_TURN, 1.0, 1.0) + elif clickInt == 47: + MenuMulti(player, self.roomId, sortMode="ascNumber", isSeason=self.isSeason).openWithRender() + player.playSound(player, Sound.UI_CARTOGRAPHY_TABLE_TAKE_RESULT, 1.0, 1.0) + elif clickInt == 48: + MenuMulti(player, self.roomId, sortMode="descNumber", isSeason=self.isSeason).openWithRender() + player.playSound(player, Sound.UI_CARTOGRAPHY_TABLE_TAKE_RESULT, 1.0, 1.0) + elif clickInt == 49: + MenuMulti(player, self.roomId, sortMode=self.sortMode, isSeason=not self.isSeason).openWithRender() + player.playSound(player, Sound.ITEM_BOOK_PAGE_TURN, 1.0, 1.0) + elif clickInt == 50: + MenuMulti(player, self.roomId, sortMode="ascDifficulty", isSeason=self.isSeason).openWithRender() + player.playSound(player, Sound.UI_CARTOGRAPHY_TABLE_TAKE_RESULT, 1.0, 1.0) + elif clickInt == 51: + MenuMulti(player, self.roomId, sortMode="descDifficulty", isSeason=self.isSeason).openWithRender() + player.playSound(player, Sound.UI_CARTOGRAPHY_TABLE_TAKE_RESULT, 1.0, 1.0) + elif clickInt == 53 and self.page < self.totalPages: + MenuMulti(player, self.roomId, page=self.page + 1, sortMode=self.sortMode, isSeason=self.isSeason).openWithRender() + player.playSound(player, Sound.ITEM_BOOK_PAGE_TURN, 1.0, 1.0) + elif clickInt < 45: + item = e.getCurrentItem() + mapNumber = getGUIValue(item) + if mapNumber is not None: + available = roomsManager.getAvailableRoomCount(mapNumber) + if available > 0: + mapNum = str(mapNumber) + if str(self.roomId) not in multiGames: + player.sendTitle(u"§f", u"§c房间未被创建,请先进入房间", 5, 60, 10) + player.playSound(player, Sound.ENTITY_VILLAGER_NO, 1.0, 1.0) + return + roomNum = roomsManager.getAvailableRoom(mapNum) + if roomNum is None: + player.sendTitle(u"§f", u"§c暂无空房", 5, 60, 10) + player.playSound(player, Sound.ENTITY_VILLAGER_NO, 1.0, 1.0) + return + multiGames[str(self.roomId)].setIceboatMap(mapNum, roomNum) + multiGames[str(self.roomId)].createHologram() #更新全息显示 + player.playSound(player, Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0, 1.0) + else: + player.playSound(player, Sound.ENTITY_VILLAGER_NO, 1.0, 1.0) + player.sendMessage(u"§c该地图暂无空房,请稍后再试") + MenuMulti(player, self.roomId, self.page, self.sortMode, self.isSeason).openWithRender() +class MenuCollection(GUIController): + def __init__(self, player, page=1, itemType=u"particles", showOwnedOnly=False): + super(MenuCollection, self).__init__(player) + self.page = page + self.itemType = itemType + self.showOwnedOnly = showOwnedOnly + self.setInfo("iceboat.collection", u"§0冰船收集品菜单 - 第 {} 页".format(page)) + self.setGUIManager(guiManager) + def render(self): + player = self.getPlayer() + playerName = player.getName() + items = list(collectionManager.getAllItems(self.itemType)) + itemsCount = len(items) + playerData = PlayerDataManager(playerName) + ownedItems = list(playerData.getOwnedItems(self.itemType)) + ownedItemsCount = len(ownedItems) + if self.showOwnedOnly: + items = ownedItems + itemsPerPage = 45 # 每页显示 45 个物品,最后一行用于控制 + self.totalPages = (len(items) + itemsPerPage - 1) // itemsPerPage + startIndex = (self.page - 1) * itemsPerPage + endIndex = startIndex + itemsPerPage + currentPageItems = items[startIndex:endIndex] + for index, itemNumber in enumerate(currentPageItems): + display = collectionManager.getDisplay(self.itemType, itemNumber) + material = collectionManager.getMaterial(self.itemType, itemNumber) + try: + material = getattr(Material, material) + except AttributeError: + material = Material.PAPER + + owned = False #默认未拥有 + available = False #默认不可用 + notForSale = False #默认可卖 + + lore = [] + if itemNumber in ownedItems: + owned = True + available = True + lore.append(u"§7已拥有") + lore.append(u"§f") + + + addLore = collectionManager.getCollectionLore(self.itemType, itemNumber) + if addLore is not None: + lore.extend(addLore) + + conditions = collectionManager.getCollectionCondition(self.itemType, itemNumber) + color = u"§c" + if conditions[0] == u"notForSale": + notForSale = True + elif conditions[0] == u"cost": + price = conditions[1] + balance = playerData.getBalance() + if price <= balance: + available = True + color = u"§f" + lore.append(u"§e价格: {}{} DC币".format(color, price)) + elif conditions[0] == u"level": + level = conditions[1] + playerLevel = playerData.getLevel() + if playerLevel >= level: + available = True + color = u"§f" + lore.append(u"§e驾照等级要求: {}{}".format(color, level)) + elif conditions[0] == u"totalTimes": + totalTimes = conditions[1] + playerTotalMapTimes = playerData.getTotalMapTimes() + if playerTotalMapTimes >= totalTimes: + available = True + color = u"§f" + lore.append(u"§e总完成次数要求: {}{}".format(color, totalTimes)) + elif conditions[0] == u"record": + lore.append(u"§e单人纪录要求:") + recordDict = conditions[1] + satisfied = True + for mapNum, record in recordDict.items(): + mapDisplay = MapManager(mapNum).getDisplay() + playerRecord = playerData.getSingleBestMapRecord(mapNum) + color = u"§c" + if playerRecord > 0: + if playerRecord <= record: + color = u"§f" + else: + satisfied = False + lore.append(u"§7- {}{} {:.2f} <= {:.2f}".format(color, mapDisplay, playerRecord, record)) + else: + satisfied = False + playerRecord = u"暂无成绩" + lore.append(u"§7- {}{} {} <= {:.2f}".format(color, mapDisplay, playerRecord, record)) + if satisfied: + available = True + elif conditions[0] == u"times": + lore.append(u"§e地图完成次数要求:") + timesDict = conditions[1] + satisfied = True + for mapNum, times in timesDict.items(): + mapDisplay = MapManager(mapNum).getDisplay() + playerTimes = playerData.getMapTimes(mapNum) + color = u"§c" + if playerTimes >= times: + color = u"§f" + else: + satisfied = False + lore.append(u"§7- {}{} {} >= {}".format(color, mapDisplay, int(playerTimes), int(times))) + if satisfied: + available = True + + lore.append(u"§f") + + if owned: + lore.append(u"§6>> §a点击使用 §6<<") + elif available: + lore.append(u"§6>> §e点击获取 §6<<") + elif notForSale: + lore.append(u"§6>> §c非卖品 §6<<") + else: + lore.append(u"§6<< §c未达要求 §6>>") + + self.set(index, material, u"§b§l【§e§l{}§b§l】§f".format(display), *lore, + data="{}:{}:{}:{}".format(owned, available, self.itemType, itemNumber)) + + self.spawnSeparators(45, 53) # 填充最后一行默认黑色染色玻璃板 + if self.page > 1: + self.set(45, Material.WHITE_STAINED_GLASS_PANE, u"§a上一页") # 上一页按钮 + self.set(47, Material.valueOf(playerData.getParticleMaterial()), u"§6>> §a选择粒子 §6<<", u'§7当前:{}'.format(playerData.getParticleDisplay())) # 粒子类型按钮 + self.set(48, Material.SPYGLASS, u"§6>> §b打开粒子预览页面 §6<<", u"§7仅在大厅可预览粒子效果") # 粒子预览 + buttonText = u"§6>> §6仅看已拥有 §6<<" if not self.showOwnedOnly else u"§6>> §6显示所有内容 §6<<" + if self.itemType == u"particles": + collectionType = u"粒子" + elif self.itemType == u"boats": + collectionType = u"船型" + elif self.itemType == u"titles": + collectionType = u"称号" + completion = float(ownedItemsCount) / float(itemsCount) * 100 + self.set(49, Material.YELLOW_STAINED_GLASS_PANE, buttonText, u"§e{}收集完成度:§f{:.2f}% §7({}/{})".format(collectionType, completion, ownedItemsCount, itemsCount)) # 仅看已拥有/显示所有内容按钮 + self.set(50, Material.valueOf(playerData.getBoatMaterial()), u"§6>> §a选择船 §6<<", u'§7当前:{}'.format(playerData.getBoatDisplay())) # 船类型按钮 + self.set(51, Material.valueOf(playerData.getTitleMaterial()), u"§6>> §a选择称号 §6<<", u'§7当前:{}'.format(playerData.getTitleDisplay())) # 称号按钮 + if self.page < self.totalPages: + self.set(53, Material.WHITE_STAINED_GLASS_PANE, u"§a下一页") # 下一页按钮 + + # 记录玩家当前页码和筛选设置 + playerPage[playerName] = { + "page": self.page, + "showOwnedOnly": self.showOwnedOnly, + "itemType": self.itemType + } + def onClick(self, e): + e.setCancelled(True) + clickInt = e.getSlot() + player = self.getPlayer() + playerManager = PlayerManager(player) + playerName = player.getName() + playerPageInfo = playerPage.get(playerName, {"page": 1, "itemType": u"boats", "showOwnedOnly": False}) + currentPage = playerPageInfo["page"] + showOwnedOnly = playerPageInfo["showOwnedOnly"] + itemType = playerPageInfo["itemType"] + + if clickInt == 45 and currentPage > 1: + MenuCollection(player, page=currentPage - 1, itemType=itemType, showOwnedOnly=showOwnedOnly).openWithRender() + player.playSound(player, Sound.ITEM_BOOK_PAGE_TURN, 1.0, 1.0) + elif clickInt == 47: + MenuCollection(player, itemType=u"particles", showOwnedOnly=showOwnedOnly).openWithRender() + player.playSound(player, Sound.ITEM_BOOK_PAGE_TURN, 1.0, 1.0) + elif clickInt == 48: + MenuPreviewing(player).openWithRender() + player.playSound(player, Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0, 1.0) + elif clickInt == 49: + MenuCollection(player, itemType=itemType, showOwnedOnly=not showOwnedOnly).openWithRender() + player.playSound(player, Sound.ITEM_BOOK_PAGE_TURN, 1.0, 1.0) + elif clickInt == 50: + MenuCollection(player, itemType=u"boats", showOwnedOnly=showOwnedOnly).openWithRender() + player.playSound(player, Sound.ITEM_BOOK_PAGE_TURN, 1.0, 1.0) + elif clickInt == 51: + MenuCollection(player, itemType=u"titles", showOwnedOnly=showOwnedOnly).openWithRender() + player.playSound(player, Sound.ITEM_BOOK_PAGE_TURN, 1.0, 1.0) + elif clickInt == 53 and currentPage < self.totalPages: + MenuCollection(player, page=currentPage + 1, itemType=itemType, showOwnedOnly=showOwnedOnly).openWithRender() + player.playSound(player, Sound.ITEM_BOOK_PAGE_TURN, 1.0, 1.0) + elif clickInt < 45: + item = e.getCurrentItem() + if item is not None: + guiValue = getGUIValue(item) + if guiValue: + owned, available, itemType, itemNumber = guiValue.split(':') + playerData = PlayerDataManager(playerName) + owned = int(owned) + available = int(available) + if owned == 0: # 如果玩家还没有该收藏品 + if available == 1: # 如果该收藏品可用 + conditions = collectionManager.getCollectionCondition(itemType, itemNumber) + if conditions[0] == u"cost": + if vaultEconomy is not None: + balance = vaultEconomy.getBalance(player) + price = conditions[1] + if vaultEconomy.has(player, price): #如果玩家有足够的钱 + vaultEconomy.withdrawPlayer(player, price) #消耗钱 + ownedItems = playerData.getOwnedItems(itemType) + ownedItems.append(itemNumber) + playerData.setOwnedItems(itemType, ownedItems) #更新玩家收藏品列表 + player.sendMessage(u"§a购买成功!剩余 §e{} §aDC币".format(balance - price)) + player.playSound(player, Sound.ENTITY_VILLAGER_YES, 1.0, 1.0) + else: #玩家钱不够则不修改数据 + player.sendMessage(u"§c你只有 §e{} §cDC币,无法购买!".format(balance)) + player.playSound(player, Sound.ENTITY_VILLAGER_NO, 1.0, 1.0) + return + else: + player.sendMessage(u"§cVault 经济插件未正确配置,请联系管理员") + return + else: #如果该收藏品不是价格型且可用 + ownedItems = playerData.getOwnedItems(itemType) + ownedItems.append(itemNumber) + playerData.setOwnedItems(itemType, ownedItems) #更新玩家收藏品列表 + player.sendMessage(u"§a获取成功!") + player.playSound(player, Sound.ENTITY_VILLAGER_YES, 1.0, 1.0) + else: #如果该收藏品不可用 + player.playSound(player, Sound.ENTITY_VILLAGER_NO, 1.0, 1.0) + return + + if itemType == "boats": + playerData.setBoatMaterial(itemNumber) + elif itemType == "particles": + playerData.setParticleType(itemNumber) + elif itemType == "titles": + playerData.setTitle(itemNumber) + player.playSound(player, Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0, 1.0) + MenuCollection(player, itemType=itemType, showOwnedOnly=showOwnedOnly).openWithRender() + + room = playerManager.inMultiRoom() # 刷新多人盔甲架 + if room: #如果房间存在 + room.createMapDisplayArmorStands() #更新全息显示 +class MenuPreviewing(GUIController): + def __init__(self, player, page=1): + super(MenuPreviewing, self).__init__(player) + self.page = page + self.setInfo("iceboat.previewing", u"§0粒子预览页面 - 第 {} 页".format(page)) + self.setGUIManager(guiManager) + def render(self): + player = self.getPlayer() + playerName = player.getName() + items = list(collectionManager.getAllItems("particles")) + itemsPerPage = 45 # 每页显示 45 个物品,最后一行用于控制 + self.totalPages = (len(items) + itemsPerPage - 1) // itemsPerPage + startIndex = (self.page - 1) * itemsPerPage + endIndex = startIndex + itemsPerPage + currentPageItems = items[startIndex:endIndex] + for index, itemNumber in enumerate(currentPageItems): + display = collectionManager.getDisplay("particles", itemNumber) + material = collectionManager.getMaterial("particles", itemNumber) + try: + material = getattr(Material, material) + except AttributeError: + material = Material.PAPER + self.set(index, material, u"§b§l【§e§l{}§b§l】§f".format(display), u"§6>> §a点击预览 §6<<", data=u"{}".format(itemNumber)) + self.spawnSeparators(45, 53) # 填充最后一行默认黑色染色玻璃板 + if self.page > 1: + self.set(45, Material.WHITE_STAINED_GLASS_PANE, u"§a上一页") # 上一页按钮 + self.set(49, Material.CHEST, u"§6>> §b打开收集品菜单 §6<<") + if self.page < self.totalPages: + self.set(53, Material.WHITE_STAINED_GLASS_PANE, u"§a下一页") # 下一页按钮 + # 记录玩家当前页码和筛选设置 + playerPage[playerName] = {"page": self.page} + def onClick(self, e): + e.setCancelled(True) + clickInt = e.getSlot() + player = self.getPlayer() + playerName = player.getName() + playerPageInfo = playerPage.get(playerName, {"page": 1}) + currentPage = playerPageInfo["page"] + + if clickInt == 45 and currentPage > 1: + MenuPreviewing(player, page=currentPage - 1).openWithRender() + player.playSound(player, Sound.ITEM_BOOK_PAGE_TURN, 1.0, 1.0) + elif clickInt == 49: + MenuCollection(player).openWithRender() + player.playSound(player, Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0, 1.0) + elif clickInt == 53 and currentPage < self.totalPages: + MenuPreviewing(player, page=currentPage + 1).openWithRender() + player.playSound(player, Sound.ITEM_BOOK_PAGE_TURN, 1.0, 1.0) + elif clickInt < 45: + item = e.getCurrentItem() + if item is not None: + itemNumber = int(float(getGUIValue(item))) + if playerName in playersInParticlePreview: #如果玩家已经在预览粒子 + playersInParticlePreview[playerName].stopPreviewing() #停止预览粒子 + playerLoc = player.getLocation() + playerLoc.setY(playerLoc.getY() + 0.5) + player.teleport(playerLoc) #将玩家传送到自己的位置 + playersInParticlePreview[playerName] = PlayerInParticlePreview(playerName, itemNumber) #创建玩家粒子预览对象 + player.closeInventory() #关闭页面 + player.playSound(player, Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0, 1.0) +class MenuLevel(GUIController): + def __init__(self, player, page=1, showAvailableOnly=True): + super(MenuLevel, self).__init__(player) + self.page = page + self.showAvailableOnly = showAvailableOnly + self.setInfo("iceboat.level", u"§0驾照等级页面 - 第 {} 页".format(page)) + self.setGUIManager(guiManager) + def render(self): + player = self.getPlayer() + playerName = player.getName() + playerData = PlayerDataManager(playerName) + playerLevel = playerData.getLevel() + if self.showAvailableOnly: + items = list(levelManager.getAvailableLevels(playerLevel)) + else: + items = list(levelManager.getAllLevels()) + + itemsPerPage = 45 # 每页显示 45 个物品,最后一行用于控制 + self.totalPages = (len(items) + itemsPerPage - 1) // itemsPerPage + startIndex = (self.page - 1) * itemsPerPage + endIndex = startIndex + itemsPerPage + + currentPageItems = items[startIndex:endIndex] + for index, itemNumber in enumerate(currentPageItems): + levelNeed = levelManager.getLevelNeed(itemNumber) + levelTarget = levelManager.getLevelTarget(itemNumber) + + levelEnough = True #默认等级足够 + finished = False #默认未完成 + goToMap = 0 #默认不跳转到地图 + if playerLevel >= levelTarget: #超越目标等级 + finished = True #无需领取 + if playerLevel < levelNeed: #等级要求 + levelEnough = False + if self.showAvailableOnly and (not levelEnough or finished): + continue # 跳过不符合条件的项 + lore = [ + u"§e等级要求: {}{}".format(u"§c" if not levelEnough else u"§f", levelNeed), + u"§e目标等级: {}{}".format(u"§c" if not finished else u"§f", levelTarget), + u"§f" + ] + + display = levelManager.getLevelDisplay(itemNumber) + material = levelManager.getLevelMaterial(itemNumber) + try: + material = getattr(Material, material) + except AttributeError: + material = Material.PAPER + + lore.append(u"§e单人纪录要求:") + recordDict = levelManager.getLevelRecords(itemNumber) + color = u"§c" + available = False #默认不可用 + satisfied = True #默认满足条件 + for mapNum, record in recordDict.items(): + mapManager = MapManager(mapNum) + mapDisplay = mapManager.getDisplay() + goToMap = int(mapNum) #最后一张地图作为跳转地图 + playerRecord = playerData.getSingleBestMapRecord(mapNum) + color = u"§c" + if playerRecord > 0: + if playerRecord <= record: + color = u"§f" + else: + satisfied = False + lore.append(u"§7- {}{} {:.2f} <= {:.2f}".format(color, mapDisplay, playerRecord, record)) + else: + satisfied = False + playerRecord = u"暂无成绩" + lore.append(u"§7- {}{} {} <= {:.2f}".format(color, mapDisplay, playerRecord, record)) + if satisfied and levelEnough and not finished: + available = True #满足条件,标记可用 + + + lore.append(u"§f") + if not levelEnough: + lore.append(u"§6<< §c未达等级 §6>>") + goToMap = 0 #等级不够无需跳转 + elif finished: + lore.append(u"§6>> §a无需升级 §6<<") + goToMap = 0 #已经完成无需跳转 + elif available: + lore.append(u"§6>> §e点击升级 §6<<") + goToMap = 0 #满足条件,无需跳转 + else: + lore.append(u"§6>> §c未达要求,点击挑战 §6<<") + + amount = int((levelTarget - levelNeed) * 10) + if amount > 64: + amount = 64 + elif amount < 1: + amount = 1 + self.set(index, material, u"§b§l【§e§l{}§b§l】§f".format(display), *lore, + data="{}:{}:{}".format(available, itemNumber, goToMap), amount=amount) + self.spawnSeparators(45, 53) # 填充最后一行默认黑色染色玻璃板 + if self.page > 1: + self.set(45, Material.WHITE_STAINED_GLASS_PANE, u"§a上一页") # 上一页按钮 + buttonText = u"§e仅看可升级" if not self.showAvailableOnly else u"§e显示所有内容" + self.set(49, Material.YELLOW_STAINED_GLASS_PANE, buttonText) # 仅看可以升级的内容 + if self.page < self.totalPages: + self.set(53, Material.WHITE_STAINED_GLASS_PANE, u"§a下一页") # 下一页按钮 + + # 记录玩家当前页码和筛选设置 + playerPage[playerName] = { + "page": self.page, + "showAvailableOnly": self.showAvailableOnly + } + def onClick(self, e): + e.setCancelled(True) + clickInt = e.getSlot() + player = self.getPlayer() + playerName = player.getName() + playerPageInfo = playerPage.get(playerName, {"page": 1, "showAvailableOnly": False}) + currentPage = playerPageInfo["page"] + showAvailableOnly = playerPageInfo["showAvailableOnly"] + + if clickInt == 45 and currentPage > 1: + MenuLevel(player, page=currentPage - 1, showAvailableOnly=showAvailableOnly).openWithRender() + player.playSound(player, Sound.ITEM_BOOK_PAGE_TURN, 1.0, 1.0) + elif clickInt == 49: + MenuLevel(player, showAvailableOnly=not showAvailableOnly).openWithRender() + player.playSound(player, Sound.ITEM_BOOK_PAGE_TURN, 1.0, 1.0) + elif clickInt == 53 and currentPage < self.totalPages: + MenuLevel(player, page=currentPage + 1, showAvailableOnly=showAvailableOnly).openWithRender() + player.playSound(player, Sound.ITEM_BOOK_PAGE_TURN, 1.0, 1.0) + elif clickInt < 45: + item = e.getCurrentItem() + if item is not None: + guiValue = getGUIValue(item) + if guiValue: + available, itemNumber, goToMap = guiValue.split(':') + playerData = PlayerDataManager(playerName) + if int(available) == 1: # 如果该收藏品可用 + levelTarget = levelManager.getLevelTarget(itemNumber) + playerData.setLevel(levelTarget) #升级 + player.sendMessage(u"§a升级成功!") + player.playSound(player, Sound.ENTITY_PLAYER_LEVELUP, 1.0, 1.0) + else: #如果不可升级 + if int(goToMap) != 0: + playerManager = PlayerManager(player) + playerManager.joinSingleRoom(goToMap) + else: + player.playSound(player, Sound.ENTITY_VILLAGER_NO, 1.0, 1.0) + return + MenuLevel(player, showAvailableOnly=showAvailableOnly).openWithRender() +class MenuRanking(GUIController): + def __init__(self, player, page=1, rankingType=u"seasonScore", mapNum=1): + super(MenuRanking, self).__init__(player) + self.page = page + self.rankingType = rankingType + self.mapNum = mapNum + self.rankManager = RankManager() + display = MapManager(mapNum).getDisplay() + if rankingType == u"seasonScore": + self.setInfo("iceboat.ranking", u"§0排行榜 §1§l当季总分§0 - 第 {} 页".format(page)) + elif rankingType == u"single": + self.setInfo("iceboat.ranking", u"§0排行榜 §1§l{}§0[单人] - 第 {} 页".format(display, page)) + elif rankingType == u"multi": + self.setInfo("iceboat.ranking", u"§0排行榜 §1§l{}§0[多人] - 第 {} 页".format(display, page)) + elif rankingType == u"mapTimes": + self.setInfo("iceboat.ranking", u"§0排行榜 §1§l{}§0[次数] - 第 {} 页".format(display, page)) + else: + self.setInfo("iceboat.ranking", u"§0全服排名 - 第 {} 页".format(page)) + self.setGUIManager(guiManager) + def render(self): + player = self.getPlayer() + playerName = player.getName() + itemsPerPage = 45 # 每页显示 45 个物品,最后一行用于控制 + + mapManager = MapManager(self.mapNum) + mapDisplay = mapManager.getDisplay() + mapMaterial = mapManager.getMaterial() + + singleConfig = ps.config.loadConfig("iceboat/ranking/singleMapRecord.yml") + multiConfig = ps.config.loadConfig("iceboat/ranking/multiMapRecord.yml") + seasonScoreConfig = ps.config.loadConfig("iceboat/ranking/seasonScore.yml") + mapTimesConfig = ps.config.loadConfig("iceboat/ranking/mapTimes.yml") + levelConfig = ps.config.loadConfig("iceboat/ranking/level.yml") + totalMapTimesConfig = ps.config.loadConfig("iceboat/ranking/totalMapTimes.yml") + singleMapSection = singleConfig.get(str(self.mapNum)) + multiMapSection = multiConfig.get(str(self.mapNum)) + mapTimesSection = mapTimesConfig.get(str(self.mapNum)) + + + playersConfig = ps.config.loadConfig("iceboat/players.yml") + players = list(playersConfig.getKeys(False)) + players.sort(key=lambda x: str(x)) + + def getScoreKey(x, config): + playerSection = config.get(x) + if playerSection is not None: + score = playerSection.get('score') + else: + if self.rankingType in [u"seasonScore", u"mapTimes"]: + score = 0 + else: + score = 99999 #一天最多86400秒 + return score + if self.rankingType == u"seasonScore": + players.sort(key=lambda x: getScoreKey(x, seasonScoreConfig), reverse=True) + if self.rankingType == u"single": + if singleMapSection is not None: + players.sort(key=lambda x: getScoreKey(x, singleMapSection)) + elif self.rankingType == u"multi": + if multiMapSection is not None: + players.sort(key=lambda x: getScoreKey(x, multiMapSection)) + elif self.rankingType == u"mapTimes": + if mapTimesSection is not None: + players.sort(key=lambda x: getScoreKey(x, mapTimesSection), reverse=True) + else: + if seasonScoreConfig is not None: + players.sort(key=lambda x: getScoreKey(x, seasonScoreConfig), reverse=True) + + self.totalPages = (len(players) + itemsPerPage - 1) // itemsPerPage + startIndex = (self.page - 1) * itemsPerPage + endIndex = startIndex + itemsPerPage + currentPagePlayers = players[startIndex:endIndex] + + for index, name in enumerate(currentPagePlayers): + if singleMapSection is None: + singleScore = u"暂无" + else: + singlePlayer = singleMapSection.get(name) + if singlePlayer is None: + singleScore = u"暂无" + else: + singleScore = singlePlayer.get('score') + + if multiMapSection is None: + multiScore = u"暂无" + else: + multiPlayer = multiMapSection.get(name) + if multiPlayer is None: + multiScore = u"暂无" + else: + multiScore = multiPlayer.get('score') + + playerDataManager = PlayerDataManager(name) + seasonMapScore = playerDataManager.getSeasonBestMapRecord(self.mapNum) + seasonScore = playerDataManager.getSeasonScore() + + if seasonMapScore == 0: + seasonMapScore = u"暂无" + + if mapTimesSection is None: + mapTimesScore = 0 + else: + mapTimesPlayer = mapTimesSection.get(name) + if mapTimesPlayer is None: + mapTimesScore = 0 + else: + mapTimesScore = mapTimesPlayer.get('score') + + if levelConfig is None: + levelScore = 0 + else: + levelPlayer = levelConfig.get(name) + if levelPlayer is None: + levelScore = 0 + else: + levelScore = levelPlayer.get('score') + + if totalMapTimesConfig is None: + totalMapTimesScore = 0 + else: + totalMapTimesPlayer = totalMapTimesConfig.get(name) + if totalMapTimesPlayer is None: + totalMapTimesScore = 0 + else: + totalMapTimesScore = totalMapTimesPlayer.get('score') + + rank = startIndex + index + 1 + + lore = [] + noRank = False + if self.rankingType == u'single': + if singleScore == u"暂无": + noRank = True + elif self.rankingType == u'multi': + if multiScore == u"暂无": + noRank = True + elif self.rankingType == u'mapTimes': + if mapTimesScore == 0: + noRank = True + elif seasonScore == 0: + noRank = True + + if noRank: + lore.append(u"§7---- §c暂无排名 §7----") + else: + lore.append(u"§7---- §a第 §e{} §a名 §7----".format(rank)) + lore.append(u"§e当季总分:§f{:.2f}".format(seasonScore)) + lore.append(u"§e驾照等级:§f{}".format(levelScore)) + lore.append(u"§e总完成次数:§f{}".format(totalMapTimesScore)) + + if self.rankingType != u"seasonScore": + lore.append(u"§f") + if singleScore == u"暂无": + lore.append(u"§e单人纪录:§f{}".format(singleScore)) + else: + lore.append(u"§e单人纪录:§f{:.2f}".format(singleScore)) + if multiScore == u"暂无": + lore.append(u"§e多人纪录:§f{}".format(multiScore)) + else: + lore.append(u"§e多人纪录:§f{:.2f}".format(multiScore)) + if seasonMapScore == u"暂无": + lore.append(u"§e当季纪录:§f{}".format(seasonMapScore)) + else: + lore.append(u"§e当季纪录:§f{:.2f}".format(seasonMapScore)) + lore.append(u"§e完成次数:§f{}".format(mapTimesScore)) + + try: + material = playerHeadsAPI.getPlayerHead(name) + except Exception: + material = Material.PLAYER_HEAD + self.set(index, material, u"§b§l【§e§l{}§b§l】".format(name), *lore) + + self.spawnSeparators(45, 53) # 填充最后一行默认黑色染色玻璃板 + + if self.page > 1: + self.set(45, Material.WHITE_STAINED_GLASS_PANE, u"§a上一页") # 上一页按钮 + if self.page < self.totalPages: + self.set(53, Material.WHITE_STAINED_GLASS_PANE, u"§a下一页") # 下一页按钮 + + playerSeasonScore = rankManager.getSeasonScoreRank(playerName) + playerSingleMapRecord = rankManager.getSingleMapRecordRank(playerName, self.mapNum) + playerMultiMapRecord = rankManager.getMultiMapRecordRank(playerName, self.mapNum) + playerMapTimes = rankManager.getMapTimesRank(playerName, self.mapNum) + try: + mapMaterial = getattr(Material, mapMaterial) + except AttributeError: + mapMaterial = Material.MAP + typeButtons = { + 47: (u"按当季总分排序", Material.SKULL_BANNER_PATTERN, u"seasonScore",[u"{}".format(u"§7您的当季总分排在第 §f{} §7名".format(playerSeasonScore) if playerSeasonScore != 0 else u'§7您暂无当季总分的排名')]), + 48: (u"按单人纪录排序", Material.NETHERITE_INGOT, u"single", [u"{}".format(u"§7您在{}§7{}".format(mapDisplay, u"的单人纪录排在第 §f{} §7名".format(playerSingleMapRecord) if playerSingleMapRecord != 0 else u'暂无单人纪录的排名') if self.rankingType != u"seasonScore" else u"§7当前排序方法无需设置地图")]), + 49: (u"按多人纪录排序", Material.NETHERITE_SCRAP, u"multi", [u"{}".format(u"§7您在{}§7{}".format(mapDisplay, u"的多人纪录排在第 §f{} §7名".format(playerMultiMapRecord) if playerMultiMapRecord != 0 else u'暂无多人纪录的排名') if self.rankingType != u"seasonScore" else u"§7当前排序方法无需设置地图")]), + 50: (u"按地图完成次数排序", Material.CLOCK, u"mapTimes", [u"{}".format(u"§7您在{}§7{}".format(mapDisplay, u"的完成次数排在第 §f{} §7名".format(playerMapTimes) if playerMapTimes != 0 else u'暂无完成次数的排名') if self.rankingType != u"seasonScore" else u"§7当前排序方法无需设置地图")]), + 51: (u"§f点击打开地图切换页面", mapMaterial, None, [u"{}".format(u"§7修改需要查询排名的地图" if self.rankingType == u"seasonScore" else u"§7当前选中的地图为{}".format(mapDisplay))]), + } + + for slot, (text, material, type, lore) in typeButtons.items(): + current = u"§e§l当前页面" if type == self.rankingType else u"§f" + self.set(slot, material, u"§6>> {}{} §6<<".format(current, text), *lore) + # 记录玩家当前页码和筛选设置 + playerPage[playerName] = { + "page": self.page, + "rankingType": self.rankingType, + "mapNum": self.mapNum + } + def onClick(self, e): + e.setCancelled(True) + clickInt = e.getSlot() + player = self.getPlayer() + playerName = player.getName() + playerPageInfo = playerPage.get(playerName, {"page": 1, "rankingType": u"single", "mapNum": 1}) + currentPage = playerPageInfo["page"] + rankingType = playerPageInfo["rankingType"] + mapNum = playerPageInfo["mapNum"] + + if clickInt == 45 and currentPage > 1: + MenuRanking(player, page=currentPage - 1, rankingType=rankingType, mapNum=mapNum).openWithRender() + player.playSound(player, Sound.ITEM_BOOK_PAGE_TURN, 1.0, 1.0) + elif clickInt in [47, 48, 49, 50]: + typeMapping = {47:u"seasonScore", 48: u"single", 49: u"multi", 50: u"mapTimes"} + newType = typeMapping[clickInt] + MenuRanking(player, rankingType=newType, mapNum=mapNum).openWithRender() + player.playSound(player, Sound.ITEM_BOOK_PAGE_TURN, 1.0, 1.0) + elif clickInt == 51: + MenuRankingMapSelect(player).openWithRender() + player.playSound(player, Sound.ITEM_BOOK_PAGE_TURN, 1.0, 1.0) + elif clickInt == 53 and currentPage < self.totalPages: + MenuRanking(player, page=currentPage + 1, rankingType=rankingType, mapNum=mapNum).openWithRender() + player.playSound(player, Sound.ITEM_BOOK_PAGE_TURN, 1.0, 1.0) +class MenuRankingMapSelect(GUIController):# 单人房间菜单 + def __init__(self, player, page=1, sortMode="default", isSeason=False): + super(MenuRankingMapSelect, self).__init__(player) + self.page = page + self.sortMode = sortMode + self.isSeason = isSeason + self.setInfo("iceboat.ranking", u"§0排行查询地图选择 - 第 {} 页".format(page)) + self.setGUIManager(guiManager) + def render(self): + player = self.getPlayer() + playerName = player.getName() + mapNumbers = [] + if self.isSeason: + mapNumbers = seasonManager.getSeasonMaps() + else: + mapNumbers = mapsManager.getMapsNum() + # 根据排序模式排序地图 + if self.sortMode == "descDifficulty": + mapNumbers.sort(key=lambda num: float(ps.config.loadConfig(u"iceboat/maps/{}.yml".format(num)).get("difficulty", 0)), reverse=True) + elif self.sortMode == "ascDifficulty": + mapNumbers.sort(key=lambda num: float(ps.config.loadConfig(u"iceboat/maps/{}.yml".format(num)).get("difficulty", 0))) + elif self.sortMode == "descNumber": + mapNumbers.sort(key=lambda num: int(num), reverse=True) + elif self.sortMode == "ascNumber": + mapNumbers.sort(key=lambda num: int(num)) + + itemsPerPage = 45 # 每页显示 45 个物品,最后一行用于控制 + self.totalPages = (len(mapNumbers) + itemsPerPage - 1) // itemsPerPage + + startIndex = (self.page - 1) * itemsPerPage + endIndex = startIndex + itemsPerPage + currentPageItems = mapNumbers[startIndex:endIndex] + + #创建物品 + for index, mapNumber in enumerate(currentPageItems): # 为每个地图创建一个物品 + mapFilePath = u"iceboat/maps/{}.yml".format(mapNumber) + mapConfig = ps.config.loadConfig(mapFilePath) + displayName = mapConfig.get("display", u"§b§l【§e§l地图 {}§b§l】§f".format(mapNumber)) + material = mapConfig.get("material", "PAPER") + lap = mapConfig.get("lap", "未知") + + mapManager = MapManager(mapNumber) + difficulty = mapManager.getDifficulty() + difficultySuffix = mapManager.getDifficultySuffix() + + try: + material = getattr(Material, material) + except AttributeError: + material = Material.PAPER + + self.set(index, material, u"§b§l【§e§l{}§b§l】§f".format(displayName), + u"§e序号: §f{}".format(mapNumber), + u"§e难度: §f{} {}".format(difficulty, difficultySuffix), + u"§e圈数: §f{}".format(lap), + "", + u"§6>> §a点击选择 §6<<", + data=str(mapNumber) + ) + + self.spawnSeparators(45, 53) # 填充最后一行默认黑色染色玻璃板 + if self.page > 1: + self.set(45, Material.WHITE_STAINED_GLASS_PANE, u"§a上一页") # 上一页按钮 + self.set(47, Material.LIME_STAINED_GLASS_PANE, u"§a按序号升序") # 序号升序按钮 + self.set(48, Material.ORANGE_STAINED_GLASS_PANE, u"§c按序号降序") # 序号降序按钮 + buttonText = u"§e仅看当季图池" if not self.isSeason else u"§e显示所有地图" + self.set(49, Material.YELLOW_STAINED_GLASS_PANE, buttonText) # 当季图池按钮 + self.set(50, Material.GREEN_STAINED_GLASS_PANE, u"§a按难度升序")# 难度升序按钮 + self.set(51, Material.RED_STAINED_GLASS_PANE, u"§c按难度降序") # 难度降序按钮 + if self.page < self.totalPages: + self.set(53, Material.WHITE_STAINED_GLASS_PANE, u"§a下一页") # 下一页按钮 + # 记录玩家当前页码和排序模式 + playerPage[playerName] = { + "page": self.page, + "sortMode": self.sortMode, + "isSeason": self.isSeason + } + def onClick(self, e): + e.setCancelled(True) + clickInt = e.getSlot() + player = self.getPlayer() + if clickInt == 45 and self.page > 1: + MenuRankingMapSelect(player, page=self.page - 1, sortMode=self.sortMode, isSeason=self.isSeason).openWithRender() + player.playSound(player, Sound.ITEM_BOOK_PAGE_TURN, 1.0, 1.0) + elif clickInt == 47: + MenuRankingMapSelect(player, sortMode="ascNumber", isSeason=self.isSeason).openWithRender() + player.playSound(player, Sound.UI_CARTOGRAPHY_TABLE_TAKE_RESULT, 1.0, 1.0) + elif clickInt == 48: + MenuRankingMapSelect(player, sortMode="descNumber", isSeason=self.isSeason).openWithRender() + player.playSound(player, Sound.UI_CARTOGRAPHY_TABLE_TAKE_RESULT, 1.0, 1.0) + elif clickInt == 49: + MenuRankingMapSelect(player, sortMode=self.sortMode, isSeason=not self.isSeason).openWithRender() + player.playSound(player, Sound.ITEM_BOOK_PAGE_TURN, 1.0, 1.0) + elif clickInt == 50: + MenuRankingMapSelect(player, sortMode="ascDifficulty", isSeason=self.isSeason).openWithRender() + player.playSound(player, Sound.UI_CARTOGRAPHY_TABLE_TAKE_RESULT, 1.0, 1.0) + elif clickInt == 51: + MenuRankingMapSelect(player, sortMode="descDifficulty", isSeason=self.isSeason).openWithRender() + player.playSound(player, Sound.UI_CARTOGRAPHY_TABLE_TAKE_RESULT, 1.0, 1.0) + elif clickInt == 53 and self.page < self.totalPages: + MenuRankingMapSelect(player, page=self.page + 1, sortMode=self.sortMode, isSeason=self.isSeason).openWithRender() + player.playSound(player, Sound.ITEM_BOOK_PAGE_TURN, 1.0, 1.0) + elif clickInt < 45: + item = e.getCurrentItem() + mapNumber = getGUIValue(item) + if mapNumber is not None: + MenuRanking(player, mapNum=int(mapNumber), rankingType=u"single").openWithRender() + player.playSound(player, Sound.ITEM_BOOK_PAGE_TURN, 1.0, 1.0) +def onInventoryClick(e): # 菜单点击 + player = e.getWhoClicked() + worldName = player.getWorld().getName() + if worldName != WORLD_NAME: # 仅在冰船世界启用 + return + inv = e.getClickedInventory() + if inv is None: + return + invHolder = inv.getHolder() + if isGUI(invHolder): + guiManager.get(e.getWhoClicked(), e.getClickedInventory().getHolder().getName()).onClick(e) # 菜单控制 + return + cancelShiftClickFromPlayerInventory(e) + + if inv.getType().name() == 'PLAYER': #处理玩家背包点击事件 + if player.getGameMode() == GameMode.CREATIVE: #不影响创造模式玩家 + return + e.setCancelled(True) # 阻止玩家调整背包物品 + clickedItem = e.getCurrentItem() + if clickedItem: + itemType = clickedItem.getType() + if itemType == Material.AIR: #不处理点击空气 + return + playerManager = PlayerManager(player) + if itemType == Material.MAGMA_CREAM: #无论何时都可以尝试退出 + iceboatCommand(player, "ib", ["leave", playerManager.playerName]) + return + elif itemType == Material.JUKEBOX: #无论何时都可以关闭音乐 + player.stopSound(SoundCategory.RECORDS) + player.sendMessage(u"§a当前音乐已关闭") + player.playSound(player, Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0, 1.0) + return + elif playerManager.inAnyRoom() and not playerManager.inLobby(): #房间内禁止其它操作 + player.sendMessage(u"§c游戏内禁止该操作") + player.playSound(player, Sound.ENTITY_VILLAGER_NO, 1.0, 1.0) + return + else: + if itemType == Material.SLIME_BALL: #打开单人模式菜单 + iceboatMenuCommand(player, "", ["single"]) + return + elif itemType == Material.NETHER_STAR: #快速进入上次游玩地图的单人模式 + mapNumber = int(getGUIValue(clickedItem)) + playerManager.joinSingleRoom(mapNumber) + elif itemType == Material.CHEST: #打开收集品菜单 + MenuCollection(player).openWithRender() + player.playSound(player, Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0, 1.0) + return + elif itemType == Material.PLAYER_HEAD: #玩家信息显示 + playerManager.updateInventoryPlayerInfo() + player.playSound(player, Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0, 1.0) + return + elif itemType == Material.SPYGLASS: #粒子预览页面 + MenuPreviewing(player).openWithRender() + player.playSound(player, Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0, 1.0) + return + elif itemType == Material.PAPER: #驾照等级页面 + MenuLevel(player).openWithRender() + player.playSound(player, Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0, 1.0) + return + elif itemType == Material.SKULL_BANNER_PATTERN: #排名查询页面 + MenuRanking(player).openWithRender() + player.playSound(player, Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0, 1.0) + return + + playerData = PlayerDataManager(playerManager.playerName) #玩家设置修改 + if itemType == Material.FIREWORK_STAR: #粒子模式 + result = playerData.toggleModeBoolean("particleMode") + if result: + player.sendMessage(u"§a自定义粒子模式已开启") + player.playSound(player, Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0, 1.0) + else: + player.sendMessage(u"§6自定义粒子模式已关闭") + player.playSound(player, Sound.BLOCK_NOTE_BLOCK_BASS, 1.0, 1.0) + elif itemType == Material.OAK_HANGING_SIGN: #详细播报模式 + result = playerData.toggleModeBoolean("detailMode") + if result: + player.sendMessage(u"§a详细播报模式已开启") + player.playSound(player, Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0, 1.0) + else: + player.sendMessage(u"§6详细播报模式已关闭") + player.playSound(player, Sound.BLOCK_NOTE_BLOCK_BASS, 1.0, 1.0) + elif itemType == Material.BEACON: #广播模式 + result = playerData.toggleModeBoolean("broadcastMode") + if result: + player.sendMessage(u"§a成绩广播模式已开启") + player.playSound(player, Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0, 1.0) + else: + player.sendMessage(u"§6成绩广播模式已关闭") + player.playSound(player, Sound.BLOCK_NOTE_BLOCK_BASS, 1.0, 1.0) + elif itemType == Material.NOTE_BLOCK: #音乐模式 + result = playerData.toggleModeBoolean("musicMode") + if result: + player.sendMessage(u"§a音乐模式已开启") + player.playSound(player, Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0, 1.0) + else: + player.sendMessage(u"§6音乐模式已关闭") + player.playSound(player, Sound.BLOCK_NOTE_BLOCK_BASS, 1.0, 1.0) +def onInventoryClose(e): # 菜单关闭 + inv = e.getInventory() + if isGUI(inv.getHolder()): + guiManager.remove(e.getPlayer()) # 关闭菜单时清空存储的控制器 +def stop(): # 程序卸载时 + closeGuiForAll() # 关菜单 +ps.listener.registerListener(onInventoryClick, InventoryClickEvent) +ps.listener.registerListener(onInventoryClose, InventoryCloseEvent) +ps.listener.registerListener(denyAllInventoryDrag, InventoryDragEvent) + + +# 机制修改 ----------------------------------------------------------------------------------------- +def onPlayerDropItem(event): # 阻止玩家丢弃物品 + player = event.getPlayer() + worldName = player.getWorld().getName() + if worldName == WORLD_NAME and player.getGameMode() != GameMode.CREATIVE: + event.setCancelled(True) +def preventItemFrameDamage(event): # 阻止玩家打掉展示框 + if event.getEntityType() == EntityType.ITEM_FRAME: + damager = event.getDamager() + worldName = damager.getWorld().getName() + if worldName == WORLD_NAME: + if isinstance(damager, Player): + if damager.getGameMode() != GameMode.CREATIVE: + event.setCancelled(True) +def preventItemFrameRotation(event): # 阻止玩家旋转展示框 + if event.getRightClicked().getType() == EntityType.ITEM_FRAME: + player = event.getPlayer() + worldName = player.getWorld().getName() + if worldName == WORLD_NAME: + if player.getGameMode() != GameMode.CREATIVE: + event.setCancelled(True) +def onPlayerSwapHandItems(event): # 阻止切换主副手 + player = event.getPlayer() + worldName = player.getWorld().getName() + if worldName == WORLD_NAME: + if player.getGameMode() != GameMode.CREATIVE: + event.setCancelled(True) +def playerExitVehicle(event): # 下船机制变革 + exited = event.getExited() #获取离开的实体 + if isinstance(exited, Player): # 只处理玩家退出情况 + worldName = exited.getWorld().getName() + if worldName == WORLD_NAME: #仅作用于冰船世界 + boat = event.getVehicle() #获取船只 + if isinstance(boat, Boat): # 只处理玩家退出船只的情况 + playerManager = PlayerManager(exited) + if playerManager.delayExit(): + event.setCancelled(True) +def onPlayerInteract(event): # 玩家交互处理:禁止修改告示牌、物品栏使用 + player = event.getPlayer() + worldName = player.getWorld().getName() + if worldName == WORLD_NAME and event.getHand() == HAND: #仅冰船世界生效、不重复触发副手 + action = event.getAction() + if action in [Action.RIGHT_CLICK_AIR, Action.RIGHT_CLICK_BLOCK]: # 检查是否为右键点击告示牌 + clickedBlock = event.getClickedBlock() + if action == Action.RIGHT_CLICK_BLOCK: #右键方块 + blockType = clickedBlock.getType() + if blockType in SIGNS: + if not (player.isOp() and player.isSneaking()): # 检查玩家是否拥有 OP 权限且蹲下 + event.setCancelled(True) + return #点击告示牌后不触发物品栏检测 + elif blockType == Material.LECTERN: #不影响阅读讲台 + return + item = player.getInventory().getItemInMainHand() + itemType = item.getType() + if itemType == Material.AIR: #不处理点击空气 + return + #处理物品栏右键 + playerManager = PlayerManager(player) + if itemType == Material.SLIME_BALL: #打开单人模式菜单 + iceboatMenuCommand(playerManager.player, "", ["single"]) + elif itemType == Material.NETHER_STAR: #快速进入上次游玩地图的单人模式 + if playerManager.inAnyRoom() and not playerManager.inLobby(): #房间内禁止其它操作 + return + mapNumber = int(getGUIValue(item)) + if mapNumber is not None: + playerManager.joinSingleRoom(mapNumber) + elif itemType == Material.MAGMA_CREAM: #离开房间 + iceboatCommand(playerManager.player, "ib", ["leave", playerManager.playerName]) + else: + return + event.setCancelled(True) +def onEntityLoad(event): # 加载时删除空船 + for entity in event.getEntities(): + if isinstance(entity, Boat): + worldName = entity.getWorld().getName() + if worldName == WORLD_NAME: #仅作用于冰船世界 + if entity.isEmpty(): # 检测空船 + entity.remove() +def cancelPlayerDamageInIceboatWorld(event): # 取消玩家受伤事件 + entity = event.getEntity() + worldName = entity.getWorld().getName() + if worldName == WORLD_NAME: + if isinstance(entity, Player): + if event.getCause() == EntityDamageEvent.DamageCause.VOID: + playerManager = PlayerManager(entity) + playerManager.teleportToLobby() + event.setCancelled(True) +def onPlayerQuit(event): # 玩家退出处理 + player = event.getPlayer() + worldName = player.getWorld().getName() + if worldName == WORLD_NAME: #仅作用于冰船世界 + playerManager = PlayerManager(player) + playerManager.removeBoat() + playerManager.outGame() + playerManager.removePlayerFromRoom() +def onPlayerJoin(event): # 玩家加入处理 + player = event.getPlayer() + worldName = player.getWorld().getName() + if worldName == WORLD_NAME: #仅作用于冰船世界 + playerManager = PlayerManager(player) + playerManager.removeBoat() + playerManager.outGame() + playersManager.addPlayerAsync() + ps.scheduler.runTaskLater(lambda : playerManager.updateInventory(), 1) #延迟设置玩家背包 +def onPlayerTeleportEvent(event): # 玩家进入冰船世界处理 + toWorldName = event.getTo().getWorld().getName() + fromWorldName = event.getFrom().getWorld().getName() + if toWorldName == WORLD_NAME: #进入冰船世界 + if fromWorldName == WORLD_NAME: #仅处理玩家从其他世界进入冰船世界的情况 + return + player = event.getPlayer() + playerManager = PlayerManager(player) + playersManager.addPlayerAsync() + ps.scheduler.runTaskLater(lambda : playerManager.updateInventory(), 1) #延迟设置玩家背包 + if fromWorldName == WORLD_NAME: #离开冰船世界 + if toWorldName == WORLD_NAME: #仅处理玩家从冰船世界离开冰船世界的情况 + return + player = event.getPlayer() + playerManager = PlayerManager(player) + playerManager.removeBoat() + playerManager.outGame() + playerManager.removePlayerFromRoom() +def onArmorStandManipulate(event): #取消盔甲架交互 + armorStand = event.getRightClicked() + worldName = armorStand.getWorld().getName() + if worldName == WORLD_NAME: #仅作用于冰船世界 + event.setCancelled(True) + +ps.listener.registerListener(onPlayerInteract, PlayerInteractEvent) +ps.listener.registerListener(onPlayerDropItem, PlayerDropItemEvent) +ps.listener.registerListener(preventItemFrameDamage, EntityDamageByEntityEvent) +ps.listener.registerListener(preventItemFrameRotation, PlayerInteractEntityEvent) +ps.listener.registerListener(onPlayerSwapHandItems, PlayerSwapHandItemsEvent) +ps.listener.registerListener(playerExitVehicle, VehicleExitEvent) +ps.listener.registerListener(onEntityLoad, EntitiesLoadEvent) +ps.listener.registerListener(cancelPlayerDamageInIceboatWorld, EntityDamageEvent) +ps.listener.registerListener(onPlayerJoin, PlayerJoinEvent) +ps.listener.registerListener(onPlayerQuit, PlayerQuitEvent) +ps.listener.registerListener(onPlayerTeleportEvent, PlayerTeleportEvent) +ps.listener.registerListener(onArmorStandManipulate, PlayerArmorStandManipulateEvent) + + +# 重载处理 ----------------------------------------------------------------------------------------- +def reloadAll(): + world = worldManager.getWorld() #仅修改冰船世界 + roomsManager.resetAvailableRooms() #重置可用房间 + players = world.getPlayers() + for player in players: #检测所有玩家 + if player.getGameMode() == GameMode.CREATIVE: #不影响创造模式玩家 + continue + player.removePotionEffect(PotionEffectType.INVISIBILITY) #取消可能的隐身 + player.closeInventory() #关闭正在打开的菜单 + playerLoc = player.getLocation() + playerX = playerLoc.getX() + playerZ = playerLoc.getZ() + playerManager = PlayerManager(player) + playerManager.updateInventory() #重置玩家背包 + if not (-LOBBY_RADIUS < playerX < LOBBY_RADIUS) and not (-LOBBY_RADIUS < playerZ < LOBBY_RADIUS): #如果玩家在大厅内 + playerManager.removeBoat() + playerManager.teleportToLobby() + + entities = world.getEntities() + for entity in entities: #检测大厅实体 + if isinstance(entity, ArmorStand) or isinstance(entity, EntityType.ITEM_FRAME.getEntityClass()) or isinstance(entity, Boat): + entityLoc = entity.getLocation() + entityLocX = entityLoc.getX() + entityLocZ = entityLoc.getZ() + if -LOBBY_RADIUS < entityLocX < LOBBY_RADIUS and -LOBBY_RADIUS < entityLocZ < LOBBY_RADIUS: #如果实体在大厅内 + isValid = False #标记无效 + if isinstance(entity, ArmorStand): + for room in multiGames.values(): # 遍历所有多人游戏房间,检查盔甲架是否有效 + if entity in room.mapDisplayArmorStands: + isValid = True + break + elif isinstance(entity, EntityType.ITEM_FRAME.getEntityClass()): + for room in multiGames.values(): + if entity == room.mapDisplayItemFrame: # 遍历所有多人游戏房间,检查展示框是否有效 + isValid = True + break + elif isinstance(entity, Boat): + for preview in playersInParticlePreview.values(): # 遍历所有单人游戏房间,检查船是否有效 + if entity == preview.boat: + isValid = True + break + if not isValid: + entity.remove() + +reloadAll() +hallOfFameManager.updateHallOfFame() # 更新名人堂 \ No newline at end of file