diff --git a/src/main/java/com/biel/lobby/GestorMapes.java b/src/main/java/com/biel/lobby/GestorMapes.java index cbb61e7..5c4ad74 100644 --- a/src/main/java/com/biel/lobby/GestorMapes.java +++ b/src/main/java/com/biel/lobby/GestorMapes.java @@ -362,6 +362,7 @@ void checkNecessary(Joc map){ if(map.getEditMode())return; if (map.getWorld().getPlayers().size() == 0){ + if (map.JocEnMarxa()) map.JocFinalitzat(); removeMap(map); } } diff --git a/src/main/java/com/biel/lobby/lobby.java b/src/main/java/com/biel/lobby/lobby.java index 40f037c..0318dfc 100644 --- a/src/main/java/com/biel/lobby/lobby.java +++ b/src/main/java/com/biel/lobby/lobby.java @@ -42,7 +42,8 @@ public void onEnable(){ @Override public void onDisable() { - // TODO Insert logic to be performed when the plugin is disabled + if (dataAPI != null) dataAPI.closeConnection(); + HologramFacade.deleteAll(); } public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args){ if(cmd.getName().equalsIgnoreCase("prova")){ diff --git a/src/main/java/com/biel/lobby/mapes/Joc.java b/src/main/java/com/biel/lobby/mapes/Joc.java index ae414bd..946788a 100644 --- a/src/main/java/com/biel/lobby/mapes/Joc.java +++ b/src/main/java/com/biel/lobby/mapes/Joc.java @@ -195,15 +195,31 @@ public int segonsTranscorreguts(){ public void JocFinalitzat(){ if (!JocIniciat){Bukkit.broadcastMessage("S'ha intentat finalitzar una partida que no havia començat."); return;} if (JocFinalitzat){Bukkit.broadcastMessage("S'ha intentat finalitzar una partida que ja havia acabat."); return;} - //--- - world.setPVP(false); - customJocFinalitzat(); - clearExternals(); - if(!won)matchData.registerEnd(-1); //Tie / no winner - registerTimestamps(true); - //--- + // Fence gameplay first. Cleanup and persistence failures must not leave a + // logically completed match running forever. JocFinalitzat = true; - updateScoreBoards(); + try { + if (world != null) world.setPVP(false); + customJocFinalitzat(); + } catch (RuntimeException exception) { + Com.getPlugin().getLogger().log(java.util.logging.Level.SEVERE, + "Game-specific finalization failed for " + getGameName(), exception); + } finally { + clearExternals(); + } + try { + if(!won && matchData != null) matchData.registerEnd(-1); //Tie / no winner + registerTimestamps(true); + } catch (RuntimeException exception) { + Com.getPlugin().getLogger().log(java.util.logging.Level.SEVERE, + "Could not persist final match state for " + getGameName(), exception); + } + try { + updateScoreBoards(); + } catch (RuntimeException exception) { + Com.getPlugin().getLogger().log(java.util.logging.Level.WARNING, + "Could not update final scoreboards for " + getGameName(), exception); + } } public void winGame(Player p){ //TODO if(won)return; @@ -581,7 +597,7 @@ public ItemStack getSnowLauncher(int amount){ isSnowLauncherEnabled = true; ItemStack ball = new ItemStack(Material.SNOWBALL); ball.addUnsafeEnchantment(Enchantment.SILK_TOUCH, 1); - ball.setAmount(amount); + ball.setAmount(Math.max(1, amount)); return Utils.setItemNameAndLore(ball, "Llançador de neu", "Et transporta a l'enemic que impacti"); } public boolean giveSnowLauncherOnKill(){ diff --git a/src/main/java/com/biel/lobby/mapes/MapaResetejable.java b/src/main/java/com/biel/lobby/mapes/MapaResetejable.java index a6f134b..33bcc88 100644 --- a/src/main/java/com/biel/lobby/mapes/MapaResetejable.java +++ b/src/main/java/com/biel/lobby/mapes/MapaResetejable.java @@ -42,7 +42,7 @@ public static void cleanupStaleRuntimeWorlds() { File dimensionsRoot = new File(new File(new File(worldContainer, "world"), "dimensions"), "minecraft"); for (File metadataDirectory : metadataDirectories) { String worldName = metadataDirectory.getName(); - if (!worldName.matches("[A-Za-z][A-Za-z0-9_-]*\\d+")) { + if (!worldName.matches("[A-Za-z][A-Za-z0-9 _-]*\\d+")) { Com.getPlugin().getLogger().warning("Skipping unexpected live metadata directory: " + worldName); continue; } diff --git a/src/main/java/com/biel/lobby/mapes/jocs/Arena4.java b/src/main/java/com/biel/lobby/mapes/jocs/Arena4.java index e4a791f..e668fb9 100644 --- a/src/main/java/com/biel/lobby/mapes/jocs/Arena4.java +++ b/src/main/java/com/biel/lobby/mapes/jocs/Arena4.java @@ -1,12 +1,17 @@ package com.biel.lobby.mapes.jocs; import java.util.ArrayList; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.UUID; import org.bukkit.ChatColor; import org.bukkit.DyeColor; import org.bukkit.Material; import org.bukkit.entity.Player; import org.bukkit.event.Event; +import org.bukkit.event.entity.EntityDamageByEntityEvent; import org.bukkit.event.entity.PlayerDeathEvent; import org.bukkit.inventory.ItemStack; @@ -14,12 +19,71 @@ import com.biel.lobby.utilities.ScoreBoardUpdater; public class Arena4 extends JocEquips { + private static final long WRONG_TARGET_MESSAGE_COOLDOWN_MILLIS = 5_000L; + private final Map lastWrongTargetMessage = new HashMap<>(); @Override protected void customJocIniciat() { // TODO Auto-generated method stub super.customJocIniciat(); setBlockBreakPlace(false); + sendGlobalMessage(ChatColor.GOLD + "Arena 4: each team hunts one marked target team."); + sendGlobalMessage(ChatColor.YELLOW + "RED hunts YELLOW, YELLOW hunts GREEN, GREEN hunts BLUE, and BLUE hunts RED."); + sendGlobalMessage(ChatColor.AQUA + "You deal 2x damage and earn 3 points against your target. You cannot damage the team hunting you; other kills earn 1 point."); + for (Player player : getPlayers()) { + Arena4Equip team = (Arena4Equip) obtenirEquip(player); + if (team == null) continue; + Arena4Equip target = (Arena4Equip) team.equipObjectiu(); + Arena4Equip hunter = getHunterOf(team); + sendPlayerMessage(player, ChatColor.GREEN + "Your target: " + formatTeam(target) + + ChatColor.GRAY + " | " + ChatColor.RED + "Your hunter: " + formatTeam(hunter)); + } + } + + @Override + protected void onPlayerDamageByPlayer(EntityDamageByEntityEvent evt, Player damaged, Player damager, boolean ranged) { + super.onPlayerDamageByPlayer(evt, damaged, damager, ranged); + if (evt.isCancelled()) return; + Arena4Equip attackerTeam = (Arena4Equip) obtenirEquip(damager); + Arena4Equip defenderTeam = (Arena4Equip) obtenirEquip(damaged); + if (attackerTeam == null || defenderTeam == null || attackerTeam == defenderTeam) return; + + if (defenderTeam.equipObjectiu() == attackerTeam) { + evt.setCancelled(true); + sendWrongTargetMessage(damager, ChatColor.RED + "You cannot attack " + formatTeam(defenderTeam) + + ChatColor.RED + "; they are hunting your team. Your target is " + + formatTeam((Arena4Equip) attackerTeam.equipObjectiu()) + ChatColor.RED + "."); + return; + } + if (attackerTeam.equipObjectiu() == defenderTeam) { + evt.setDamage(evt.getDamage() * 2.0); + return; + } + sendWrongTargetMessage(damager, ChatColor.YELLOW + "Wrong target: " + formatTeam(defenderTeam) + + ChatColor.YELLOW + " takes normal damage. Hunt " + + formatTeam((Arena4Equip) attackerTeam.equipObjectiu()) + + ChatColor.YELLOW + " for 2x damage and 3 points."); + } + + private Arena4Equip getHunterOf(Arena4Equip huntedTeam) { + for (Equip team : Equips) { + Arena4Equip arenaTeam = (Arena4Equip) team; + if (arenaTeam.equipObjectiu() == huntedTeam) return arenaTeam; + } + return null; + } + + private String formatTeam(Arena4Equip team) { + if (team == null) return "unknown team"; + return team.getChatColor() + team.getColor().name().toLowerCase(Locale.ROOT) + " team"; + } + + private void sendWrongTargetMessage(Player player, String message) { + long now = System.currentTimeMillis(); + long lastMessage = lastWrongTargetMessage.getOrDefault(player.getUniqueId(), 0L); + if (now - lastMessage < WRONG_TARGET_MESSAGE_COOLDOWN_MILLIS) return; + lastWrongTargetMessage.put(player.getUniqueId(), now); + sendPlayerMessage(player, message); } @Override public String getGameName() { @@ -78,9 +142,10 @@ protected synchronized void gameEvent(Event event) { PlayerDeathEvent evt = (PlayerDeathEvent)event; Player killed = evt.getEntity(); Player killer = killed.getKiller(); + if (killer == null){return;} Arena4Equip eqKilled = (Arena4Equip) obtenirEquip(killed); Arena4Equip eqKiller = (Arena4Equip) obtenirEquip(killer); - if (killer == null){return;} + if (eqKilled == null || eqKiller == null){return;} if (areEnemies(killed, killer)){ int suma = 0; int resta = 1; diff --git a/src/main/java/com/biel/lobby/mapes/jocs/InkWars.java b/src/main/java/com/biel/lobby/mapes/jocs/InkWars.java index 52d0d21..983b3b7 100644 --- a/src/main/java/com/biel/lobby/mapes/jocs/InkWars.java +++ b/src/main/java/com/biel/lobby/mapes/jocs/InkWars.java @@ -11,6 +11,7 @@ import org.bukkit.Effect; import org.bukkit.Location; import org.bukkit.Material; +import org.bukkit.Particle; import org.bukkit.Sound; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; @@ -402,6 +403,9 @@ public void tick(){ public double getWeaponSpeedModifier(){ return 0; } + public double getMaxHealth(){ + return 20; + } public int neededReloadTicks(){ return 50; } @@ -560,7 +564,6 @@ class MachinegunInkWeapon extends ProjectileInkWeapon{ public MachinegunInkWeapon(Player ply) { super(ply); - ply.setMaxHealth(20); } @Override public int getMaxLoad() { @@ -613,10 +616,13 @@ protected void onPlayerDamageByPlayer(EntityDamageByEntityEvent evt, class EnderInkWeapon extends ProjectileInkWeapon{ Player targeted; int chargeTicks = 0; - int toolTicks = 0; + int toolTicks = -1; public EnderInkWeapon(Player ply) { super(ply); - ply.setMaxHealth(10); + } + @Override + public double getMaxHealth() { + return 10; } @Override public int getMaxLoad() { @@ -701,9 +707,9 @@ public void tick() { super.tick(); if(toolTicks == 0){ getPlayer().getInventory().addItem(getToolMaterial()); - toolTicks = 200000; + toolTicks = -1; } - toolTicks--; + if(toolTicks > 0)toolTicks--; if(chargeTicks > 0 && targeted != null){ rollerLinePaint(1.5 + (getWeaponLevel() / 3) + chargeTicks / (20 * 4), 1.2, targeted); @@ -749,7 +755,10 @@ protected void onPlayerDeathByPlayer(PlayerDeathEvent evt, class RollerInkWeapon extends InkWeapon{ public RollerInkWeapon(Player ply) { super(ply); - ply.setMaxHealth(18); + } + @Override + public double getMaxHealth() { + return 18; } @Override protected void onPlayerMove(PlayerMoveEvent evt, Player p) { @@ -785,7 +794,10 @@ class BrushInkWeapon extends InkWeapon{ int charges = 0; public BrushInkWeapon(Player ply) { super(ply); - ply.setMaxHealth(18); + } + @Override + public double getMaxHealth() { + return 18; } @Override protected void onPlayerMove(PlayerMoveEvent evt, Player p) { @@ -826,6 +838,7 @@ public void tick() { protected void onPlayerDamageByPlayer(EntityDamageByEntityEvent evt, Player damaged, Player damager, boolean ranged) { super.onPlayerDamageByPlayer(evt, damaged, damager, ranged); + if(damager != getPlayer())return; if(!ranged && damager == getPlayer() && !damaged.hasPotionEffect(PotionEffectType.WITHER) && !evt.isCancelled()){ if(targeted == null)targeted = damaged; if(targeted != damaged){ @@ -842,7 +855,7 @@ protected void onPlayerDamageByPlayer(EntityDamageByEntityEvent evt, damaged.addPotionEffect(new PotionEffect(PotionEffectType.WITHER, (int) Math.round(20 * (5 + 0.6 * getWeaponLevel())), Math.round(getWeaponLevel() + 2 / 5)), true); getWorld().playSound(damaged.getEyeLocation(), Sound.ENTITY_GENERIC_SWIM, 1, 1.25F); } - }else{evt.setCancelled(true);} + }else if(!evt.isCancelled()){evt.setCancelled(true);} } } @@ -875,14 +888,19 @@ public void registerWeaponAlert(){ public int getWeaponAlerts() { return weaponAlerts; } - public boolean isShileded(){ + public boolean isShielded(){ return getShieldTicks() > 0; } public int getShieldTicks() { return shieldTicks; } public void setShieldTicks(int shieldTicks) { - this.shieldTicks = shieldTicks; + boolean shieldWasInactive = this.shieldTicks <= 0; + this.shieldTicks = Math.max(0, shieldTicks); + if(shieldWasInactive && this.shieldTicks > 0){ + getWorld().spawnParticle(Particle.END_ROD, getPlayer().getLocation().add(0, 1, 0), 18, 0.5, 0.8, 0.5, 0.02); + getPlayer().playSound(getPlayer().getLocation(), Sound.BLOCK_BEACON_ACTIVATE, 0.7F, 1.6F); + } } @Override public void ultraTick() { @@ -900,7 +918,7 @@ public void ultraTick() { setSpeedModifier(0); } if(b != null && teamOwningBlock != null){ - boolean isOnItsColor = (teamOwningBlock == e) || isShileded(); + boolean isOnItsColor = (teamOwningBlock == e) || isShielded(); boolean isHighInk = highInkBlocks.containsKey(b); double speedMod = 0; if(isOnItsColor){speedMod += 5;}else{speedMod += -5;} @@ -926,7 +944,16 @@ public void ultraTick() { dmgTicks = 0; } } - shieldTicks--; + if(shieldTicks > 0){ + if(shieldTicks % 5 == 0){ + getWorld().spawnParticle(Particle.END_ROD, getPlayer().getLocation().add(0, 1, 0), 3, 0.45, 0.65, 0.45, 0); + } + shieldTicks--; + if(shieldTicks == 0){ + getWorld().spawnParticle(Particle.SMOKE, getPlayer().getLocation().add(0, 1, 0), 10, 0.4, 0.6, 0.4, 0.02); + getPlayer().playSound(getPlayer().getLocation(), Sound.BLOCK_BEACON_DEACTIVATE, 0.6F, 1.8F); + } + } super.tick(); } public void registerBlockPaint(EquipInkWars oldOwner){ @@ -950,7 +977,10 @@ public void setActiveWeapon(InkWeapon newWeapon) { if (pW != null) { pW.destroy(); } + double healthBeforeSwitch = getPlayer().getHealth(); + getPlayer().setMaxHealth(newWeapon.getMaxHealth()); donarItemsInicials(getPlayer()); //Clears and gives starting (colored) armor + getPlayer().setHealth(Math.min(healthBeforeSwitch, getPlayer().getMaxHealth())); this.activeWeapon = newWeapon; this.activeWeapon.giveTool(); } @@ -1003,7 +1033,7 @@ public void setOwnedBlocks(int ownedBlocks) { this.ownedBlocks = ownedBlocks; } public void incrementOwnedBlocks(int increase){ - setOwnedBlocks(getOwnedBlocks() + increase); + setOwnedBlocks(Math.max(0, getOwnedBlocks() + increase)); } public double getOwnedPercent(){ if(getTotalPaintedBlocks() == 0)return 0; diff --git a/src/main/java/com/biel/lobby/mapes/jocs/Parkour.java b/src/main/java/com/biel/lobby/mapes/jocs/Parkour.java index 03adf41..05f48dd 100644 --- a/src/main/java/com/biel/lobby/mapes/jocs/Parkour.java +++ b/src/main/java/com/biel/lobby/mapes/jocs/Parkour.java @@ -44,9 +44,17 @@ public class Parkour extends JocScoreCombo{ ArrayList streams = new ArrayList<>(); + ArrayList nativeFinishers = new ArrayList<>(); ParkourProvider provider = new ParkourProvider(); int playerCount = 0; int mapLength = 40; + boolean nativeDatapackMap = false; + @Override + public void initialize() { + super.initialize(); + nativeDatapackMap = pMapaActual().ExisteixPropietat("nativeDatapack") + && Boolean.parseBoolean(pMapaActual().ObtenirPropietat("nativeDatapack")); + } @Override public String getGameName() { // TODO Auto-generated method stub @@ -103,10 +111,34 @@ protected ArrayList getStartingItems(Player ply) { protected void customJoin(Player ply) { // TODO Auto-generated method stub super.customJoin(ply); + if(nativeDatapackMap){ + ply.removeScoreboardTag("joined"); + ply.removeScoreboardTag("finished"); + ply.removeScoreboardTag("ingame"); + } //updateStartingPlatforms(); } @Override protected void teletransportarTothom() { + if(nativeDatapackMap){ + Location courseStart = pMapaActual().ExisteixPropietat("courseStart") + ? pMapaActual().ObtenirLocation("courseStart", getWorld()).add(0.5, 0, 0.5) + : getWorld().getSpawnLocation(); + float courseStartYaw = pMapaActual().ExisteixPropietat("courseStartYaw") + ? pMapaActual().ObtenirPropietatDouble("courseStartYaw").floatValue() : 0F; + float courseStartPitch = pMapaActual().ExisteixPropietat("courseStartPitch") + ? pMapaActual().ObtenirPropietatDouble("courseStartPitch").floatValue() : 10F; + courseStart.setYaw(courseStartYaw); + courseStart.setPitch(courseStartPitch); + for(Player player : getPlayers()){ + getPlayerInfo(player).setInGame(true); + player.removeScoreboardTag("finished"); + player.removeScoreboardTag("ingame"); + player.setGameMode(GameMode.ADVENTURE); + player.teleport(courseStart); + } + return; + } //Generate & TP generateStreams(); @@ -115,6 +147,7 @@ public void generateStreams() { getPlayers().forEach(p -> streams.add(new ParkourStream(p, p.getLocation().getBlock().getLocation().add(0, -1, 0)))); } protected void updateStartingPlatforms(){ + if(nativeDatapackMap)return; ArrayList players = getPlayers(); if(playerCount == players.size())return; Vector mv = new Vector(0, 1, 0).crossProduct(getForward()); @@ -137,6 +170,17 @@ protected void updateStartingPlatforms(){ } public void comprovarFinish(){ + if(nativeDatapackMap){ + for(Player player : getPlayers()){ + ParkourPlayerInfo playerInfo = getPlayerInfo(player); + if(playerInfo.isInGame() && player.getScoreboardTags().contains("finished")){ + nativeFinishers.add(player); + playerInfo.setInGame(false); + player.setGameMode(GameMode.SPECTATOR); + sendGlobalMessage(player.getName() + " ha arribat a la meta!"); + } + } + } //boolean allFinished = streams.stream().mapToInt(ParkourStream::getTargetBubbleIndex).min().getAsInt() > 100; boolean allFinished = getPlayers().stream().map(p -> getPlayerInfo(p).isInGame()).allMatch(b -> b == false); if(allFinished){ @@ -159,8 +203,19 @@ public void heartbeat() { public void ultraHeartbeat() { // TODO Auto-generated method stub super.ultraHeartbeat(); - streams.removeIf(s -> !s.isValid()); - streams.forEach(ParkourStream::ultraTick); + if(!nativeDatapackMap){ + streams.removeIf(s -> !s.isValid()); + streams.forEach(ParkourStream::ultraTick); + } + } + @Override + public ArrayList getOrderedWinnerList(){ + if(!nativeDatapackMap)return super.getOrderedWinnerList(); + ArrayList orderedPlayers = new ArrayList<>(nativeFinishers); + for(Player player : getPlayers()){ + if(!orderedPlayers.contains(player))orderedPlayers.add(player); + } + return orderedPlayers; } @Override protected void customJocIniciat() { diff --git a/src/main/java/com/biel/lobby/mapes/jocs/Quakecraft.java b/src/main/java/com/biel/lobby/mapes/jocs/Quakecraft.java index 4afeee7..33b6883 100644 --- a/src/main/java/com/biel/lobby/mapes/jocs/Quakecraft.java +++ b/src/main/java/com/biel/lobby/mapes/jocs/Quakecraft.java @@ -119,7 +119,7 @@ ItemStack getRailgun(Player ply){ ItemStack item = new ItemStack(Material.DIAMOND_HOE); if(t >= 1000){return Utils.setItemNameAndLore(item, ChatColor.AQUA + "Railgun", l);} if(t > 550){return Utils.setItemNameAndLore(item, ChatColor.AQUA + "Ultimate Railgun", l);} - item.addEnchantment(Enchantment.POWER, 4); + item.addUnsafeEnchantment(Enchantment.POWER, 4); if(t <= 550){return Utils.setItemNameAndLore(item, ChatColor.AQUA +"SUPER Railgun", l);} return new ItemStack(Material.OAK_PLANKS); } diff --git a/src/main/java/com/biel/lobby/mapes/jocs/RainbowClay.java b/src/main/java/com/biel/lobby/mapes/jocs/RainbowClay.java index 2979cd3..199e559 100644 --- a/src/main/java/com/biel/lobby/mapes/jocs/RainbowClay.java +++ b/src/main/java/com/biel/lobby/mapes/jocs/RainbowClay.java @@ -104,10 +104,12 @@ public boolean giveSnowLauncherOnKill() { @Override protected void onPlayerDeath(PlayerDeathEvent evt, Player killed) { - // TODO Auto-generated method stub - super.onPlayerDeath(evt, killed); - Equip team = obtenirEquip(killed); + if (team == null) { + evt.setDeathMessage(ChatColor.GRAY + killed.getName() + " ha mort."); + return; + } + super.onPlayerDeath(evt, killed); evt.setDeathMessage(team.getChatColor() + killed.getName() + ChatColor.GRAY + " ha mort."); @@ -130,6 +132,7 @@ protected ArrayList getStartingItems(Player ply) { ArrayList items = new ArrayList<>(); Equip e = obtenirEquip(ply); + if (e == null) return items; items.add(new ItemStack(Material.IRON_SWORD, 1)); @@ -232,6 +235,7 @@ protected void onBlockBreak(BlockBreakEvent evt, Block blk) { Player ply = evt.getPlayer(); Equip team = obtenirEquip(ply); + if (team == null) return; Cuboid centre = pMapaActual().ObtenirCuboid("RegC", getWorld()); diff --git a/src/main/java/com/biel/lobby/mapes/jocs/Torres.java b/src/main/java/com/biel/lobby/mapes/jocs/Torres.java index aad0bb2..2bd762a 100644 --- a/src/main/java/com/biel/lobby/mapes/jocs/Torres.java +++ b/src/main/java/com/biel/lobby/mapes/jocs/Torres.java @@ -37,6 +37,7 @@ import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerPickupItemEvent; import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.metadata.MetadataValue; @@ -53,6 +54,7 @@ import com.biel.lobby.utilities.Utils; public class Torres extends JocEquips { + private static final double GENERAL_DAMAGE_MULTIPLIER = 0.6; boolean debug = false; public ArrayList Turrets = new ArrayList<>(); @Override @@ -434,9 +436,7 @@ clickBlock, plyr, this, obtenirEquip(plyr), turr.Build(); turr.Attack(); if (turr.built && debug == false) { - ItemStack item = stack.clone(); - item.setAmount(1); - inv.removeItem(item); + consumeTurretItem(plyr, evt.getHand(), stack); } } } @@ -450,6 +450,17 @@ clickBlock, plyr, this, obtenirEquip(plyr), } } } + private void consumeTurretItem(Player player, EquipmentSlot hand, ItemStack usedItem) { + if (usedItem.getAmount() > 1) { + usedItem.setAmount(usedItem.getAmount() - 1); + return; + } + if (hand == EquipmentSlot.OFF_HAND) { + player.getInventory().setItemInOffHand(new ItemStack(Material.AIR)); + } else { + player.getInventory().setItemInMainHand(new ItemStack(Material.AIR)); + } + } @Override protected void onPlayerDeath(PlayerDeathEvent evt, Player killed) { // TODO Auto-generated method stub @@ -500,7 +511,16 @@ protected void onEntityDamage(EntityDamageEvent evt, Entity e) { //} } - evt.setDamage(evt.getDamage() * 0.6); + evt.setDamage(evt.getDamage() * GENERAL_DAMAGE_MULTIPLIER); + } + @Override + protected void onPlayerDamageByPlayer(EntityDamageByEntityEvent evt, Player damaged, Player damager, boolean ranged) { + super.onPlayerDamageByPlayer(evt, damaged, damager, ranged); + if (!evt.isCancelled()) { + // Torres historically reduced every damage source to 60%. Restore + // normal damage specifically for player-versus-player combat. + evt.setDamage(evt.getDamage() / GENERAL_DAMAGE_MULTIPLIER); + } } @Override protected void onPlayerFish(PlayerFishEvent evt, Player p) { diff --git a/src/main/java/com/biel/lobby/utilities/Turret.java b/src/main/java/com/biel/lobby/utilities/Turret.java index 0f0c8ad..0ae90b1 100644 --- a/src/main/java/com/biel/lobby/utilities/Turret.java +++ b/src/main/java/com/biel/lobby/utilities/Turret.java @@ -20,8 +20,10 @@ import org.bukkit.event.entity.ProjectileHitEvent; import org.bukkit.event.inventory.InventoryAction; import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryDragEvent; import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.metadata.FixedMetadataValue; @@ -78,11 +80,24 @@ public void setAutoUpgrade(Boolean autoUpgrade) { this.autoUpgrade = autoUpgrade; } String invString = ""; + private static final class TurretInventoryHolder implements InventoryHolder { + private final Turret turret; + private Inventory inventory; + + private TurretInventoryHolder(Turret turret) { + this.turret = turret; + } + + @Override + public Inventory getInventory() { + return inventory; + } + } private int taskId; private int taskEscutId; //Estats public int VelAtac = 22; - public int Atac = 2; + public int Atac = 4; public int distAtac = 14; public int xpPerTir = 1; public int maxHpEscut = 0; @@ -649,8 +664,6 @@ protected void onProjectileHit(ProjectileHitEvent evt, Projectile proj) { Location loc = entity.getLocation(); - LivingEntity shooter = (LivingEntity) proj.getShooter(); - //Location land = loc.add(entity.getVelocity().normalize().multiply(0.8)); Arrow arrow = (Arrow)proj; if((arrow.getShooter() instanceof Player)){ @@ -678,7 +691,7 @@ protected void onProjectileHit(ProjectileHitEvent evt, Projectile proj) { } } if (hit) { - Hit(10); + Hit(15); player.playSound(player.getEyeLocation(), Sound.ENTITY_ARROW_HIT_PLAYER, 1F, 0.9F); arrow.remove(); } @@ -733,7 +746,9 @@ String generateInvString(){ public void openOrRefreshInventory(Player plyr){ if (headless == false){ invString = generateInvString(); - Inventory inv = Bukkit.getServer().createInventory(plyr, 9, invString); + TurretInventoryHolder holder = new TurretInventoryHolder(this); + Inventory inv = Bukkit.getServer().createInventory(holder, 9, invString); + holder.inventory = inv; if (linkCreador == false){ for (Millora mill : Millores){ inv.addItem(mill.toItemStack()); @@ -779,7 +794,7 @@ protected void onPlayerInteract(PlayerInteractEvent evt, Player p) { } if (evt.getAction() == Action.LEFT_CLICK_BLOCK){ if (ContainsTurretBlock(evt.getClickedBlock().getLocation())){ - Hit(3); + Hit(5); } } @@ -789,6 +804,33 @@ protected void onPlayerInteract(PlayerInteractEvent evt, Player p) { protected void onInventoryClick(InventoryClickEvent evt, Inventory inv) { // TODO Auto-generated method stub super.onInventoryClick(evt, inv); + Inventory topInventory = evt.getView().getTopInventory(); + if (!(topInventory.getHolder() instanceof TurretInventoryHolder holder) || holder.turret != this) return; + + // The upgrade panel is a control surface, not a container. Cancel every + // transfer route before interpreting the click as an upgrade request. + evt.setCancelled(true); + int slot = evt.getRawSlot(); + if (slot < 0 || slot >= topInventory.getSize() || slot >= Millores.size()) return; + ItemStack cursor = evt.getCursor(); + if (cursor != null && !cursor.getType().isAir()) return; + if (!(evt.getWhoClicked() instanceof Player plyr)) return; + + Millora upgrade = Millores.get(slot); + if (!upgrade.possibleUpgrade()) return; + if (evt.isShiftClick()) { + upgrade.upgradeMaximum(); + } else if (evt.getClick().isLeftClick() || evt.getClick().isRightClick()) { + upgrade.lvlUp(); + } else { + return; + } + + if (anyUpgradePossible()) { + openOrRefreshInventory(plyr); + } else { + plyr.closeInventory(); + } //Bukkit.broadcastMessage(evt.getAction().name()); // TODO UPDATE @@ -838,6 +880,15 @@ protected void onInventoryClick(InventoryClickEvent evt, Inventory inv) { // } } + @Override + protected void onInventoryDrag(InventoryDragEvent evt, Inventory inv) { + super.onInventoryDrag(evt, inv); + Inventory topInventory = evt.getView().getTopInventory(); + if (topInventory.getHolder() instanceof TurretInventoryHolder holder && holder.turret == this) { + evt.setCancelled(true); + } + } + public enum TipusMillora {MAL, VELOCITAT_ATAC, FOC, DIST_ATAC, RESISTÈNCIA, QUÍMICA, MECÀNICA, MAGNETISME, APRENENTATGE}; public class Millora{ String name; diff --git a/src/main/java/com/biel/lobby/utilities/data/DataAPI.java b/src/main/java/com/biel/lobby/utilities/data/DataAPI.java index 8d7e663..8815f99 100644 --- a/src/main/java/com/biel/lobby/utilities/data/DataAPI.java +++ b/src/main/java/com/biel/lobby/utilities/data/DataAPI.java @@ -4,16 +4,31 @@ import java.sql.*; import java.util.ArrayList; import java.util.Set; +import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; import java.util.logging.Logger; import org.bukkit.entity.Player; import com.biel.BielAPI.Utils.Pair; public class DataAPI { + private static final String DATABASE_URL = "jdbc:mysql://localhost:3306/minicat?connectTimeout=2000&socketTimeout=3000&useSSL=false"; + private static final String DATABASE_USER = "minicat_usr"; + private static final String DATABASE_PASSWORD = "minicat"; boolean datalessMode = false; Logger logger = Logger.getLogger("DataAPI"); private final Set emittedDatalessWarnings = ConcurrentHashMap.newKeySet(); + private final Set emittedTimestampWarnings = ConcurrentHashMap.newKeySet(); + private final ThreadPoolExecutor timestampWriter = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, + new ArrayBlockingQueue<>(4096), runnable -> { + Thread thread = new Thread(runnable, "Minicat timestamp writer"); + thread.setDaemon(true); + return thread; + }, (runnable, executor) -> warnTimestampOnce("queue-full", + "Timestamp queue is full; dropping telemetry until the database writer catches up.")); + private Connection timestampConnection; public DataAPI() { } @@ -29,14 +44,8 @@ private void warnDatalessOnce(String operation, String message) { public Connection connection; void openConnection() { - String host = "localhost"; - String port = "3306"; - String db = "minicat"; - String user = "minicat_usr"; - String password = "minicat"; try { - connection = DriverManager.getConnection("jdbc:mysql://" + host - + ":" + port + "/" + db + "?connectTimeout=2000&socketTimeout=3000&useSSL=false", user, password); + connection = openDatabaseConnection(); } catch (Exception e) { datalessMode = true; logger.warning("Could not connect to database; switching to dataless mode: " + e.getMessage()); @@ -46,12 +55,29 @@ public void repairConnection(){ if(connection == null && !datalessMode)openConnection(); } public void closeConnection() { + timestampWriter.shutdown(); try { - connection.close(); - } catch (Exception e) { - e.printStackTrace(); + if (!timestampWriter.awaitTermination(5, TimeUnit.SECONDS)) { + logger.warning("Timestamp writer did not drain within 5 seconds; remaining telemetry will be discarded."); + timestampWriter.shutdownNow(); + } + } catch (InterruptedException exception) { + Thread.currentThread().interrupt(); + timestampWriter.shutdownNow(); + } + try { + if (timestampConnection != null) timestampConnection.close(); + if (connection != null) connection.close(); + } catch (SQLException exception) { + logger.warning("Could not close a database connection cleanly: " + exception.getMessage()); } } + private Connection openDatabaseConnection() throws SQLException { + return DriverManager.getConnection(DATABASE_URL, DATABASE_USER, DATABASE_PASSWORD); + } + private void warnTimestampOnce(String key, String message) { + if (emittedTimestampWarnings.add(key)) logger.warning(message); + } public void registerNewPlayer(Player player) { repairConnection(); @@ -341,24 +367,23 @@ public int getMapId(String name, int gameId) { public MatchData registerMatchStart(int gameId, int mapId, String teams) { if(datalessMode)return new DatalessMatchData(); repairConnection(); - try { - PreparedStatement ps = connection.prepareStatement( + try (PreparedStatement ps = connection.prepareStatement( "INSERT INTO `match_history` " + "(`game_id`, `map_id`, `teams`) VALUES " - + "(?, ?, ?);", Statement.RETURN_GENERATED_KEYS); + + "(?, ?, ?);", Statement.RETURN_GENERATED_KEYS)) { ps.setInt(1, gameId); ps.setInt(2, mapId); ps.setString(3, teams); ps.executeUpdate(); try (ResultSet generatedKeys = ps.getGeneratedKeys()) { - if (generatedKeys.next()) {return new MatchData(generatedKeys.getInt(1));} - } - ps.close(); - + if (generatedKeys.next()) return new MatchData(generatedKeys.getInt(1)); + } + logger.severe("The database did not return an id for the new match; continuing without match persistence."); } catch (SQLException e) { - e.printStackTrace(); + logger.log(java.util.logging.Level.SEVERE, + "Could not register match start; continuing without match persistence.", e); } - return null; + return new DatalessMatchData(); } public void registerMatchEnd(int matchId, int winner) { if(datalessMode)return; @@ -394,10 +419,17 @@ public double getAvgGameLength(int gameId) { } //TIMESTAMP public void registerTimestamp(int matchId, int playerId, int frameId, int kills, int deaths, double damageDealt, boolean isAlive, String itemInHand, int blocksPlaced, int blocksBroken, int objectivesCompleted, int spree) { //Gamemode - if(datalessMode)return; - repairConnection(); + if (datalessMode || timestampWriter.isShutdown()) return; + timestampWriter.execute(() -> writeTimestamp(matchId, playerId, frameId, kills, deaths, damageDealt, + isAlive, blocksPlaced, blocksBroken, objectivesCompleted, spree)); + } + private void writeTimestamp(int matchId, int playerId, int frameId, int kills, int deaths, double damageDealt, + boolean isAlive, int blocksPlaced, int blocksBroken, int objectivesCompleted, int spree) { try { - PreparedStatement ps = connection.prepareStatement("INSERT INTO `player_match_timestamps` " + if (timestampConnection == null || timestampConnection.isClosed() || !timestampConnection.isValid(1)) { + timestampConnection = openDatabaseConnection(); + } + PreparedStatement ps = timestampConnection.prepareStatement("INSERT INTO `player_match_timestamps` " + "(`match_id`, `player_id`, `frame_id`, `kills`, `deaths`, `damage_dealt`, `is_alive`, `item_in_hand`, `blocks_placed`, `blocks_broken`, `objectives_completed`, `spree`) " + "VALUES (?,?,?,?,?,?,?,?,?,?,?,?);"); ps.setInt(1, matchId); @@ -407,7 +439,10 @@ public void registerTimestamp(int matchId, int playerId, int frameId, int kills, ps.setInt(5, deaths); ps.setDouble(6, damageDealt); ps.setBoolean(7, isAlive); -// ps.setInt(8, itemInHand); + // Recovered history stores legacy numeric material ids. Modern Paper + // exposes namespaced keys, so use the legacy "unknown" value until the + // telemetry column receives a dedicated schema migration. + ps.setInt(8, 0); ps.setInt(9, blocksPlaced); ps.setInt(10, blocksBroken); ps.setInt(11, objectivesCompleted); @@ -415,8 +450,15 @@ public void registerTimestamp(int matchId, int playerId, int frameId, int kills, ps.executeUpdate(); ps.close(); - } catch (SQLException e) { - e.printStackTrace(); + emittedTimestampWarnings.clear(); + } catch (SQLException exception) { + warnTimestampOnce("sql-" + exception.getErrorCode(), + "Timestamp persistence is temporarily unavailable: " + exception.getMessage()); + try { + if (timestampConnection != null) timestampConnection.close(); + } catch (SQLException ignored) { + } + timestampConnection = null; } } }