This commit is contained in:
@@ -34,6 +34,7 @@ Creative and spectator players do not receive rewards by default. Creative earni
|
||||
- Java 25 for compilation/server runtime
|
||||
- JobsReborn 5.2.6.x
|
||||
- CMILib, as required by JobsReborn
|
||||
- GriefPrevention 16.18.6+ (optional; enables protected-claim emergency building)
|
||||
- A JobsReborn-compatible economy if monetary payouts are desired
|
||||
|
||||
## Build
|
||||
@@ -110,7 +111,7 @@ Other management commands:
|
||||
|
||||
Automatic emergencies default to a random 30-60 minute interval and require at least one online Firefighter. Manual starts use the same online-Firefighter requirement unless `emergencies.require-firefighters-online` is disabled.
|
||||
|
||||
Emergency fire generation:
|
||||
Emergency fire generation and response behavior:
|
||||
|
||||
- Uses admin-defined named sites and radii.
|
||||
- Can be limited to configured world names with `emergencies.allowed-worlds`.
|
||||
@@ -119,7 +120,13 @@ Emergency fire generation:
|
||||
- Cancels emergency-fire spread and spread ignition.
|
||||
- Cancels nearby block burning.
|
||||
- Tracks and removes remaining emergency fire on completion, manual stop, expiry, plugin disable, or server shutdown.
|
||||
- Periodically removes stale tracked entries if an emergency fire disappears through another legitimate game event.
|
||||
- Grants on-duty Firefighters the temporary `rosefirefighter.emergency.build` runtime permission while an incident is active.
|
||||
- When GriefPrevention is installed, uses its claim-permission API to allow block placement and water-bucket response only inside the active site. Emergency fire and tracked temporary response blocks can also be broken there.
|
||||
- Does **not** grant `/IgnoreClaims` or global `griefprevention.adminclaims`, so an incident does not become a server-wide claim bypass.
|
||||
- Tracks blocks Firefighters place at the incident and restores the replaced block state when the incident ends.
|
||||
- Returns one consumed placement item per still-tracked placement to the Firefighter who placed it. Blocks the player already broke are removed from the return ledger to avoid duplication.
|
||||
- Saves returns for offline Firefighters in `pending-returns.yml` and delivers them on their next join.
|
||||
- Periodically removes stale tracked fire entries if an emergency fire disappears through another legitimate game event.
|
||||
- Broadcasts configurable alerts, by default to all online players.
|
||||
|
||||
Example alert:
|
||||
@@ -128,6 +135,22 @@ Example alert:
|
||||
🔥 FIRE ALERT! A fire has been reported at RoseGarden near X:123 Y:64 Z:-245. Firefighters are needed immediately!
|
||||
```
|
||||
|
||||
## Protected-site integration
|
||||
|
||||
`emergencies.protected-site-building` controls temporary emergency build access. The permission is granted with Bukkit's runtime `PermissionAttachment`, so it is temporary and does not create permanent LuckPerms/permissions-plugin data.
|
||||
|
||||
With GriefPrevention installed, RoseFirefighter listens to `ClaimPermissionCheckEvent` and clears a denied `Build` check only when all of the following are true:
|
||||
|
||||
- An emergency is active.
|
||||
- The player is currently working the JobsReborn Firefighter job.
|
||||
- The player currently has RoseFirefighter's temporary emergency-build attachment.
|
||||
- The affected block is inside the active site's configured radius.
|
||||
- The action is placing a block, emptying a bucket, breaking an emergency fire, or breaking a tracked temporary Firefighter block.
|
||||
|
||||
Normal protected structures cannot be broken merely because an emergency is active.
|
||||
|
||||
`emergencies.cleanup-firefighter-blocks` controls whether placements made by Firefighters inside the active site are added to the temporary response-construction ledger.
|
||||
|
||||
## Configuration
|
||||
|
||||
`src/main/resources/config.yml` includes controls for:
|
||||
@@ -144,6 +167,7 @@ Example alert:
|
||||
- Emergency reward multiplier.
|
||||
- Announcement behavior.
|
||||
- Permitted emergency worlds.
|
||||
- Protected-site emergency building and temporary Firefighter block cleanup.
|
||||
- Saved emergency sites.
|
||||
|
||||
## Development notes
|
||||
@@ -160,4 +184,10 @@ JobsReborn is referenced through its documented JitPack coordinate:
|
||||
compileOnly("com.github.Zrips:Jobs:v5.2.6.2")
|
||||
```
|
||||
|
||||
The JobsReborn dependency is non-transitive because RoseFirefighter only needs the JobsReborn API classes and should not pull JobsReborn's optional integration dependencies into its compile classpath.
|
||||
GriefPrevention is an optional compile-time API dependency and a runtime soft dependency:
|
||||
|
||||
```kotlin
|
||||
compileOnly("com.github.GriefPrevention:GriefPrevention:16.18.6")
|
||||
```
|
||||
|
||||
The JobsReborn and GriefPrevention dependencies are non-transitive because RoseFirefighter only needs their public API classes and should not pull their optional integrations into its compile classpath.
|
||||
|
||||
@@ -18,6 +18,9 @@ dependencies {
|
||||
compileOnly("com.github.Zrips:Jobs:v5.2.6.2") {
|
||||
isTransitive = false
|
||||
}
|
||||
compileOnly("com.github.GriefPrevention:GriefPrevention:16.18.6") {
|
||||
isTransitive = false
|
||||
}
|
||||
}
|
||||
|
||||
java {
|
||||
|
||||
@@ -0,0 +1,219 @@
|
||||
package net.therosegarden.firefighter;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockState;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.BlockBreakEvent;
|
||||
import org.bukkit.event.block.BlockMultiPlaceEvent;
|
||||
import org.bukkit.event.block.BlockPlaceEvent;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
final class EmergencyConstructionManager implements Listener {
|
||||
private final RoseFirefighterPlugin plugin;
|
||||
private final JobsFacade jobs;
|
||||
private final EmergencyPermissionManager permissions;
|
||||
private final Map<UUID, Placement> placements = new HashMap<>();
|
||||
private final Map<BlockKey, UUID> byBlock = new HashMap<>();
|
||||
private final Map<UUID, List<ItemStack>> pendingReturns = new HashMap<>();
|
||||
private final File pendingFile;
|
||||
private EmergencySite activeSite;
|
||||
|
||||
EmergencyConstructionManager(RoseFirefighterPlugin plugin, JobsFacade jobs, EmergencyPermissionManager permissions) {
|
||||
this.plugin = plugin;
|
||||
this.jobs = jobs;
|
||||
this.permissions = permissions;
|
||||
this.pendingFile = new File(plugin.getDataFolder(), "pending-returns.yml");
|
||||
loadPendingReturns();
|
||||
}
|
||||
|
||||
void begin(EmergencySite site) {
|
||||
activeSite = site;
|
||||
placements.clear();
|
||||
byBlock.clear();
|
||||
}
|
||||
|
||||
boolean isTemporaryBlock(Block block) {
|
||||
return block != null && byBlock.containsKey(BlockKey.of(block));
|
||||
}
|
||||
|
||||
void cleanupAndReturn() {
|
||||
if (placements.isEmpty()) {
|
||||
activeSite = null;
|
||||
return;
|
||||
}
|
||||
|
||||
Map<UUID, List<ItemStack>> returns = new HashMap<>();
|
||||
for (Placement placement : new ArrayList<>(placements.values())) {
|
||||
boolean restoredAny = false;
|
||||
for (PlacedBlock placed : placement.blocks()) {
|
||||
Block current = placed.key().block();
|
||||
if (current != null && current.getType() == placed.placedType()) {
|
||||
placed.replacedState().update(true, false);
|
||||
restoredAny = true;
|
||||
}
|
||||
}
|
||||
if (restoredAny) {
|
||||
returns.computeIfAbsent(placement.owner(), ignored -> new ArrayList<>()).add(placement.returnItem().clone());
|
||||
}
|
||||
}
|
||||
|
||||
placements.clear();
|
||||
byBlock.clear();
|
||||
activeSite = null;
|
||||
|
||||
for (Map.Entry<UUID, List<ItemStack>> entry : returns.entrySet()) {
|
||||
returnItems(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onPlace(BlockPlaceEvent event) {
|
||||
if (activeSite == null || !plugin.getConfig().getBoolean("emergencies.cleanup-firefighter-blocks", true)) {
|
||||
return;
|
||||
}
|
||||
Player player = event.getPlayer();
|
||||
if (!jobs.isFirefighter(player) || !permissions.hasBuildAccess(player) || !activeSite.contains(event.getBlock().getLocation())) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<BlockState> replacedStates = new ArrayList<>();
|
||||
if (event instanceof BlockMultiPlaceEvent multi) {
|
||||
replacedStates.addAll(multi.getReplacedBlockStates());
|
||||
} else {
|
||||
replacedStates.add(event.getBlockReplacedState());
|
||||
}
|
||||
|
||||
List<PlacedBlock> blocks = new ArrayList<>();
|
||||
for (BlockState replaced : replacedStates) {
|
||||
Block current = replaced.getBlock();
|
||||
if (!activeSite.contains(current.getLocation())) {
|
||||
continue;
|
||||
}
|
||||
blocks.add(new PlacedBlock(BlockKey.of(current), current.getType(), replaced));
|
||||
}
|
||||
if (blocks.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ItemStack returned = event.getItemInHand().clone();
|
||||
returned.setAmount(1);
|
||||
UUID placementId = UUID.randomUUID();
|
||||
Placement placement = new Placement(placementId, player.getUniqueId(), returned, List.copyOf(blocks));
|
||||
placements.put(placementId, placement);
|
||||
for (PlacedBlock block : blocks) {
|
||||
byBlock.put(block.key(), placementId);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onBreak(BlockBreakEvent event) {
|
||||
UUID placementId = byBlock.get(BlockKey.of(event.getBlock()));
|
||||
if (placementId == null) {
|
||||
return;
|
||||
}
|
||||
Placement placement = placements.remove(placementId);
|
||||
if (placement == null) {
|
||||
return;
|
||||
}
|
||||
for (PlacedBlock block : placement.blocks()) {
|
||||
byBlock.remove(block.key());
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onJoin(PlayerJoinEvent event) {
|
||||
List<ItemStack> items = pendingReturns.remove(event.getPlayer().getUniqueId());
|
||||
if (items == null || items.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
giveItems(event.getPlayer(), items);
|
||||
savePendingReturns();
|
||||
}
|
||||
|
||||
private void returnItems(UUID owner, List<ItemStack> items) {
|
||||
Player player = Bukkit.getPlayer(owner);
|
||||
if (player != null && player.isOnline()) {
|
||||
giveItems(player, items);
|
||||
return;
|
||||
}
|
||||
pendingReturns.computeIfAbsent(owner, ignored -> new ArrayList<>()).addAll(items);
|
||||
savePendingReturns();
|
||||
}
|
||||
|
||||
private void giveItems(Player player, List<ItemStack> items) {
|
||||
int count = 0;
|
||||
for (ItemStack item : items) {
|
||||
if (item == null || item.getType() == Material.AIR || item.getAmount() <= 0) {
|
||||
continue;
|
||||
}
|
||||
count += item.getAmount();
|
||||
Map<Integer, ItemStack> overflow = player.getInventory().addItem(item.clone());
|
||||
for (ItemStack extra : overflow.values()) {
|
||||
player.getWorld().dropItemNaturally(player.getLocation(), extra);
|
||||
}
|
||||
}
|
||||
if (count > 0) {
|
||||
String message = plugin.message("messages.returned-blocks").replace("%count%", Integer.toString(count));
|
||||
if (!message.isEmpty()) {
|
||||
player.sendMessage(plugin.prefix() + message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void loadPendingReturns() {
|
||||
if (!pendingFile.isFile()) {
|
||||
return;
|
||||
}
|
||||
YamlConfiguration yaml = YamlConfiguration.loadConfiguration(pendingFile);
|
||||
for (String key : yaml.getKeys(false)) {
|
||||
try {
|
||||
UUID uuid = UUID.fromString(key);
|
||||
List<ItemStack> items = new ArrayList<>();
|
||||
for (Object value : yaml.getList(key, List.of())) {
|
||||
if (value instanceof ItemStack item) {
|
||||
items.add(item);
|
||||
}
|
||||
}
|
||||
if (!items.isEmpty()) {
|
||||
pendingReturns.put(uuid, items);
|
||||
}
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
plugin.getLogger().warning("Ignoring invalid pending-return UUID: " + key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void savePendingReturns() {
|
||||
YamlConfiguration yaml = new YamlConfiguration();
|
||||
for (Map.Entry<UUID, List<ItemStack>> entry : pendingReturns.entrySet()) {
|
||||
yaml.set(entry.getKey().toString(), entry.getValue());
|
||||
}
|
||||
try {
|
||||
if (!plugin.getDataFolder().exists() && !plugin.getDataFolder().mkdirs()) {
|
||||
plugin.getLogger().warning("Could not create plugin data folder for pending returns.");
|
||||
return;
|
||||
}
|
||||
yaml.save(pendingFile);
|
||||
} catch (IOException ex) {
|
||||
plugin.getLogger().severe("Could not save pending firefighter block returns: " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private record Placement(UUID id, UUID owner, ItemStack returnItem, List<PlacedBlock> blocks) {}
|
||||
private record PlacedBlock(BlockKey key, Material placedType, BlockState replacedState) {}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ final class EmergencyManager {
|
||||
private final Random random = new Random();
|
||||
private final Map<String, EmergencySite> sites = new LinkedHashMap<>();
|
||||
private final Set<BlockKey> activeFires = new HashSet<>();
|
||||
private EmergencyPermissionManager permissions;
|
||||
private EmergencyConstructionManager construction;
|
||||
|
||||
private EmergencySite activeSite;
|
||||
private BukkitTask expiryTask;
|
||||
@@ -38,6 +40,11 @@ final class EmergencyManager {
|
||||
this.jobs = jobs;
|
||||
}
|
||||
|
||||
void setEmergencyServices(EmergencyPermissionManager permissions, EmergencyConstructionManager construction) {
|
||||
this.permissions = permissions;
|
||||
this.construction = construction;
|
||||
}
|
||||
|
||||
void reload() {
|
||||
loadSites();
|
||||
scheduleNextAutomaticEmergency();
|
||||
@@ -48,6 +55,12 @@ final class EmergencyManager {
|
||||
cancelExpiryTask();
|
||||
cancelIntegrityTask();
|
||||
clearEmergencyFireBlocks();
|
||||
if (construction != null) {
|
||||
construction.cleanupAndReturn();
|
||||
}
|
||||
if (permissions != null) {
|
||||
permissions.deactivate();
|
||||
}
|
||||
activeFires.clear();
|
||||
activeSite = null;
|
||||
}
|
||||
@@ -216,6 +229,12 @@ final class EmergencyManager {
|
||||
|
||||
activeSite = site;
|
||||
activeFires.addAll(spawned);
|
||||
if (construction != null) {
|
||||
construction.begin(site);
|
||||
}
|
||||
if (permissions != null) {
|
||||
permissions.activate(site);
|
||||
}
|
||||
|
||||
int durationMinutes = Math.max(1, plugin.getConfig().getInt("emergencies.duration-minutes", 10));
|
||||
expiryTask = Bukkit.getScheduler().runTaskLater(plugin, () -> endEmergency(false), durationMinutes * 60L * 20L);
|
||||
@@ -317,6 +336,10 @@ final class EmergencyManager {
|
||||
return;
|
||||
}
|
||||
|
||||
if (permissions != null) {
|
||||
permissions.syncResponders();
|
||||
}
|
||||
|
||||
activeFires.removeIf(key -> {
|
||||
Block block = key.block();
|
||||
return block == null || (block.getType() != Material.FIRE && block.getType() != Material.SOUL_FIRE);
|
||||
@@ -338,6 +361,12 @@ final class EmergencyManager {
|
||||
cancelIntegrityTask();
|
||||
|
||||
clearEmergencyFireBlocks();
|
||||
if (construction != null) {
|
||||
construction.cleanupAndReturn();
|
||||
}
|
||||
if (permissions != null) {
|
||||
permissions.deactivate();
|
||||
}
|
||||
activeFires.clear();
|
||||
activeSite = null;
|
||||
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
package net.therosegarden.firefighter;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
import org.bukkit.permissions.PermissionAttachment;
|
||||
|
||||
final class EmergencyPermissionManager implements Listener {
|
||||
static final String BUILD_PERMISSION = "rosefirefighter.emergency.build";
|
||||
|
||||
private final RoseFirefighterPlugin plugin;
|
||||
private final JobsFacade jobs;
|
||||
private final Map<UUID, PermissionAttachment> attachments = new HashMap<>();
|
||||
private EmergencySite activeSite;
|
||||
|
||||
EmergencyPermissionManager(RoseFirefighterPlugin plugin, JobsFacade jobs) {
|
||||
this.plugin = plugin;
|
||||
this.jobs = jobs;
|
||||
}
|
||||
|
||||
void activate(EmergencySite site) {
|
||||
activeSite = site;
|
||||
syncResponders();
|
||||
}
|
||||
|
||||
void syncResponders() {
|
||||
if (activeSite == null || !plugin.getConfig().getBoolean("emergencies.protected-site-building", true)) {
|
||||
revokeAll(true);
|
||||
return;
|
||||
}
|
||||
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||
if (jobs.isFirefighter(player)) {
|
||||
grant(player);
|
||||
} else {
|
||||
revoke(player, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void deactivate() {
|
||||
activeSite = null;
|
||||
revokeAll(true);
|
||||
}
|
||||
|
||||
boolean hasBuildAccess(Player player) {
|
||||
return activeSite != null && player != null && player.hasPermission(BUILD_PERMISSION) && jobs.isFirefighter(player);
|
||||
}
|
||||
|
||||
boolean isInsideActiveSite(org.bukkit.Location location) {
|
||||
return activeSite != null && activeSite.contains(location);
|
||||
}
|
||||
|
||||
private void grant(Player player) {
|
||||
if (attachments.containsKey(player.getUniqueId())) {
|
||||
return;
|
||||
}
|
||||
PermissionAttachment attachment = player.addAttachment(plugin);
|
||||
attachment.setPermission(BUILD_PERMISSION, true);
|
||||
attachments.put(player.getUniqueId(), attachment);
|
||||
String message = plugin.message("messages.temporary-build-enabled");
|
||||
if (!message.isEmpty()) {
|
||||
player.sendMessage(plugin.prefix() + message);
|
||||
}
|
||||
}
|
||||
|
||||
private void revoke(Player player, boolean notify) {
|
||||
PermissionAttachment attachment = attachments.remove(player.getUniqueId());
|
||||
if (attachment == null) {
|
||||
return;
|
||||
}
|
||||
player.removeAttachment(attachment);
|
||||
if (notify) {
|
||||
String message = plugin.message("messages.temporary-build-disabled");
|
||||
if (!message.isEmpty()) {
|
||||
player.sendMessage(plugin.prefix() + message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void revokeAll(boolean notify) {
|
||||
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||
revoke(player, notify);
|
||||
}
|
||||
attachments.clear();
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onJoin(PlayerJoinEvent event) {
|
||||
if (activeSite != null) {
|
||||
Bukkit.getScheduler().runTask(plugin, this::syncResponders);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onQuit(PlayerQuitEvent event) {
|
||||
revoke(event.getPlayer(), false);
|
||||
}
|
||||
}
|
||||
@@ -9,4 +9,13 @@ record EmergencySite(String name, String worldName, int x, int y, int z, int rad
|
||||
World world = Bukkit.getWorld(worldName);
|
||||
return world == null ? null : new Location(world, x + 0.5, y, z + 0.5);
|
||||
}
|
||||
|
||||
boolean contains(Location location) {
|
||||
if (location == null || location.getWorld() == null || !location.getWorld().getName().equals(worldName)) {
|
||||
return false;
|
||||
}
|
||||
long dx = (long) location.getBlockX() - x;
|
||||
long dz = (long) location.getBlockZ() - z;
|
||||
return (dx * dx) + (dz * dz) <= (long) radius * radius;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
package net.therosegarden.firefighter;
|
||||
|
||||
import me.ryanhamshire.GriefPrevention.ClaimPermission;
|
||||
import me.ryanhamshire.GriefPrevention.events.ClaimPermissionCheckEvent;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.BlockBreakEvent;
|
||||
import org.bukkit.event.block.BlockPlaceEvent;
|
||||
import org.bukkit.event.player.PlayerBucketEmptyEvent;
|
||||
|
||||
final class GriefPreventionBridge implements Listener {
|
||||
private final EmergencyManager emergencies;
|
||||
private final EmergencyPermissionManager permissions;
|
||||
private final EmergencyConstructionManager construction;
|
||||
|
||||
GriefPreventionBridge(EmergencyManager emergencies, EmergencyPermissionManager permissions,
|
||||
EmergencyConstructionManager construction) {
|
||||
this.emergencies = emergencies;
|
||||
this.permissions = permissions;
|
||||
this.construction = construction;
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void onClaimPermissionCheck(ClaimPermissionCheckEvent event) {
|
||||
if (event.getRequiredPermission() != ClaimPermission.Build || event.getDenialReason() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Player player = event.getCheckedPlayer();
|
||||
if (player == null || !permissions.hasBuildAccess(player)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Event trigger = event.getTriggeringEvent();
|
||||
if (trigger instanceof BlockPlaceEvent placeEvent) {
|
||||
allowIfInside(event, placeEvent.getBlock().getLocation());
|
||||
return;
|
||||
}
|
||||
|
||||
if (trigger instanceof PlayerBucketEmptyEvent bucketEvent) {
|
||||
allowIfInside(event, bucketEvent.getBlock().getLocation());
|
||||
return;
|
||||
}
|
||||
|
||||
if (trigger instanceof BlockBreakEvent breakEvent) {
|
||||
Block block = breakEvent.getBlock();
|
||||
if ((emergencies.isEmergencyFire(block) || construction.isTemporaryBlock(block))
|
||||
&& permissions.isInsideActiveSite(block.getLocation())) {
|
||||
event.setDenialReason(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void allowIfInside(ClaimPermissionCheckEvent event, Location location) {
|
||||
if (permissions.isInsideActiveSite(location)) {
|
||||
event.setDenialReason(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.PluginCommand;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
public final class RoseFirefighterPlugin extends JavaPlugin {
|
||||
@@ -16,6 +17,8 @@ public final class RoseFirefighterPlugin extends JavaPlugin {
|
||||
private FirefighterRewardService rewards;
|
||||
private EmergencyManager emergencies;
|
||||
private FireSafetyAndOriginListener safetyListener;
|
||||
private EmergencyPermissionManager emergencyPermissions;
|
||||
private EmergencyConstructionManager emergencyConstruction;
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
@@ -26,9 +29,23 @@ public final class RoseFirefighterPlugin extends JavaPlugin {
|
||||
emergencies = new EmergencyManager(this, jobs);
|
||||
rewards.setEmergencyManager(emergencies);
|
||||
safetyListener = new FireSafetyAndOriginListener(this, rewards, emergencies);
|
||||
emergencyPermissions = new EmergencyPermissionManager(this, jobs);
|
||||
emergencyConstruction = new EmergencyConstructionManager(this, jobs, emergencyPermissions);
|
||||
emergencies.setEmergencyServices(emergencyPermissions, emergencyConstruction);
|
||||
|
||||
Bukkit.getPluginManager().registerEvents(new FireInteractionListener(this, rewards, emergencies), this);
|
||||
Bukkit.getPluginManager().registerEvents(safetyListener, this);
|
||||
Bukkit.getPluginManager().registerEvents(emergencyPermissions, this);
|
||||
Bukkit.getPluginManager().registerEvents(emergencyConstruction, this);
|
||||
|
||||
Plugin griefPrevention = Bukkit.getPluginManager().getPlugin("GriefPrevention");
|
||||
if (griefPrevention != null && griefPrevention.isEnabled()) {
|
||||
Bukkit.getPluginManager().registerEvents(
|
||||
new GriefPreventionBridge(emergencies, emergencyPermissions, emergencyConstruction), this);
|
||||
getLogger().info("GriefPrevention detected. Emergency-site build overrides are enabled.");
|
||||
} else {
|
||||
getLogger().info("GriefPrevention not detected. Emergency build permission will remain internal to RoseFirefighter.");
|
||||
}
|
||||
|
||||
FirefighterCommand commandHandler = new FirefighterCommand(this, jobs, emergencies);
|
||||
PluginCommand command = Objects.requireNonNull(getCommand("firefighter"), "firefighter command missing from plugin.yml");
|
||||
|
||||
@@ -48,6 +48,9 @@ emergencies:
|
||||
enabled: true
|
||||
automatic: true
|
||||
|
||||
protected-site-building: true
|
||||
cleanup-firefighter-blocks: true
|
||||
|
||||
# Applies to both automatic and manual emergency starts. Set false if admins
|
||||
# should be able to start/test an emergency with no Firefighters online.
|
||||
require-firefighters-online: true
|
||||
@@ -80,6 +83,9 @@ messages:
|
||||
cleared: '&aFire at &f%site% &ahas been brought under control.'
|
||||
expired: '&eThe fire response at &f%site% &ehas ended.'
|
||||
joined-needed: '&7You must be working as a &cFirefighter &7to receive firefighting pay.'
|
||||
temporary-build-enabled: '&eEmergency build access is active inside the response site.'
|
||||
temporary-build-disabled: '&7Emergency build access has ended.'
|
||||
returned-blocks: '&aReturned &f%count% &atemporary response block item(s) from the completed incident.'
|
||||
|
||||
# Managed with /firefighter site add <name> [radius]
|
||||
sites: {}
|
||||
|
||||
@@ -3,6 +3,7 @@ version: '${version}'
|
||||
main: net.therosegarden.firefighter.RoseFirefighterPlugin
|
||||
api-version: '26.2'
|
||||
depend: [Jobs]
|
||||
softdepend: [GriefPrevention]
|
||||
description: Firefighter profession companion and controlled fire emergencies for JobsReborn.
|
||||
commands:
|
||||
firefighter:
|
||||
@@ -13,3 +14,6 @@ permissions:
|
||||
rosefirefighter.admin:
|
||||
description: Manage Firefighter sites and emergencies.
|
||||
default: op
|
||||
rosefirefighter.emergency.build:
|
||||
description: Temporary runtime permission granted to on-duty Firefighters at an active emergency site.
|
||||
default: false
|
||||
|
||||
Reference in New Issue
Block a user