diff --git a/Plugins/Mineplex.Core/src/mineplex/core/game/GameDisplay.java b/Plugins/Mineplex.Core/src/mineplex/core/game/GameDisplay.java index 413dbbc8f..ac7c74b16 100644 --- a/Plugins/Mineplex.Core/src/mineplex/core/game/GameDisplay.java +++ b/Plugins/Mineplex.Core/src/mineplex/core/game/GameDisplay.java @@ -107,6 +107,8 @@ public enum GameDisplay MOBA("Heroes of GWEN", Material.PRISMARINE, (byte)0, GameCategory.CLASSICS, 70, true), MOBATraining("Heroes of GWEN Training", Material.PRISMARINE, (byte)0, GameCategory.CLASSICS, 70, false), + BattleRoyale("Battle Royale", Material.DIAMOND_SWORD, (byte)0, GameCategory.EVENT, 72, false), + GemHunters("Gem Hunters", Material.EMERALD, (byte) 0, GameCategory.SURVIVAL, 71, false), Event("Mineplex Event", Material.CAKE, (byte)0, GameCategory.EVENT, 999, false), diff --git a/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/GameType.java b/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/GameType.java index 503422b10..c60f4c16c 100644 --- a/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/GameType.java +++ b/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/GameType.java @@ -9,6 +9,7 @@ import nautilus.game.arcade.game.games.alieninvasion.AlienInvasion; import nautilus.game.arcade.game.games.baconbrawl.BaconBrawl; import nautilus.game.arcade.game.games.barbarians.Barbarians; import nautilus.game.arcade.game.games.basketball.Basketball; +import nautilus.game.arcade.game.games.battleroyale.BattleRoyaleSolo; import nautilus.game.arcade.game.games.bossbattles.BossBattles; import nautilus.game.arcade.game.games.bouncyballs.BouncyBalls; import nautilus.game.arcade.game.games.bridge.Bridge; @@ -237,6 +238,8 @@ public enum GameType MOBA(MobaClassic.class, GameDisplay.MOBA), MOBATraining(MobaTraining.class, GameDisplay.MOBATraining), + BattleRoyale(BattleRoyaleSolo.class, GameDisplay.BattleRoyale), + Event(EventGame.class, GameDisplay.Event, new GameType[]{ GameType.BaconBrawl, GameType.Barbarians, GameType.Bridge, GameType.Build, GameType.Build, GameType.Cards, GameType.CastleSiege, GameType.ChampionsDominate, GameType.ChampionsTDM, GameType.Christmas, diff --git a/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/games/battleroyale/BattleRoyale.java b/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/games/battleroyale/BattleRoyale.java new file mode 100644 index 000000000..e1cbaab7c --- /dev/null +++ b/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/games/battleroyale/BattleRoyale.java @@ -0,0 +1,347 @@ +package nautilus.game.arcade.game.games.battleroyale; + +import mineplex.core.common.util.C; +import mineplex.core.common.util.F; +import mineplex.core.common.util.UtilAlg; +import mineplex.core.common.util.UtilBlock; +import mineplex.core.common.util.UtilMath; +import mineplex.core.common.util.UtilParticle; +import mineplex.core.common.util.UtilParticle.ParticleType; +import mineplex.core.common.util.UtilParticle.ViewDist; +import mineplex.core.common.util.UtilPlayer; +import mineplex.core.common.util.UtilTextBottom; +import mineplex.core.common.util.UtilTime; +import mineplex.core.common.util.UtilWorld; +import mineplex.core.recharge.Recharge; +import mineplex.core.updater.UpdateType; +import mineplex.core.updater.event.UpdateEvent; +import mineplex.minecraft.game.core.damage.CustomDamageEvent; +import nautilus.game.arcade.ArcadeManager; +import nautilus.game.arcade.GameType; +import nautilus.game.arcade.events.GameStateChangeEvent; +import nautilus.game.arcade.game.Game; +import nautilus.game.arcade.game.modules.chest.ChestLootModule; +import nautilus.game.arcade.game.modules.chest.ChestLootPool; +import nautilus.game.arcade.game.modules.compass.CompassModule; +import nautilus.game.arcade.kit.Kit; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.WorldBorder; +import org.bukkit.entity.Chicken; +import org.bukkit.entity.EnderDragon; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageEvent.DamageCause; +import org.bukkit.event.entity.EntityExplodeEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +public abstract class BattleRoyale extends Game +{ + + private static final long PREPARE_TIME = TimeUnit.SECONDS.toMillis(20); + private static final int MAX_CORD = 1000; + private static final int SPAWN_Y = 130; + private static final int WORLD_SIZE_BUFFER = 300; + private static final int MIN_DISTANCE_APART_FOR_SPAWNS_SQUARED = 100; + private static final long MAX_DRAGON_TIME = TimeUnit.SECONDS.toMillis(60); + private static final long BORDER_TIME = TimeUnit.MINUTES.toSeconds(20); + + private final Map _playerData = new HashMap<>(70); + + protected WorldBorder _border; + + public BattleRoyale(ArcadeManager manager, GameType gameType, Kit[] kits, String[] gameDesc) + { + super(manager, gameType, kits, gameDesc); + + PrepareTime = PREPARE_TIME; + PrepareFreeze = false; + Damage = false; + DamageTeamSelf = true; + DeathDropItems = true; + QuitDropItems = true; + HungerSet = 20; + DeathTeleport = false; + WorldChunkUnload = true; + + ItemDrop = true; + ItemPickup = true; + + StrictAntiHack = true; + InventoryClick = true; + InventoryOpenBlock = true; + InventoryOpenChest = true; + + new CompassModule() + .register(this); + + new ChestLootModule() + .registerChestType("Standard", + + // Swords + new ChestLootPool() + .addItem(new ItemStack(Material.WOOD_SWORD)) + .addItem(new ItemStack(Material.STONE_SWORD)) + , + // Armour + new ChestLootPool() + .addItem(new ItemStack(Material.LEATHER_HELMET)) + .addItem(new ItemStack(Material.LEATHER_CHESTPLATE)) + .addItem(new ItemStack(Material.LEATHER_LEGGINGS)) + .addItem(new ItemStack(Material.LEATHER_BOOTS)) + ) + .register(this); + } + + @Override + public void ParseData() + { + ChestLootModule chestModule = getModule(ChestLootModule.class); + + chestModule.setSpawnsForType("Standard", WorldData.GetDataLocs("ORANGE")); + + WorldData.MinX = -MAX_CORD; + WorldData.MinZ = -MAX_CORD; + WorldData.MaxX = MAX_CORD; + WorldData.MaxZ = MAX_CORD; + + _border = WorldData.World.getWorldBorder(); + } + + @EventHandler + public void prepare(GameStateChangeEvent event) + { + if (event.GetState() != GameState.Prepare) + { + return; + } + + _border.setCenter(getRandomCenter()); + _border.setSize(MAX_CORD * 2); + + List toTeleport = GetPlayers(true); + AtomicInteger index = new AtomicInteger(); + + Manager.runSyncTimer(new BukkitRunnable() + { + @Override + public void run() + { + if (index.get() >= toTeleport.size()) + { + cancel(); + return; + } + + Player player = toTeleport.get(index.get()); + + if (player == null || !player.isOnline() || UtilPlayer.isSpectator(player)) + { + return; + } + + Location spawn = null; + int attempts = 0; + int initialXZ = 0; + + while (spawn == null && attempts++ < 20) + { + if (attempts > 10) + { + initialXZ += 20; + } + + spawn = getPlayerSpawn(initialXZ); + } + + // Couldn't create a spawn, this should never happen and is pretty much impossible + if (spawn == null) + { + cancel(); + SetState(GameState.Dead); + return; + } + + Location goal = spawn.clone(); + + goal.setX(-spawn.getX()); + goal.setZ(-spawn.getZ()); + + Bukkit.broadcastMessage(player.getName() + " -> " + UtilWorld.locToStrClean(spawn) + " after " + attempts + " attempts"); + BattleRoyalePlayer royalePlayer = new BattleRoyalePlayer(Manager, player, spawn, goal); + _playerData.put(player, royalePlayer); + + index.getAndIncrement(); + } + }, 100, 2); + } + + private Location getPlayerSpawn(int initialXZ) + { + // Calculate where a player should spawn + int max = MAX_CORD - WORLD_SIZE_BUFFER; + int x = initialXZ; + int z = initialXZ; + boolean varyX = UtilMath.random.nextBoolean(); + boolean sign = UtilMath.random.nextBoolean(); + + if (varyX) + { + x += UtilMath.rRange(-max, max); + z += sign ? max : -max; + } + else + { + x += sign ? max : -max; + z += UtilMath.rRange(-max, max); + } + + Location location = new Location(WorldData.World, x, SPAWN_Y, z); + + // Check to make sure no players are nearby + for (BattleRoyalePlayer other : _playerData.values()) + { + if (UtilMath.offsetSquared(location, other.getLocation()) < MIN_DISTANCE_APART_FOR_SPAWNS_SQUARED) + { + return null; + } + } + + location.setYaw(UtilAlg.GetYaw(UtilAlg.getTrajectory(location, GetSpectatorLocation()))); + return location; + } + + private Location getRandomCenter() + { + int attempts = 0; + + while (attempts++ < 20) + { + Location location = UtilAlg.getRandomLocation(GetSpectatorLocation(), 200, 0, 200); + + if (UtilBlock.airFoliage(UtilBlock.getHighest(location.getWorld(), location.getBlock()))) + { + return location; + } + } + + return SpectatorSpawn; + } + + @EventHandler + public void live(GameStateChangeEvent event) + { + if (event.GetState() != GameState.Live) + { + return; + } + + CreatureAllowOverride = true; + + _playerData.forEach((player, battleRoyalePlayer) -> + { + battleRoyalePlayer.removeCage(); + battleRoyalePlayer.spawnDragon(); + }); + + CreatureAllowOverride = false; + } + + @EventHandler + public void updateDragons(UpdateEvent event) + { + if (event.getType() != UpdateType.FAST || !IsLive()) + { + return; + } + + Iterator iterator = _playerData.keySet().iterator(); + + while (iterator.hasNext()) + { + Player player = iterator.next(); + BattleRoyalePlayer royalePlayer = _playerData.get(player); + + if (royalePlayer == null || !player.isOnline()) + { + iterator.remove(); + continue; + } + + EnderDragon dragon = royalePlayer.getDragon(); + Chicken chicken = royalePlayer.getChicken(); + + if (dragon == null || chicken == null) + { + continue; + } + + UtilTextBottom.display((player.getTicksLived() % 5 == 0 ? C.cGreenB : C.cWhiteB) + "PRESS YOUR SNEAK KEY TO DISMOUNT YOUR DRAGON", player); + if (dragon.getPassenger() == null || chicken.getPassenger() == null) + { + Recharge.Instance.useForce(player, "Fall Damage", TimeUnit.SECONDS.toMillis(10)); + UtilParticle.PlayParticleToAll(ParticleType.WITCH_MAGIC, player.getLocation(), 5, 5, 5, 0.01F, 100, ViewDist.NORMAL); + player.playSound(player.getLocation(), Sound.BLAZE_DEATH, 1, 0.6F); + dragon.remove(); + chicken.remove(); + iterator.remove(); + } + } + + if (!Damage && UtilTime.elapsed(GetStateTime(), MAX_DRAGON_TIME)) + { + _playerData.forEach((player, battleRoyalePlayer) -> + { + Recharge.Instance.useForce(player, "Fall Damage", TimeUnit.SECONDS.toMillis(10)); + player.sendMessage(F.main("Game", "You were too slow!")); + battleRoyalePlayer.getDragon().remove(); + battleRoyalePlayer.getChicken().remove(); + }); + _playerData.clear(); + + Announce(C.cRedB + "Grace Period Over!", false); + + for (Player player : Bukkit.getOnlinePlayers()) + { + player.playSound(player.getLocation(), Sound.ENDERDRAGON_GROWL, 1, 1); + } + + Damage = true; + HungerSet = -1; + _border.setSize(100, BORDER_TIME); + } + } + + @EventHandler + public void fallDamage(CustomDamageEvent event) + { + Player player = event.GetDamageePlayer(); + + if (player == null || event.GetCause() != DamageCause.FALL || Recharge.Instance.usable(player, "Fall Damage")) + { + return; + } + + event.SetCancelled("Dragon Fall"); + } + + @EventHandler(priority = EventPriority.LOWEST) + public void preventDragonExplosion(EntityExplodeEvent event) + { + if (event.getEntity() instanceof EnderDragon) + { + event.blockList().clear(); + } + } +} diff --git a/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/games/battleroyale/BattleRoyalePlayer.java b/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/games/battleroyale/BattleRoyalePlayer.java new file mode 100644 index 000000000..6ceff59dd --- /dev/null +++ b/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/games/battleroyale/BattleRoyalePlayer.java @@ -0,0 +1,132 @@ +package nautilus.game.arcade.game.games.battleroyale; + +import mineplex.core.common.Rank; +import mineplex.core.common.util.MapUtil; +import mineplex.core.common.util.UtilColor; +import mineplex.core.common.util.UtilEnt; +import nautilus.game.arcade.ArcadeManager; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.craftbukkit.v1_8_R3.entity.CraftEnderDragon; +import org.bukkit.entity.Chicken; +import org.bukkit.entity.EnderDragon; +import org.bukkit.entity.Player; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +import java.util.HashSet; +import java.util.Set; + +class BattleRoyalePlayer +{ + + private final Player _player; + private final Location _location; + private final Location _goal; + private final Set _cageBlocks; + private EnderDragon _dragon; + private Chicken _chicken; + + public BattleRoyalePlayer(ArcadeManager manager, Player player, Location location, Location goal) + { + _player = player; + _location = location; + _goal = goal; + _cageBlocks = new HashSet<>(); + + // Colour the cage based on the player's rank + Rank rank = manager.GetClients().Get(player).GetRank(); + byte data = UtilColor.chatColorToWoolData(rank.getColor()); + + // Build the cage + buildCage(data); + // Teleport the player to the cage + player.teleport(_location.add(0, 1, 0)); + } + + private void buildCage(byte colourData) + { + // Floor + for (int x = -2; x <= 2; x++) + { + for (int z = -2; z <= 2; z++) + { + _location.add(x, -1, z); + MapUtil.QuickChangeBlockAt(_location, Material.STAINED_GLASS, colourData); + _cageBlocks.add(_location); + _location.subtract(x, -1, z); + } + } + + // Roof + for (int x = -2; x <= 2; x++) + { + for (int z = -2; z <= 2; z++) + { + _location.add(x, 4, z); + MapUtil.QuickChangeBlockAt(_location, Material.STAINED_GLASS, colourData); + _cageBlocks.add(_location); + _location.subtract(x, 4, z); + } + } + + // Walls + for (int y = 0; y < 4; y++) + { + for (int x = -2; x <= 2; x++) + { + for (int z = -2; z <= 2; z++) + { + if (x != -2 && x != 2 && z != -2 && z != 2) + { + continue; + } + + _location.add(x, y, z); + MapUtil.QuickChangeBlockAt(_location, Material.STAINED_GLASS, colourData); + _cageBlocks.add(_location); + _location.subtract(x, y, z); + } + } + } + } + + public void removeCage() + { + for (Location location : _cageBlocks) + { + MapUtil.QuickChangeBlockAt(location, Material.AIR); + } + } + + public void spawnDragon() + { + _dragon = _location.getWorld().spawn(_location, EnderDragon.class); + UtilEnt.vegetate(_dragon); + UtilEnt.ghost(_dragon, true, false); + + _chicken = _location.getWorld().spawn(_location, Chicken.class); + _chicken.addPotionEffect(new PotionEffect(PotionEffectType.INVISIBILITY, Integer.MAX_VALUE, 0)); + + _dragon.setPassenger(_chicken); + _chicken.setPassenger(_player); + + ((CraftEnderDragon) _dragon).getHandle().setTargetBlock(_goal.getBlockX(), _goal.getBlockY(), _goal.getBlockZ()); + } + + public Location getLocation() + { + return _location; + } + + public EnderDragon getDragon() + { + return _dragon; + } + + public Chicken getChicken() + { + return _chicken; + } +} diff --git a/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/games/battleroyale/BattleRoyaleSolo.java b/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/games/battleroyale/BattleRoyaleSolo.java new file mode 100644 index 000000000..0e1fca782 --- /dev/null +++ b/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/games/battleroyale/BattleRoyaleSolo.java @@ -0,0 +1,151 @@ +package nautilus.game.arcade.game.games.battleroyale; + +import mineplex.core.common.util.C; +import mineplex.core.common.util.UtilWorld; +import mineplex.core.updater.UpdateType; +import mineplex.core.updater.event.UpdateEvent; +import nautilus.game.arcade.ArcadeManager; +import nautilus.game.arcade.GameType; +import nautilus.game.arcade.events.GameStateChangeEvent; +import nautilus.game.arcade.game.GameTeam; +import nautilus.game.arcade.game.games.moba.kit.KitPlayer; +import nautilus.game.arcade.kit.Kit; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class BattleRoyaleSolo extends BattleRoyale +{ + + private static final String[] DESCRIPTION = { + "Battle Royale!" + }; + + private GameTeam _players; + + public BattleRoyaleSolo(ArcadeManager manager) + { + super(manager, GameType.BattleRoyale, new Kit[] {new KitPlayer(manager)}, DESCRIPTION); + } + + @EventHandler + public void customTeamGeneration(GameStateChangeEvent event) + { + if (event.GetState() != GameState.Recruit) + return; + + _players = GetTeamList().get(0); + _players.SetColor(ChatColor.YELLOW); + _players.SetName("Players"); + } + + @Override + @EventHandler + public void ScoreboardUpdate(UpdateEvent event) + { + if (event.getType() != UpdateType.FAST || !InProgress()) + { + return; + } + + Scoreboard.writeNewLine(); + + Scoreboard.write(C.cYellow + C.Bold + "Players"); + if (_players.GetPlayers(true).size() > 10) + { + Scoreboard.write(String.valueOf( _players.GetPlayers(true).size())); + } + else + { + for (Player player : _players.GetPlayers(true)) + { + Scoreboard.write(player.getName()); + } + } + + Scoreboard.writeNewLine(); + + int size = (int) _border.getSize(); + Scoreboard.write(C.cRedB + "World Border"); + Scoreboard.write(UtilWorld.locToStrClean(_border.getCenter())); + Scoreboard.write(size + " Blocks Wide"); + + Scoreboard.draw(); + } + + @Override + public void EndCheck() + { + if (!IsLive()) + { + return; + } + + if (GetPlayers(true).size() <= 1) + { + List places = _players.GetPlacements(true); + + AnnounceEnd(places); + + if (places.size() >= 1) + { + AddGems(places.get(0), 20, "1st Place", false, false); + } + if (places.size() >= 2) + { + AddGems(places.get(1), 15, "2nd Place", false, false); + } + if (places.size() >= 3) + { + AddGems(places.get(2), 10, "3rd Place", false, false); + } + + for (Player player : GetPlayers(false)) + { + if (player.isOnline()) + { + AddGems(player, 10, "Participation", false, false); + } + } + + _border.setSize(10000); + SetState(GameState.End); + } + } + + @Override + public List getWinners() + { + if (GetState().ordinal() >= GameState.End.ordinal()) + { + List places = _players.GetPlacements(true); + + if (places.isEmpty() || !places.get(0).isOnline()) + return new ArrayList<>(0); + else + return Collections.singletonList(places.get(0)); + } + else + return null; + } + + @Override + public List getLosers() + { + List winners = getWinners(); + + if (winners == null) + { + return null; + } + + List losers = _players.GetPlayers(false); + losers.removeAll(winners); + + return losers; + } +} diff --git a/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/modules/chest/ChestLootItem.java b/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/modules/chest/ChestLootItem.java new file mode 100644 index 000000000..a86370368 --- /dev/null +++ b/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/modules/chest/ChestLootItem.java @@ -0,0 +1,37 @@ +package nautilus.game.arcade.game.modules.chest; + +import mineplex.core.common.util.UtilMath; +import org.bukkit.inventory.ItemStack; + +public class ChestLootItem +{ + + private ItemStack _item; + private int _lowestAmount, _highestAmount; + private double _rarity; + + public ChestLootItem(ItemStack item, int lowestAmount, int highestAmount, double rarity) + { + _item = item; + _lowestAmount = lowestAmount; + _highestAmount = highestAmount; + _rarity = rarity; + } + + public ItemStack getItem() + { + ItemStack itemStack = _item.clone(); + + if (_lowestAmount != _highestAmount) + { + itemStack.setAmount(UtilMath.rRange(_lowestAmount, _highestAmount)); + } + + return itemStack; + } + + public double getProbability() + { + return _rarity; + } +} \ No newline at end of file diff --git a/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/modules/chest/ChestLootModule.java b/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/modules/chest/ChestLootModule.java new file mode 100644 index 000000000..b57c21cad --- /dev/null +++ b/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/modules/chest/ChestLootModule.java @@ -0,0 +1,262 @@ +package nautilus.game.arcade.game.modules.chest; + +import mineplex.core.common.util.UtilBlock; +import mineplex.core.common.util.UtilEvent; +import mineplex.core.common.util.UtilEvent.ActionType; +import mineplex.core.common.util.UtilMath; +import mineplex.core.common.util.UtilTime; +import mineplex.core.updater.UpdateType; +import mineplex.core.updater.event.UpdateEvent; +import nautilus.game.arcade.events.GameStateChangeEvent; +import nautilus.game.arcade.game.Game.GameState; +import nautilus.game.arcade.game.modules.Module; +import org.bukkit.Effect; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.Chest; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.player.PlayerInteractEvent; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +public class ChestLootModule extends Module +{ + + private static final BlockFace[] FACES = { + BlockFace.NORTH, + BlockFace.SOUTH, + BlockFace.WEST, + BlockFace.EAST + }; + + private final List _chestTypes; + private final Map> _chests; + + private long _destroyAfterOpened; + private boolean _autoRotateChests = true; + + public ChestLootModule() + { + _chestTypes = new ArrayList<>(); + _chests = new HashMap<>(); + } + + public ChestLootModule registerChestType(String name, ChestLootPool... pools) + { + return registerChestType(name, 1, pools); + } + + public ChestLootModule registerChestType(String name, double spawnChance, ChestLootPool... pools) + { + _chestTypes.add(new ChestType(name, spawnChance, pools)); + return this; + } + + public ChestLootModule setSpawnsForType(String typeName, List locations) + { + for (ChestType type : _chestTypes) + { + if (type.Name.equals(typeName)) + { + type.ChestSpawns = locations; + } + } + + return this; + } + + public ChestLootModule destoryAfterOpened(int seconds) + { + _destroyAfterOpened = TimeUnit.SECONDS.toMillis(seconds); + return this; + } + + public ChestLootModule autoRotateChests(boolean autoRotate) + { + _autoRotateChests = autoRotate; + return this; + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void populateChests(GameStateChangeEvent event) + { + if (event.GetState() != GameState.Prepare) + { + return; + } + + for (ChestType chestType : _chestTypes) + { + if (chestType.ChestSpawns == null) + { + continue; + } + + Set metadataSet = new HashSet<>(); + + for (Location location : chestType.ChestSpawns) + { + if (chestType.SpawnChance == 1 || Math.random() < chestType.SpawnChance) + { + Block block = location.getBlock(); + block.setType(Material.CHEST); + + if (_autoRotateChests) + { + for (BlockFace face : FACES) + { + if (UtilBlock.airFoliage(block.getRelative(face))) + { + block.setData(getData(face)); + break; + } + } + } + + ChestMetadata metadata = new ChestMetadata(block, chestType); + metadataSet.add(metadata); + } + } + + _chests.put(chestType, metadataSet); + } + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void openChest(PlayerInteractEvent event) + { + Block block = event.getClickedBlock(); + + if (event.isCancelled() || !UtilEvent.isAction(event, ActionType.R_BLOCK) || block == null || !(block.getState() instanceof Chest)) + { + return; + } + + ChestMetadata metadata = getFromBlock(block); + + if (metadata == null) + { + return; + } + + if (metadata.Opened) + { + return; + } + + metadata.Opened = true; + metadata.OpenedAt = System.currentTimeMillis(); + metadata.populateChest((Chest) block.getState()); + } + + @EventHandler + public void destroyOpenedChests(UpdateEvent event) + { + if (event.getType() != UpdateType.SEC || _destroyAfterOpened == 0) + { + return; + } + + for (Set metadataSet : _chests.values()) + { + metadataSet.removeIf(metadata -> + { + if (metadata.Opened && UtilTime.elapsed(metadata.OpenedAt, _destroyAfterOpened)) + { + Block block = metadata.Chest; + Location location = block.getLocation(); + location.getWorld().playEffect(location.add(0.5, 0.5, 0.5), Effect.STEP_SOUND, block.getType(), block.getData()); + block.setType(Material.AIR); + return true; + } + + return false; + }); + } + } + + private ChestMetadata getFromBlock(Block block) + { + Location blockLocation = block.getLocation(); + + for (Set metadataSet : _chests.values()) + { + for (ChestMetadata metadata : metadataSet) + { + if (UtilMath.offsetSquared(blockLocation, metadata.Chest.getLocation()) < 4) + { + return metadata; + } + } + } + + return null; + } + + private byte getData(BlockFace face) + { + switch (face) + { + case NORTH: + return 0; + case SOUTH: + return 3; + case WEST: + return 4; + case EAST: + return 5; + } + + return 0; + } + + private class ChestMetadata + { + + Block Chest; + ChestType Type; + long OpenedAt; + boolean Opened; + + ChestMetadata(Block chest, ChestType type) + { + Chest = chest; + Type = type; + } + + void populateChest(Chest chest) + { + for (ChestLootPool pool : Type.Pool) + { + pool.populateChest(chest); + } + } + } + + public class ChestType + { + String Name; + double SpawnChance; + List Pool; + List ChestSpawns; + + public ChestType(String name, double spawnChance, ChestLootPool... pools) + { + Name = name; + SpawnChance = spawnChance; + Pool = Arrays.asList(pools); + } + } + + +} diff --git a/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/modules/chest/ChestLootPool.java b/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/modules/chest/ChestLootPool.java new file mode 100644 index 000000000..973597748 --- /dev/null +++ b/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/modules/chest/ChestLootPool.java @@ -0,0 +1,94 @@ +package nautilus.game.arcade.game.modules.chest; + +import mineplex.core.common.util.UtilMath; +import org.bukkit.block.Chest; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; + +import java.util.ArrayList; +import java.util.List; + +public class ChestLootPool +{ + + private List _items; + private int _minimumPerChest; + private int _maximumPerChest; + + public ChestLootPool() + { + _items = new ArrayList<>(); + _minimumPerChest = 1; + _maximumPerChest = 1; + } + + public ChestLootPool addItem(ItemStack itemStack) + { + return addItem(itemStack, itemStack.getAmount(), itemStack.getAmount(), 1); + } + + public ChestLootPool addItem(ItemStack itemStack, double rarity) + { + return addItem(itemStack, itemStack.getAmount(), itemStack.getAmount(), rarity); + } + + public ChestLootPool addItem(ItemStack itemStack, int lowestAmount, int highestAmount) + { + return addItem(itemStack, lowestAmount, highestAmount, 1); + } + + public ChestLootPool addItem(ItemStack itemStack, int lowestAmount, int highestAmount, double rarity) + { + _items.add(new ChestLootItem(itemStack, lowestAmount, highestAmount, rarity)); + return this; + } + + public ChestLootPool setAmountsPerChest(int minimumPerChest, int maximumPerChest) + { + _minimumPerChest = minimumPerChest; + _maximumPerChest = maximumPerChest; + return this; + } + + public void populateChest(Chest chest) + { + Inventory inventory = chest.getBlockInventory(); + + for (int i = 0; i < UtilMath.rRange(_minimumPerChest, _maximumPerChest); i++) + { + int slot = UtilMath.r(inventory.getSize()); + ChestLootItem item = getRandomItem(); + + if (item == null) + { + continue; + } + + inventory.setItem(slot, item.getItem()); + } + + chest.update(); + } + + private ChestLootItem getRandomItem() + { + double totalWeight = 0; + + for (ChestLootItem item : _items) + { + totalWeight += item.getProbability(); + } + + double select = Math.random() * totalWeight; + + for (ChestLootItem item : _items) + { + if ((select -= item.getProbability()) <= 0) + { + return item; + } + } + + return null; + } +} \ No newline at end of file