Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@
import org.bukkit.inventory.meta.Damageable;
import org.bukkit.inventory.meta.ItemMeta;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ThreadLocalRandom;

public class SpawnerBreakListener implements Listener {
private static final int MAX_STACK_SIZE = 64;
Expand Down Expand Up @@ -130,12 +132,27 @@ private void handleSmartSpawnerBreak(Block block, SpawnerData spawner, Player pl

plugin.getSpawnerGuiViewManager().closeAllViewersInventory(spawner);

SpawnerBreakResult result = processDrops(player, location, spawner, player.isSneaking(), block);
double dropChance = plugin.getConfig().getDouble("spawner_break.drop_chance", 100.0);
boolean shouldDrop = player.getGameMode() == GameMode.CREATIVE || passesChanceCheck(dropChance);

if (result.isSuccess()) {
Map<String, String> chancePlaceholders = new HashMap<>();
chancePlaceholders.put("chance", String.format("%.1f", dropChance));

if (shouldDrop) {
SpawnerBreakResult result = processDrops(player, location, spawner, player.isSneaking(), block);

if (result.isSuccess()) {
if (player.getGameMode() != GameMode.CREATIVE) {
reduceDurability(tool, player, result.getDurabilityLoss());
}
messageService.sendMessage(player, "spawner_break_success", chancePlaceholders);
}
} else {
cleanupSpawner(block, spawner);
if (player.getGameMode() != GameMode.CREATIVE) {
reduceDurability(tool, player, result.getDurabilityLoss());
reduceDurability(tool, player, plugin.getConfig().getInt("spawner_break.durability_loss", 1));
}
messageService.sendMessage(player, "spawner_break_no_drop", chancePlaceholders);
}
} finally {
locationLockManager.unlock(location);
Expand All @@ -162,25 +179,38 @@ private void handleVanillaSpawnerBreak(Block block, CreatureSpawner creatureSpaw
return;
}

EntityType entityType = creatureSpawner.getSpawnedType();
ItemStack spawnerItem;
if (plugin.getConfig().getBoolean("natural_spawner.convert_to_smart_spawner", false)) {
spawnerItem = spawnerItemFactory.createSmartSpawnerItem(entityType);
} else {
spawnerItem = spawnerItemFactory.createVanillaSpawnerItem(entityType);
}
double dropChance = plugin.getConfig().getDouble("spawner_break.drop_chance", 100.0);
boolean shouldDrop = player.getGameMode() == GameMode.CREATIVE || passesChanceCheck(dropChance);

boolean directToInventory = plugin.getConfig().getBoolean("spawner_break.direct_to_inventory", false);
Map<String, String> chancePlaceholders = new HashMap<>();
chancePlaceholders.put("chance", String.format("%.1f", dropChance));

World world = location.getWorld();
if (world != null) {
block.setType(Material.AIR);

if (directToInventory) {
giveSpawnersToPlayer(player, 1, spawnerItem);
player.playSound(player.getLocation(), Sound.ENTITY_ITEM_PICKUP, 0.5f, 1.2f);
if (shouldDrop) {
EntityType entityType = creatureSpawner.getSpawnedType();
ItemStack spawnerItem;
if (plugin.getConfig().getBoolean("natural_spawner.convert_to_smart_spawner", false)) {
spawnerItem = spawnerItemFactory.createSmartSpawnerItem(entityType);
} else {
spawnerItem = spawnerItemFactory.createVanillaSpawnerItem(entityType);
}

boolean directToInventory = plugin.getConfig().getBoolean("spawner_break.direct_to_inventory", false);

block.setType(Material.AIR);

if (directToInventory) {
giveSpawnersToPlayer(player, 1, spawnerItem);
player.playSound(player.getLocation(), Sound.ENTITY_ITEM_PICKUP, 0.5f, 1.2f);
} else {
world.dropItemNaturally(location.toCenterLocation(), spawnerItem);
}

messageService.sendMessage(player, "spawner_break_success", chancePlaceholders);
} else {
world.dropItemNaturally(location.toCenterLocation(), spawnerItem);
block.setType(Material.AIR);
messageService.sendMessage(player, "spawner_break_no_drop", chancePlaceholders);
}

reduceDurability(tool, player, plugin.getConfig().getInt("spawner_break.durability_loss", 1));
Expand Down Expand Up @@ -370,6 +400,12 @@ public int getDurabilityLoss() {
}
}

private boolean passesChanceCheck(double chance) {
if (chance >= 100.0) return true;
if (chance <= 0.0) return false;
return ThreadLocalRandom.current().nextDouble(100.0) < chance;
}

@EventHandler
public void onSpawnerDamage(BlockDamageEvent event) {
Block block = event.getBlock();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,39 @@
import github.nighter.smartspawner.api.events.SpawnerExplodeEvent;
import github.nighter.smartspawner.extras.HopperHandler;
import github.nighter.smartspawner.spawner.data.SpawnerManager;
import github.nighter.smartspawner.spawner.item.SpawnerItemFactory;
import github.nighter.smartspawner.spawner.properties.SpawnerData;
import github.nighter.smartspawner.spawner.data.SpawnerFileHandler;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.CreatureSpawner;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityExplodeEvent;
import org.bukkit.event.block.BlockExplodeEvent;
import org.bukkit.entity.EntityType;
import org.bukkit.inventory.ItemStack;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;

public class SpawnerExplosionListener implements Listener {
private final SmartSpawner plugin;
private final SpawnerManager spawnerManager;
private final SpawnerFileHandler spawnerFileHandler;
private final SpawnerItemFactory spawnerItemFactory;
private final HopperHandler hopperHandler;

public SpawnerExplosionListener(SmartSpawner plugin) {
this.plugin = plugin;
this.spawnerManager = plugin.getSpawnerManager();
this.spawnerFileHandler = plugin.getSpawnerFileHandler();
this.spawnerItemFactory = plugin.getSpawnerItemFactory();
this.hopperHandler = plugin.getHopperHandler();
}

Expand Down Expand Up @@ -61,6 +69,10 @@ private void handleExplosion(EntityExplodeEvent event, List<Block> blockList) {
e = new SpawnerExplodeEvent(null, spawnerData.getSpawnerLocation(), 1, false);
}
} else {
double explosionDropChance = plugin.getConfig().getDouble("spawner_properties.default.explosion_drop_chance", 0.0);
if (passesChanceCheck(explosionDropChance)) {
dropSmartSpawnerItem(block, spawnerData);
}
spawnerData.getSpawnerStop().set(true);
String spawnerId = spawnerData.getSpawnerId();
cleanupAssociatedHopper(block);
Expand All @@ -74,9 +86,13 @@ private void handleExplosion(EntityExplodeEvent event, List<Block> blockList) {
Bukkit.getPluginManager().callEvent(e);
}
} else {
// Allow vanilla spawners to be destroyed
if (plugin.getConfig().getBoolean("natural_spawner.protect_from_explosions", false)) {
blocksToRemove.add(block);
} else {
double naturalDropChance = plugin.getConfig().getDouble("natural_spawner.explosion_drop_chance", 0.0);
if (passesChanceCheck(naturalDropChance)) {
dropVanillaSpawnerItem(block);
}
}
}
} else if (block.getType() == Material.RESPAWN_ANCHOR) {
Expand Down Expand Up @@ -114,6 +130,43 @@ private boolean hasProtectedSpawnersNearby(Block anchorBlock) {
return false;
}

private boolean passesChanceCheck(double chance) {
if (chance >= 100.0) return true;
if (chance <= 0.0) return false;
return ThreadLocalRandom.current().nextDouble(100.0) < chance;
}

private void dropSmartSpawnerItem(Block block, SpawnerData spawnerData) {
Location location = block.getLocation();
World world = location.getWorld();
if (world == null) return;

ItemStack spawnerItem;
if (spawnerData.isItemSpawner()) {
spawnerItem = spawnerItemFactory.createItemSpawnerItem(spawnerData.getSpawnedItemMaterial());
} else {
spawnerItem = spawnerItemFactory.createSmartSpawnerItem(spawnerData.getEntityType());
}
world.dropItemNaturally(location.toCenterLocation(), spawnerItem);
}

private void dropVanillaSpawnerItem(Block block) {
Location location = block.getLocation();
World world = location.getWorld();
if (world == null) return;

if (block.getState(false) instanceof CreatureSpawner creatureSpawner) {
EntityType entityType = creatureSpawner.getSpawnedType();
ItemStack spawnerItem;
if (plugin.getConfig().getBoolean("natural_spawner.convert_to_smart_spawner", false)) {
spawnerItem = spawnerItemFactory.createSmartSpawnerItem(entityType);
} else {
spawnerItem = spawnerItemFactory.createVanillaSpawnerItem(entityType);
}
world.dropItemNaturally(location.toCenterLocation(), spawnerItem);
}
}

private void cleanupAssociatedHopper(Block block) {
Block blockBelow = block.getRelative(BlockFace.DOWN);
if (blockBelow.getType() == Material.HOPPER && hopperHandler != null) {
Expand Down
14 changes: 14 additions & 0 deletions core/src/main/resources/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ spawner_properties:
allow_exp_mending: true # Allow spawners to repair items with stored XP
protect_from_explosions: true # Protect spawner blocks from explosion

# Chance (0-100%) for spawner to drop as item when destroyed by explosion
# Only applies when protect_from_explosions is false
# 100.0 = always drops, 50.0 = 50% chance, 0.0 = never drops (just destroyed)
explosion_drop_chance: 0.0

#---------------------------------------------------
# Spawner Breaking Mechanics
#---------------------------------------------------
Expand All @@ -62,6 +67,10 @@ spawner_break:
# Durability impact on tools when breaking a spawner
durability_loss: 1 # Number of durability points deducted

# Chance (0-100%) for spawner to drop when broken with valid tool
# 100.0 = always drops, 50.0 = 50% chance, 0.0 = never drops
drop_chance: 100.0

# Enchantment Requirements for successful spawner collection
silk_touch:
required: true # Whether Silk Touch is needed to obtain spawners
Expand All @@ -85,6 +94,11 @@ natural_spawner:
# Whether natural spawner block will be protected from explosions
protect_from_explosions: false

# Chance (0-100%) for natural spawner to drop as item when destroyed by explosion
# Only applies when protect_from_explosions is false
# 100.0 = always drops, 50.0 = 50% chance, 0.0 = never drops (just destroyed)
explosion_drop_chance: 0.0

#---------------------------------------------------
# Economy Settings
#---------------------------------------------------
Expand Down
12 changes: 10 additions & 2 deletions core/src/main/resources/language/DonutSMP/messages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -152,11 +152,11 @@ spawner_break_no_permission:
sound: entity.villager.no

spawner_break_silk_touch_required:
action_bar: "&#ff5252ꜱɪʟᴋ ᴛᴏᴜᴄʜ ʀᴇǫᴜɪʀᴇᴅ"
action_bar: "&#ff5252ᴛᴏ ᴍɪɴᴇ ᴀ ꜱᴘᴀᴡɴᴇʀ ʏᴏᴜ ɴᴇᴇᴅ ᴀ ᴘɪᴄᴋᴀxᴇ ᴡɪᴛʜ ꜱɪʟᴋ ᴛᴏᴜᴄʜ ᴇɴᴄʜᴀɴᴛᴍᴇɴᴛ"
sound: item.shield.block

spawner_break_required_tools:
action_bar: "&#ff5252ᴄᴀɴ'ᴛ ʙʀᴇᴀᴋ ꜱᴘᴀᴡɴᴇʀ ᴡɪᴛʜ ᴛʜɪꜱ ᴛᴏᴏʟ!"
action_bar: "&#ff5252ᴛᴏ ᴍɪɴᴇ ᴀ ꜱᴘᴀᴡɴᴇʀ ʏᴏᴜ ɴᴇᴇᴅ ᴀ ᴘɪᴄᴋᴀxᴇ ᴡɪᴛʜ ꜱɪʟᴋ ᴛᴏᴜᴄʜ ᴇɴᴄʜᴀɴᴛᴍᴇɴᴛ"
sound: item.shield.block

spawner_break_warning:
Expand All @@ -165,6 +165,14 @@ spawner_break_warning:
subtitle: "&#e6e6faɪᴛᴇᴍꜱ ɪɴ ꜱᴘᴀᴡɴᴇʀ ᴡɪʟʟ ʙᴇ ʟᴏꜱᴛ!"
sound: block.note_block.bass

spawner_break_success:
action_bar: "&#37eb9aꜱᴘᴀᴡɴᴇʀ ꜱᴜᴄᴄᴇꜱꜱꜰᴜʟʟʏ ᴍɪɴᴇᴅ! &#f8f8ff(&#37eb9a{chance}% &#f8f8ffᴄʜᴀɴᴄᴇ)"
sound: entity.experience_orb.pickup

spawner_break_no_drop:
action_bar: "&#ff5252ꜰᴀɪʟᴇᴅ ᴛᴏ ᴍɪɴᴇ ꜱᴘᴀᴡɴᴇʀ &#f8f8ff(&#ff5252{chance}% &#f8f8ffᴄʜᴀɴᴄᴇ)"
sound: block.note_block.bass

natural_spawner_break_blocked:
action_bar: "&#ff5252ɴᴀᴛᴜʀᴀʟ ꜱᴘᴀᴡɴᴇʀꜱ ᴄᴀɴɴᴏᴛ ʙᴇ ʙʀᴏᴋᴇɴ ᴀɴᴅ ᴜꜱᴇᴅ"
sound: block.note_block.pling
Expand Down
12 changes: 10 additions & 2 deletions core/src/main/resources/language/de_DE/messages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -152,11 +152,11 @@ spawner_break_no_permission:
sound: entity.villager.no

spawner_break_silk_touch_required:
action_bar: "&#ff5252ʙᴇʜᴜᴛꜱᴀᴍᴋᴇɪᴛ ᴇʀꜰᴏʀᴅᴇʀʟɪᴄʜ"
action_bar: "&#ff5252ᴢᴜᴍ ᴀʙʙᴀᴜᴇɴ ᴇɪɴᴇꜱ ꜱᴘᴀᴡɴᴇʀꜱ ʙʀᴀᴜᴄʜꜱᴛ ᴅᴜ ᴇɪɴᴇ ꜱᴘɪᴛᴢʜᴀᴄᴋᴇ ᴍɪᴛ ʙᴇʜᴜᴛꜱᴀᴍᴋᴇɪᴛ"
sound: item.shield.block

spawner_break_required_tools:
action_bar: "&#ff5252ꜱᴘᴀᴡɴᴇʀ ᴋᴀɴɴ ɴɪᴄʜᴛ ᴍɪᴛ ᴅɪᴇꜱᴇᴍ ᴡᴇʀᴋᴢᴇᴜɢ ᴀʙɢᴇʙᴀᴜᴛ ᴡᴇʀᴅᴇɴ!"
action_bar: "&#ff5252ᴢᴜᴍ ᴀʙʙᴀᴜᴇɴ ᴇɪɴᴇꜱ ꜱᴘᴀᴡɴᴇʀꜱ ʙʀᴀᴜᴄʜꜱᴛ ᴅᴜ ᴇɪɴᴇ ꜱᴘɪᴛᴢʜᴀᴄᴋᴇ ᴍɪᴛ ʙᴇʜᴜᴛꜱᴀᴍᴋᴇɪᴛ"
sound: item.shield.block

spawner_break_warning:
Expand All @@ -165,6 +165,14 @@ spawner_break_warning:
subtitle: "&#e6e6faɪᴛᴇᴍꜱ ɪᴍ ꜱᴘᴀᴡɴᴇʀ ɢᴇʜᴇɴ ᴠᴇʀʟᴏʀᴇɴ!"
sound: block.note_block.bass

spawner_break_success:
action_bar: "&#37eb9aꜱᴘᴀᴡɴᴇʀ ᴇʀꜰᴏʟɢʀᴇɪᴄʜ ᴀʙɢᴇʙᴀᴜᴛ! &#f8f8ff(&#37eb9a{chance}% &#f8f8ffᴄʜᴀɴᴄᴇ)"
sound: entity.experience_orb.pickup

spawner_break_no_drop:
action_bar: "&#ff5252ꜱᴘᴀᴡɴᴇʀ ᴀʙʙᴀᴜ ꜰᴇʜʟɢᴇꜱᴄʜʟᴀɢᴇɴ &#f8f8ff(&#ff5252{chance}% &#f8f8ffᴄʜᴀɴᴄᴇ)"
sound: block.note_block.bass

natural_spawner_break_blocked:
action_bar: "&#ff5252ɴᴀᴛᴜ̈ʀʟɪᴄʜᴇ ꜱᴘᴀᴡɴᴇʀ ᴋᴏ̈ɴɴᴇɴ ɴɪᴄʜᴛ ᴀʙɢᴇʙᴀᴜᴛ ᴜɴᴅ ᴠᴇʀᴡᴇɴᴅᴇᴛ ᴡᴇʀᴅᴇɴ"
sound: block.note_block.pling
Expand Down
12 changes: 10 additions & 2 deletions core/src/main/resources/language/en_US/messages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -152,11 +152,11 @@ spawner_break_no_permission:
sound: entity.villager.no

spawner_break_silk_touch_required:
action_bar: "&#ff5252ꜱɪʟᴋ ᴛᴏᴜᴄʜ ʀᴇǫᴜɪʀᴇᴅ"
action_bar: "&#ff5252ᴛᴏ ᴍɪɴᴇ ᴀ ꜱᴘᴀᴡɴᴇʀ ʏᴏᴜ ɴᴇᴇᴅ ᴀ ᴘɪᴄᴋᴀxᴇ ᴡɪᴛʜ ꜱɪʟᴋ ᴛᴏᴜᴄʜ ᴇɴᴄʜᴀɴᴛᴍᴇɴᴛ"
sound: item.shield.block

spawner_break_required_tools:
action_bar: "&#ff5252ᴄᴀɴ'ᴛ ʙʀᴇᴀᴋ ꜱᴘᴀᴡɴᴇʀ ᴡɪᴛʜ ᴛʜɪꜱ ᴛᴏᴏʟ!"
action_bar: "&#ff5252ᴛᴏ ᴍɪɴᴇ ᴀ ꜱᴘᴀᴡɴᴇʀ ʏᴏᴜ ɴᴇᴇᴅ ᴀ ᴘɪᴄᴋᴀxᴇ ᴡɪᴛʜ ꜱɪʟᴋ ᴛᴏᴜᴄʜ ᴇɴᴄʜᴀɴᴛᴍᴇɴᴛ"
sound: item.shield.block

spawner_break_warning:
Expand All @@ -165,6 +165,14 @@ spawner_break_warning:
subtitle: "&#e6e6faɪᴛᴇᴍꜱ ɪɴ ꜱᴘᴀᴡɴᴇʀ ᴡɪʟʟ ʙᴇ ʟᴏꜱᴛ!"
sound: block.note_block.bass

spawner_break_success:
action_bar: "&#37eb9aꜱᴘᴀᴡɴᴇʀ ꜱᴜᴄᴄᴇꜱꜱꜰᴜʟʟʏ ᴍɪɴᴇᴅ! &#f8f8ff(&#37eb9a{chance}% &#f8f8ffᴄʜᴀɴᴄᴇ)"
sound: entity.experience_orb.pickup

spawner_break_no_drop:
action_bar: "&#ff5252ꜰᴀɪʟᴇᴅ ᴛᴏ ᴍɪɴᴇ ꜱᴘᴀᴡɴᴇʀ &#f8f8ff(&#ff5252{chance}% &#f8f8ffᴄʜᴀɴᴄᴇ)"
sound: block.note_block.bass

natural_spawner_break_blocked:
action_bar: "&#ff5252ɴᴀᴛᴜʀᴀʟ ꜱᴘᴀᴡɴᴇʀꜱ ᴄᴀɴɴᴏᴛ ʙᴇ ʙʀᴏᴋᴇɴ ᴀɴᴅ ᴜꜱᴇᴅ"
sound: block.note_block.pling
Expand Down
12 changes: 10 additions & 2 deletions core/src/main/resources/language/vi_VN/messages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -155,11 +155,11 @@ spawner_break_no_permission:
sound: entity.villager.no

spawner_break_silk_touch_required:
action_bar: "&#ff5252ᴄầɴ ᴘʜù ᴘʜéᴘ sɪʟᴋ ᴛᴏᴜᴄʜ để đậᴘ"
action_bar: "&#ff5252để đậᴘ ꜱᴘᴀᴡɴᴇʀ ᴄầɴ ᴄúᴘ ᴄó ᴘʜù ᴘʜéᴘ sɪʟᴋ ᴛᴏᴜᴄʜ"
sound: item.shield.block

spawner_break_required_tools:
action_bar: "&#ff5252ᴋʜôɴɢ ᴛʜể ᴘʜá ꜱᴘᴀᴡɴᴇʀ ʙằɴɢ ᴄôɴɢ ᴄụ ɴàʏ!!"
action_bar: "&#ff5252để đậᴘ ꜱᴘᴀᴡɴᴇʀ ᴄầɴ ᴄúᴘ ᴄó ᴘʜù ᴘʜéᴘ sɪʟᴋ ᴛᴏᴜᴄʜ"
sound: item.shield.block

spawner_break_warning:
Expand All @@ -168,6 +168,14 @@ spawner_break_warning:
subtitle: "&#e6e6faᴛấᴛ ᴄả ᴠậᴛ ᴘʜẩᴍ ᴛʀᴏɴɢ ꜱᴘᴀᴡɴᴇʀ sẽ ʙị ᴍấᴛ!"
sound: block.note_block.bass

spawner_break_success:
action_bar: "&#37eb9ađã đậᴘ ᴛʜàɴʜ ᴄôɴɢ ꜱᴘᴀᴡɴᴇʀ! &#f8f8ff(&#37eb9a{chance}% &#f8f8ffᴛỉ ʟệ)"
sound: entity.experience_orb.pickup

spawner_break_no_drop:
action_bar: "&#ff5252đậᴘ ꜱᴘᴀᴡɴᴇʀ ᴛʜấᴛ ʙạɪ &#f8f8ff(&#ff5252{chance}% &#f8f8ffᴛỉ ʟệ)"
sound: block.note_block.bass

natural_spawner_break_blocked:
action_bar: "&#ff5252ꜱᴘᴀᴡɴᴇʀ ᴛự ɴʜɪêɴ ᴋʜôɴɢ ᴛʜể ʙị ᴘʜá và sử ᴅụɴɢ"
sound: block.note_block.pling
Expand Down
Loading