Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
61c09a5b4d | ||
|
|
039cfab92d |
@@ -26,6 +26,7 @@ import us.tss3.blockparty.logic.TargetColorSelector;
|
||||
import us.tss3.blockparty.logic.TargetDensityCalculator;
|
||||
import us.tss3.blockparty.model.PlayerState;
|
||||
import us.tss3.blockparty.powerup.ActivePowerup;
|
||||
import us.tss3.blockparty.powerup.PowerupItems;
|
||||
import us.tss3.blockparty.powerup.PowerupManager;
|
||||
import us.tss3.blockparty.powerup.PowerupType;
|
||||
import us.tss3.blockparty.session.PlayerSession;
|
||||
@@ -186,6 +187,7 @@ public class Arena {
|
||||
player.setHealth(20.0);
|
||||
player.setFoodLevel(20);
|
||||
player.setFireTicks(0);
|
||||
player.setCollidable(!plugin.getConfigManager().isPlayerCollisionDisabled());
|
||||
Location target = config.getSpectator() != null ? config.getSpectator() : config.getLobby();
|
||||
if (target != null) {
|
||||
player.teleport(target);
|
||||
@@ -201,6 +203,7 @@ public class Arena {
|
||||
player.setHealth(20.0);
|
||||
player.setFoodLevel(20);
|
||||
player.setFireTicks(0);
|
||||
player.setCollidable(!plugin.getConfigManager().isPlayerCollisionDisabled());
|
||||
if (config.getLobby() != null) {
|
||||
player.teleport(config.getLobby());
|
||||
}
|
||||
@@ -219,6 +222,7 @@ public class Arena {
|
||||
}
|
||||
if (player != null) {
|
||||
plugin.getScoreboardManager().detach(player);
|
||||
player.setCollidable(true);
|
||||
}
|
||||
checkCountdownCancel();
|
||||
checkForWinner();
|
||||
@@ -344,8 +348,11 @@ public class Arena {
|
||||
World world = config.getWorld();
|
||||
if (world != null) {
|
||||
powerupManager.expireOld(world, round + 1);
|
||||
powerupManager.maybeSpawn(world, random, round + 1);
|
||||
PowerupType spawned = powerupManager.maybeSpawn(world, random, round + 1);
|
||||
powerupManager.applyMarkers(world);
|
||||
if (spawned != null) {
|
||||
announcePowerupSpawn(spawned);
|
||||
}
|
||||
}
|
||||
onComplete.run();
|
||||
});
|
||||
@@ -503,8 +510,10 @@ public class Arena {
|
||||
return;
|
||||
}
|
||||
powerupManager.clearMarkerBlock(config.getWorld(), key);
|
||||
applyPowerupEffect(player, powerup.getType());
|
||||
plugin.getStatsManager().recordPowerupUse(player.getUniqueId());
|
||||
ItemStack item = PowerupItems.create(plugin, powerup.getType(), powerupManager.itemMaterial(powerup.getType()));
|
||||
for (ItemStack leftover : player.getInventory().addItem(item).values()) {
|
||||
player.getWorld().dropItemNaturally(player.getLocation(), leftover);
|
||||
}
|
||||
Map<String, String> ph = Map.of("type", powerup.getType().displayName(), "player", player.getName());
|
||||
player.sendMessage(plugin.getMessages().get("powerup.claimed-self", ph));
|
||||
for (UUID uuid : players) {
|
||||
@@ -518,6 +527,29 @@ public class Arena {
|
||||
}
|
||||
}
|
||||
|
||||
/** Activates a powerup item's effect on right-click use (see PowerupListener) - the deferred
|
||||
* half of claim-then-use. Returns false (and consumes nothing) if the player isn't currently
|
||||
* an active participant, so a stale item from a previous match can't be used out of context. */
|
||||
public boolean usePowerupItem(Player player, PowerupType type) {
|
||||
if (!players.contains(player.getUniqueId()) || getPhase() != ArenaPhase.RUNNING) {
|
||||
return false;
|
||||
}
|
||||
applyPowerupEffect(player, type);
|
||||
plugin.getStatsManager().recordPowerupUse(player.getUniqueId());
|
||||
Map<String, String> ph = Map.of("type", type.displayName(), "player", player.getName());
|
||||
player.sendMessage(plugin.getMessages().get("powerup.used-self", ph));
|
||||
for (UUID uuid : players) {
|
||||
if (uuid.equals(player.getUniqueId())) {
|
||||
continue;
|
||||
}
|
||||
Player other = plugin.getServer().getPlayer(uuid);
|
||||
if (other != null) {
|
||||
other.sendMessage(plugin.getMessages().get("powerup.used-other", ph));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void applyPowerupEffect(Player player, PowerupType type) {
|
||||
switch (type) {
|
||||
case SUPER_SPEED -> {
|
||||
@@ -556,6 +588,19 @@ public class Arena {
|
||||
}
|
||||
}
|
||||
|
||||
/** Announces a freshly-spawned powerup to everyone currently in the arena, so players know
|
||||
* to go look for it instead of relying on spotting the marker block by chance. */
|
||||
private void announcePowerupSpawn(PowerupType type) {
|
||||
Map<String, String> ph = Map.of("type", type.displayName());
|
||||
for (UUID uuid : allParticipants()) {
|
||||
Player p = plugin.getServer().getPlayer(uuid);
|
||||
if (p != null) {
|
||||
p.sendMessage(plugin.getMessages().get("powerup.spawned", ph));
|
||||
}
|
||||
}
|
||||
playToParticipants("powerup-spawn");
|
||||
}
|
||||
|
||||
/** Plays a configured sound directly to every current player/spectator (each at their own
|
||||
* location), instead of once at a single world coordinate — Bukkit's location-based
|
||||
* playSound falls off with distance, so anyone far from that one spot would hear nothing. */
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
package us.tss3.blockparty.config;
|
||||
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/** Wraps global config.yml access: UI toggles, sounds, rewards, default floor palette. */
|
||||
public class ConfigManager {
|
||||
@@ -15,10 +20,27 @@ public class ConfigManager {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
/** Loads config.yml, then merges in any keys the bundled jar defines that the on-disk file
|
||||
* is missing (e.g. a config.yml saved before a feature like powerups was added) and persists
|
||||
* the merge to disk. Bukkit's saveDefaultConfig() only writes the file when it doesn't exist
|
||||
* yet, so without this, upgrading the plugin on top of an existing config.yml would silently
|
||||
* leave newer sections absent - every getter still falls back to its own hardcoded default,
|
||||
* but the feature looks "on" from the docs while actually running on stale settings. */
|
||||
public void load() {
|
||||
plugin.saveDefaultConfig();
|
||||
plugin.reloadConfig();
|
||||
this.config = plugin.getConfig();
|
||||
try (InputStream defaultStream = plugin.getResource("config.yml")) {
|
||||
if (defaultStream != null) {
|
||||
YamlConfiguration defaults = YamlConfiguration.loadConfiguration(
|
||||
new InputStreamReader(defaultStream, StandardCharsets.UTF_8));
|
||||
config.setDefaults(defaults);
|
||||
config.options().copyDefaults(true);
|
||||
plugin.saveConfig();
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
plugin.getLogger().log(Level.WARNING, "Failed to merge default config.yml keys", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public FileConfiguration raw() {
|
||||
@@ -113,6 +135,12 @@ public class ConfigManager {
|
||||
return config.getDouble("floor.target-fraction-reduction-per-round", 0.04);
|
||||
}
|
||||
|
||||
/** If true (default), participants and spectators can't push each other around while in an
|
||||
* arena - handy since BlockParty rounds tend to bunch everyone onto a shrinking floor. */
|
||||
public boolean isPlayerCollisionDisabled() {
|
||||
return config.getBoolean("gameplay.disable-player-collision", true);
|
||||
}
|
||||
|
||||
public boolean isPowerupsEnabled() {
|
||||
return config.getBoolean("powerups.enabled", true);
|
||||
}
|
||||
@@ -143,6 +171,17 @@ public class ConfigManager {
|
||||
return material != null ? material : org.bukkit.Material.TORCH;
|
||||
}
|
||||
|
||||
/** The item a claimed powerup shows up as in the player's inventory. Deliberately a separate
|
||||
* setting from the floor marker block: block items (TORCH, END_ROD, etc.) only fire a
|
||||
* right-click interact event when the player is aiming at a block within reach, so using one
|
||||
* as the held item made the powerup unusable unless the player happened to be looking at the
|
||||
* floor. Non-block items fire right-click in any direction. */
|
||||
public org.bukkit.Material getPowerupItemMaterial(String typeKey, org.bukkit.Material fallback) {
|
||||
String name = config.getString("powerups.types." + typeKey + ".item-material", fallback.name());
|
||||
org.bukkit.Material material = org.bukkit.Material.matchMaterial(name);
|
||||
return material != null ? material : fallback;
|
||||
}
|
||||
|
||||
public int getSuperSpeedDurationSeconds() {
|
||||
return config.getInt("powerups.types.super-speed.duration-seconds", 8);
|
||||
}
|
||||
|
||||
@@ -6,12 +6,16 @@ import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.Action;
|
||||
import org.bukkit.event.player.PlayerInteractEvent;
|
||||
import org.bukkit.inventory.EquipmentSlot;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import us.tss3.blockparty.BlockPartyPlugin;
|
||||
import us.tss3.blockparty.arena.Arena;
|
||||
import us.tss3.blockparty.powerup.PowerupItems;
|
||||
import us.tss3.blockparty.powerup.PowerupType;
|
||||
import us.tss3.blockparty.session.PlayerSession;
|
||||
|
||||
/** Lets participants claim a powerup marker by left-clicking it, in addition to just walking
|
||||
* over it (see Arena's fall-watcher loop). */
|
||||
/** Lets participants claim a powerup marker off the floor by left-clicking it (in addition to
|
||||
* just walking over it - see Arena's fall-watcher loop), and use a claimed powerup item by
|
||||
* right-clicking it later, whenever they choose. */
|
||||
public class PowerupListener implements Listener {
|
||||
|
||||
private final BlockPartyPlugin plugin;
|
||||
@@ -33,4 +37,31 @@ public class PowerupListener implements Listener {
|
||||
plugin.getArenaManager().get(session.getArenaName()).ifPresent(arena ->
|
||||
arena.claimPowerupAt(player, event.getClickedBlock().getLocation()));
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
|
||||
public void onRightClickUseItem(PlayerInteractEvent event) {
|
||||
if (event.getHand() != EquipmentSlot.HAND) {
|
||||
return;
|
||||
}
|
||||
if (event.getAction() != Action.RIGHT_CLICK_AIR && event.getAction() != Action.RIGHT_CLICK_BLOCK) {
|
||||
return;
|
||||
}
|
||||
ItemStack item = event.getItem();
|
||||
PowerupType type = PowerupItems.readType(plugin, item);
|
||||
if (type == null) {
|
||||
return;
|
||||
}
|
||||
Player player = event.getPlayer();
|
||||
PlayerSession session = plugin.getSessionManager().get(player.getUniqueId());
|
||||
if (session == null || session.getRole() != PlayerSession.Role.PLAYER) {
|
||||
return;
|
||||
}
|
||||
boolean used = plugin.getArenaManager().get(session.getArenaName())
|
||||
.map(arena -> arena.usePowerupItem(player, type))
|
||||
.orElse(false);
|
||||
if (used) {
|
||||
event.setCancelled(true);
|
||||
item.setAmount(item.getAmount() - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package us.tss3.blockparty.powerup;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/** Builds/reads the inventory item a player receives when they claim a powerup marker off the
|
||||
* floor. Claiming only hands over this item - the effect itself is applied later, when the
|
||||
* player right-clicks it (see PowerupListener), rather than instantly on pickup. */
|
||||
public final class PowerupItems {
|
||||
|
||||
private static final String KEY_NAME = "powerup-type";
|
||||
|
||||
private PowerupItems() {
|
||||
}
|
||||
|
||||
public static ItemStack create(Plugin plugin, PowerupType type, Material icon) {
|
||||
ItemStack stack = new ItemStack(icon);
|
||||
ItemMeta meta = stack.getItemMeta();
|
||||
if (meta != null) {
|
||||
meta.setDisplayName("§b" + type.displayName() + " Powerup");
|
||||
meta.setLore(List.of("§7Right-click to use"));
|
||||
meta.getPersistentDataContainer().set(key(plugin), PersistentDataType.STRING, type.name());
|
||||
stack.setItemMeta(meta);
|
||||
}
|
||||
return stack;
|
||||
}
|
||||
|
||||
/** The powerup type this item represents, or null if it isn't one of ours (e.g. a plain
|
||||
* torch the player picked up elsewhere). */
|
||||
public static PowerupType readType(Plugin plugin, ItemStack stack) {
|
||||
if (stack == null || stack.getType().isAir()) {
|
||||
return null;
|
||||
}
|
||||
ItemMeta meta = stack.getItemMeta();
|
||||
if (meta == null) {
|
||||
return null;
|
||||
}
|
||||
String raw = meta.getPersistentDataContainer().get(key(plugin), PersistentDataType.STRING);
|
||||
if (raw == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return PowerupType.valueOf(raw);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static NamespacedKey key(Plugin plugin) {
|
||||
return new NamespacedKey(plugin, KEY_NAME);
|
||||
}
|
||||
}
|
||||
@@ -57,25 +57,28 @@ public class PowerupManager {
|
||||
}
|
||||
|
||||
/** Rolls whether a new powerup should appear this round and, if so, places one above a
|
||||
* random free floor cell. No-op while powerups are disabled or the floor has no free cells. */
|
||||
public void maybeSpawn(World world, Random random, int currentRound) {
|
||||
* random free floor cell. No-op while powerups are disabled or the floor has no free cells.
|
||||
*
|
||||
* @return the type that spawned, or null if nothing spawned this call (so the caller can
|
||||
* decide whether to announce it - see Arena). */
|
||||
public PowerupType maybeSpawn(World world, Random random, int currentRound) {
|
||||
if (!configManager.isPowerupsEnabled() || world == null) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
if (active.size() >= configManager.getPowerupMaxActive()) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
if (random.nextDouble() >= configManager.getPowerupSpawnChance()) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
List<String> floorKeys = floorManager.layoutKeys();
|
||||
if (floorKeys.isEmpty()) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
String floorKey = floorKeys.get(random.nextInt(floorKeys.size()));
|
||||
String markerKey = aboveKey(floorKey);
|
||||
if (markerKey == null || active.containsKey(markerKey)) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
PowerupType[] types = PowerupType.values();
|
||||
PowerupType type = types[random.nextInt(types.length)];
|
||||
@@ -84,6 +87,7 @@ public class PowerupManager {
|
||||
int lifetime = min + (max > min ? random.nextInt(max - min + 1) : 0);
|
||||
active.put(markerKey, new ActivePowerup(type, markerKey, currentRound, lifetime));
|
||||
placeMarkerBlock(world, markerKey, markerMaterial(type));
|
||||
return type;
|
||||
}
|
||||
|
||||
/** (Re-)places every currently active powerup's marker block in the world. Only strictly
|
||||
@@ -108,6 +112,15 @@ public class PowerupManager {
|
||||
return configManager.getPowerupMarkerMaterial(type == PowerupType.SUPER_SPEED ? "super-speed" : "air-blast");
|
||||
}
|
||||
|
||||
/** The material a claimed powerup uses as its held/inventory item - see
|
||||
* {@link us.tss3.blockparty.config.ConfigManager#getPowerupItemMaterial} for why this is
|
||||
* intentionally not the same as the floor marker material. */
|
||||
public Material itemMaterial(PowerupType type) {
|
||||
return type == PowerupType.SUPER_SPEED
|
||||
? configManager.getPowerupItemMaterial("super-speed", Material.SUGAR)
|
||||
: configManager.getPowerupItemMaterial("air-blast", Material.FEATHER);
|
||||
}
|
||||
|
||||
private void placeMarkerBlock(World world, String key, Material material) {
|
||||
Block block = blockAt(world, key);
|
||||
if (block != null && block.getType() != material) {
|
||||
|
||||
@@ -20,6 +20,7 @@ sounds:
|
||||
eliminate: ENTITY_VILLAGER_NO
|
||||
round-complete: BLOCK_NOTE_BLOCK_BELL
|
||||
victory: UI_TOAST_CHALLENGE_COMPLETE
|
||||
powerup-spawn: ENTITY_PLAYER_LEVELUP
|
||||
|
||||
music:
|
||||
# Optional background music for the duration of a match, played on the client's MUSIC
|
||||
@@ -63,6 +64,11 @@ performance:
|
||||
# blocks processed per server tick when generating/removing/restoring the floor
|
||||
floor-blocks-per-tick: 200
|
||||
|
||||
gameplay:
|
||||
# If true (default), players/spectators can't push each other around while in an arena -
|
||||
# rounds tend to bunch everyone onto a shrinking floor, and collision makes that chaotic.
|
||||
disable-player-collision: true
|
||||
|
||||
floor:
|
||||
# If true (default), the floor's material-to-position layout is randomly reshuffled
|
||||
# (same set of positions and same count of each material, just rearranged) at the start
|
||||
@@ -96,12 +102,18 @@ powerups:
|
||||
super-speed:
|
||||
# Block used to mark a Super Speed powerup's spot on the floor.
|
||||
marker-block: TORCH
|
||||
# Item the player holds after claiming it, used later via right-click. Must NOT be a
|
||||
# placeable block - block items only fire a right-click event when aimed at a nearby
|
||||
# block, so a claimed powerup would be unusable except while looking at the floor.
|
||||
item-material: SUGAR
|
||||
duration-seconds: 8
|
||||
# Potion amplifier: 0 = Speed I, 1 = Speed II, etc.
|
||||
amplifier: 1
|
||||
air-blast:
|
||||
# Block used to mark an Air Blast powerup's spot on the floor.
|
||||
marker-block: END_ROD
|
||||
# See super-speed.item-material above - must not be a placeable block.
|
||||
item-material: FEATHER
|
||||
# Radius, in blocks, that nearby players get pushed within when this is claimed.
|
||||
radius: 4.0
|
||||
strength: 1.4
|
||||
|
||||
@@ -55,8 +55,10 @@ game:
|
||||
|
||||
powerup:
|
||||
spawned: "<aqua>A %type% powerup appeared on the floor!</aqua>"
|
||||
claimed-self: "<green>You claimed <bold>%type%</bold>!</green>"
|
||||
claimed-other: "<yellow>%player% claimed a %type% powerup!</yellow>"
|
||||
claimed-self: "<green>You picked up a <bold>%type%</bold> powerup! Right-click it to use it.</green>"
|
||||
claimed-other: "<yellow>%player% picked up a %type% powerup!</yellow>"
|
||||
used-self: "<green>You used <bold>%type%</bold>!</green>"
|
||||
used-other: "<yellow>%player% used a %type% powerup!</yellow>"
|
||||
|
||||
scoreboard:
|
||||
title: "<gold><bold>BlockParty</bold></gold>"
|
||||
|
||||
Reference in New Issue
Block a user