Scale round difficulty over time and add claimable powerups
Build / build (push) Successful in 1m17s
Build / build (push) Successful in 1m17s
Reshuffles the floor so the target color's share shrinks each round (TargetDensityCalculator) instead of always averaging ~1/paletteSize, making later rounds progressively harder to read. Adds Super Speed and Air Blast powerups that spawn on the floor each round, claimable by walking over or left-clicking their marker, expiring after 2-3 unclaimed rounds. Usage is tracked in player stats. Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
8049aa04af
commit
4c4f5a1215
@@ -0,0 +1,141 @@
|
||||
package us.tss3.blockparty.powerup;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
import us.tss3.blockparty.config.ConfigManager;
|
||||
import us.tss3.blockparty.floor.FloorManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
||||
/** Owns the powerups currently sitting on a single arena's floor: spawning new ones each round,
|
||||
* ageing out unclaimed ones, and rendering/clearing their markers in the world. A powerup's
|
||||
* marker is a non-solid block floating directly above its floor cell (the player's foot space
|
||||
* when standing there) rather than replacing the floor block itself, so it never removes a
|
||||
* player's footing and never has to be reconciled with the floor's own color each round.
|
||||
* Claiming (i.e. what happens when a player actually gets one) is handled by the caller (see
|
||||
* Arena) since it needs to apply effects and record stats. */
|
||||
public class PowerupManager {
|
||||
|
||||
private final ConfigManager configManager;
|
||||
private final FloorManager floorManager;
|
||||
/** marker position key ("x,y,z", one block above the floor cell) -> the powerup there */
|
||||
private final Map<String, ActivePowerup> active = new LinkedHashMap<>();
|
||||
|
||||
public PowerupManager(ConfigManager configManager, FloorManager floorManager) {
|
||||
this.configManager = configManager;
|
||||
this.floorManager = floorManager;
|
||||
}
|
||||
|
||||
public boolean hasActiveAt(String markerKey) {
|
||||
return active.containsKey(markerKey);
|
||||
}
|
||||
|
||||
/** Removes and returns the powerup at a marker position (e.g. a player walked over or
|
||||
* clicked it), or null if nothing is there. Does not touch the world block - the caller is
|
||||
* expected to clear it via {@link #clearMarkerBlock}. */
|
||||
public ActivePowerup claim(String markerKey) {
|
||||
return active.remove(markerKey);
|
||||
}
|
||||
|
||||
/** Ages out any powerup that's sat unclaimed too long, clearing its marker. */
|
||||
public void expireOld(World world, int currentRound) {
|
||||
List<String> expired = new ArrayList<>();
|
||||
for (Map.Entry<String, ActivePowerup> entry : active.entrySet()) {
|
||||
if (entry.getValue().isExpired(currentRound)) {
|
||||
expired.add(entry.getKey());
|
||||
}
|
||||
}
|
||||
for (String key : expired) {
|
||||
active.remove(key);
|
||||
clearMarkerBlock(world, key);
|
||||
}
|
||||
}
|
||||
|
||||
/** 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) {
|
||||
if (!configManager.isPowerupsEnabled() || world == null) {
|
||||
return;
|
||||
}
|
||||
if (active.size() >= configManager.getPowerupMaxActive()) {
|
||||
return;
|
||||
}
|
||||
if (random.nextDouble() >= configManager.getPowerupSpawnChance()) {
|
||||
return;
|
||||
}
|
||||
List<String> floorKeys = floorManager.layoutKeys();
|
||||
if (floorKeys.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
String floorKey = floorKeys.get(random.nextInt(floorKeys.size()));
|
||||
String markerKey = aboveKey(floorKey);
|
||||
if (markerKey == null || active.containsKey(markerKey)) {
|
||||
return;
|
||||
}
|
||||
PowerupType[] types = PowerupType.values();
|
||||
PowerupType type = types[random.nextInt(types.length)];
|
||||
int min = Math.min(configManager.getPowerupMinLifetimeRounds(), configManager.getPowerupMaxLifetimeRounds());
|
||||
int max = Math.max(configManager.getPowerupMinLifetimeRounds(), configManager.getPowerupMaxLifetimeRounds());
|
||||
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));
|
||||
}
|
||||
|
||||
/** (Re-)places every currently active powerup's marker block in the world. Only strictly
|
||||
* needed for freshly-spawned ones (the floor restore each round never touches the marker's
|
||||
* above-floor position), but cheap and safe to call for all of them defensively. */
|
||||
public void applyMarkers(World world) {
|
||||
for (ActivePowerup powerup : active.values()) {
|
||||
placeMarkerBlock(world, powerup.getPositionKey(), markerMaterial(powerup.getType()));
|
||||
}
|
||||
}
|
||||
|
||||
/** Clears every active powerup's marker - used when a match ends/resets so no stray blocks
|
||||
* are left floating over the arena. */
|
||||
public void clearAll(World world) {
|
||||
for (String key : new ArrayList<>(active.keySet())) {
|
||||
clearMarkerBlock(world, key);
|
||||
}
|
||||
active.clear();
|
||||
}
|
||||
|
||||
public Material markerMaterial(PowerupType type) {
|
||||
return configManager.getPowerupMarkerMaterial(type == PowerupType.SUPER_SPEED ? "super-speed" : "air-blast");
|
||||
}
|
||||
|
||||
private void placeMarkerBlock(World world, String key, Material material) {
|
||||
Block block = blockAt(world, key);
|
||||
if (block != null && block.getType() != material) {
|
||||
block.setType(material, false);
|
||||
}
|
||||
}
|
||||
|
||||
public void clearMarkerBlock(World world, String key) {
|
||||
Block block = blockAt(world, key);
|
||||
if (block != null) {
|
||||
block.setType(Material.AIR, false);
|
||||
}
|
||||
}
|
||||
|
||||
private Block blockAt(World world, String key) {
|
||||
if (world == null || key == null) {
|
||||
return null;
|
||||
}
|
||||
String[] parts = key.split(",");
|
||||
return world.getBlockAt(Integer.parseInt(parts[0]), Integer.parseInt(parts[1]), Integer.parseInt(parts[2]));
|
||||
}
|
||||
|
||||
private String aboveKey(String floorKey) {
|
||||
String[] parts = floorKey.split(",");
|
||||
if (parts.length != 3) {
|
||||
return null;
|
||||
}
|
||||
int y = Integer.parseInt(parts[1]);
|
||||
return parts[0] + "," + (y + 1) + "," + parts[2];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user