fix: coop session creation, schematic cleanup, wire dead generator config
- coop: create() builds a real CoopGenerator session (host + lobby), join() tracks contributions (п.23) - schematics: removeSchematicBlocks() airs and prunes schematic blocks instead of removeTrailBlocks(0) (п.34) - config: wire jump-validation into JumpValidator, useSpecialBlocks gates special chances, blockLead drives generation lead; remove dead jump-types keys and EffectManager (п.38)
This commit is contained in:
parent
fea15e61c8
commit
4c255111f0
12 changed files with 151 additions and 301 deletions
|
|
@ -95,9 +95,6 @@ public class Option {
|
|||
public static double MAX_HORIZONTAL_DISTANCE;
|
||||
public static double MAX_VERTICAL_UP;
|
||||
public static double MAX_VERTICAL_DOWN;
|
||||
public static boolean JUMP_TYPES_ENABLED;
|
||||
public static Map<String, Boolean> JUMP_TYPE_ENABLED;
|
||||
public static Map<String, Double> JUMP_TYPE_CHANCE;
|
||||
|
||||
// General
|
||||
public static double BORDER_SIZE;
|
||||
|
|
@ -154,9 +151,6 @@ public class Option {
|
|||
MAX_HORIZONTAL_DISTANCE = OptionGeneration.MAX_HORIZONTAL_DISTANCE;
|
||||
MAX_VERTICAL_UP = OptionGeneration.MAX_VERTICAL_UP;
|
||||
MAX_VERTICAL_DOWN = OptionGeneration.MAX_VERTICAL_DOWN;
|
||||
JUMP_TYPES_ENABLED = OptionGeneration.JUMP_TYPES_ENABLED;
|
||||
JUMP_TYPE_ENABLED = OptionGeneration.JUMP_TYPE_ENABLED;
|
||||
JUMP_TYPE_CHANCE = OptionGeneration.JUMP_TYPE_CHANCE;
|
||||
|
||||
// General
|
||||
BORDER_SIZE = OptionGeneral.BORDER_SIZE;
|
||||
|
|
|
|||
|
|
@ -3,11 +3,8 @@ package dev.loki.loparkour.config.options.section;
|
|||
import dev.loki.loparkour.LoParkour;
|
||||
import dev.loki.loparkour.config.core.Config;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Generation-related configuration options (jump types, validation, memory, ghost mode).
|
||||
* Generation-related configuration options (validation, memory, ghost mode).
|
||||
*/
|
||||
public class OptionGeneration {
|
||||
|
||||
|
|
@ -62,14 +59,8 @@ public class OptionGeneration {
|
|||
public static double MAX_VERTICAL_UP;
|
||||
public static double MAX_VERTICAL_DOWN;
|
||||
|
||||
// ── Jump types ────────────────────────────────────────────────────────────
|
||||
public static boolean JUMP_TYPES_ENABLED;
|
||||
public static Map<String, Boolean> JUMP_TYPE_ENABLED;
|
||||
public static Map<String, Double> JUMP_TYPE_CHANCE;
|
||||
|
||||
public static void init() {
|
||||
initJumpValidation();
|
||||
initJumpTypes();
|
||||
initMemory();
|
||||
initGhostMode();
|
||||
initGenerationProbabilities();
|
||||
|
|
@ -88,23 +79,6 @@ public class OptionGeneration {
|
|||
? Config.CONFIG.getDouble("jump-validation.max-vertical-down") : DEFAULT_MAX_VERTICAL_DOWN;
|
||||
}
|
||||
|
||||
private static void initJumpTypes() {
|
||||
JUMP_TYPES_ENABLED = Config.CONFIG.isPath("jump-types.enabled")
|
||||
&& Config.CONFIG.getBoolean("jump-types.enabled");
|
||||
JUMP_TYPE_ENABLED = new HashMap<>();
|
||||
JUMP_TYPE_CHANCE = new HashMap<>();
|
||||
|
||||
if (Config.CONFIG.isPath("jump-types.types")) {
|
||||
for (final String type : Config.CONFIG.getChildren("jump-types.types")) {
|
||||
final String p = "jump-types.types." + type;
|
||||
JUMP_TYPE_ENABLED.put(type, Config.CONFIG.isPath(p + ".enabled")
|
||||
&& Config.CONFIG.getBoolean(p + ".enabled"));
|
||||
JUMP_TYPE_CHANCE.put(type, Config.CONFIG.isPath(p + ".chance")
|
||||
? Config.CONFIG.getDouble(p + ".chance") : 0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void initMemory() {
|
||||
BLOCK_CLEANUP_DISTANCE = Config.CONFIG.isPath("memory.block-cleanup-distance")
|
||||
? Config.CONFIG.getInt("memory.block-cleanup-distance") : DEFAULT_BLOCK_CLEANUP_DISTANCE;
|
||||
|
|
|
|||
|
|
@ -127,6 +127,12 @@ public class GeneratorProfileManager {
|
|||
generator.state.defaultChances.put(ParkourGenerator.BlockGenerationType.SPECIAL, (double) specialChance);
|
||||
generator.state.defaultChances.put(ParkourGenerator.BlockGenerationType.SCHEMATIC, (double) schematicChance);
|
||||
|
||||
final boolean specialsEnabled = !generator.profile.settings.containsKey("useSpecialBlocks")
|
||||
|| generator.profile.get("useSpecialBlocks").asBoolean();
|
||||
if (!specialsEnabled) {
|
||||
generator.state.defaultChances.put(ParkourGenerator.BlockGenerationType.SPECIAL, 0.0);
|
||||
}
|
||||
|
||||
normalizeMap(generator.state.defaultChances);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,95 +0,0 @@
|
|||
package dev.loki.loparkour.generator.effect;
|
||||
|
||||
import dev.loki.loparkour.config.options.Option;
|
||||
import dev.loki.loparkour.generator.core.coordinator.ParkourGenerator;
|
||||
import dev.loki.loparkour.player.core.ParkourPlayer;
|
||||
import dev.loki.loparkour.util.particle.ParticleUtil;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.block.Block;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Handles particle and sound effects for the parkour generator.
|
||||
* Extracted from ParkourGenerator to keep it focused on generation logic.
|
||||
*/
|
||||
public class EffectManager {
|
||||
|
||||
private static final int DOT_PARTICLE_COUNT = 5;
|
||||
private static final double DOT_PARTICLE_OFFSET = 0.1;
|
||||
private static final double CIRCLE_CENTER_OFFSET = 0.5;
|
||||
private static final int CIRCLE_RADIUS = 8;
|
||||
private static final double BOX_PARTICLE_SPACING = 0.3;
|
||||
|
||||
private final ParkourGenerator generator;
|
||||
|
||||
public EffectManager(ParkourGenerator generator) {
|
||||
this.generator = generator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Spawns particles around the given blocks based on the configured shape.
|
||||
*/
|
||||
public void particles(List<Block> blocks) {
|
||||
if (!generator.profile.get("particles").asBoolean()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final List<Location> locations = blocks.stream().map(Block::getLocation).toList();
|
||||
final Location max = locations.stream().reduce((a, b) -> {
|
||||
final double x = Math.max(a.getX(), b.getX());
|
||||
final double y = Math.max(a.getY(), b.getY());
|
||||
final double z = Math.max(a.getZ(), b.getZ());
|
||||
return new Location(a.getWorld(), x, y, z);
|
||||
}).orElse(null);
|
||||
final Location min = locations.stream().reduce((a, b) -> {
|
||||
final double x = Math.min(a.getX(), b.getX());
|
||||
final double y = Math.min(a.getY(), b.getY());
|
||||
final double z = Math.min(a.getZ(), b.getZ());
|
||||
return new Location(a.getWorld(), x, y, z);
|
||||
}).orElse(null);
|
||||
|
||||
if (max == null || min == null || max.getWorld() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final List<ParkourPlayer> viewers = generator.getPlayers();
|
||||
|
||||
switch (Option.PARTICLE_SHAPE) {
|
||||
case DOT -> {
|
||||
final Location center = min.clone().add(
|
||||
(max.getX() - min.getX()) / 2,
|
||||
(max.getY() - min.getY()) / 2,
|
||||
(max.getZ() - min.getZ()) / 2
|
||||
);
|
||||
viewers.forEach(p -> p.player.spawnParticle(
|
||||
Option.PARTICLE_TYPE, center, DOT_PARTICLE_COUNT, DOT_PARTICLE_OFFSET, DOT_PARTICLE_OFFSET, DOT_PARTICLE_OFFSET, 0,
|
||||
Option.PARTICLE_DATA
|
||||
));
|
||||
}
|
||||
case CIRCLE -> ParticleUtil.circle(
|
||||
min.clone().add(CIRCLE_CENTER_OFFSET, CIRCLE_CENTER_OFFSET, CIRCLE_CENTER_OFFSET), Option.PARTICLE_TYPE, CIRCLE_RADIUS, 1
|
||||
);
|
||||
case BOX -> ParticleUtil.box(
|
||||
org.bukkit.util.BoundingBox.of(min, max), max.getWorld(), Option.PARTICLE_TYPE,
|
||||
viewers.isEmpty() ? null : viewers.get(0).player, BOX_PARTICLE_SPACING
|
||||
);
|
||||
default -> {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Plays the configured sound at the first block's location.
|
||||
*/
|
||||
public void sound(List<Block> blocks) {
|
||||
if (!generator.profile.get("sound").asBoolean() || blocks.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Location loc = blocks.get(0).getLocation();
|
||||
generator.getPlayers().forEach(p ->
|
||||
p.player.playSound(loc, Option.SOUND_TYPE, Option.SOUND_VOLUME, Option.SOUND_PITCH));
|
||||
generator.getSpectators().forEach(s ->
|
||||
s.player.playSound(loc, Option.SOUND_TYPE, Option.SOUND_VOLUME, Option.SOUND_PITCH));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package dev.loki.loparkour.generator.jump.calculation;
|
||||
|
||||
import dev.loki.loparkour.config.options.Option;
|
||||
import dev.loki.loparkour.util.misc.MaterialUtil;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.util.Vector;
|
||||
|
|
@ -22,7 +23,12 @@ public class JumpValidator {
|
|||
private final double maxVerticalDown;
|
||||
|
||||
public JumpValidator() {
|
||||
this(MAX_JUMP_DISTANCE, MAX_HORIZONTAL_DISTANCE, MAX_VERTICAL_UP, MAX_VERTICAL_DOWN);
|
||||
this(
|
||||
Option.JUMP_VALIDATION_ENABLED ? Option.MAX_JUMP_DISTANCE : MAX_JUMP_DISTANCE,
|
||||
Option.JUMP_VALIDATION_ENABLED ? Option.MAX_HORIZONTAL_DISTANCE : MAX_HORIZONTAL_DISTANCE,
|
||||
Option.JUMP_VALIDATION_ENABLED ? Option.MAX_VERTICAL_UP : MAX_VERTICAL_UP,
|
||||
Option.JUMP_VALIDATION_ENABLED ? Option.MAX_VERTICAL_DOWN : MAX_VERTICAL_DOWN
|
||||
);
|
||||
}
|
||||
|
||||
public JumpValidator(double maxDistance, double maxHorizontal, double maxVerticalUp, double maxVerticalDown) {
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ public class LifecycleTickManager {
|
|||
|
||||
private void maintainBlockLead() {
|
||||
final int currentBlocks = generator.state.generatedBlocks;
|
||||
final int targetBlocks = generator.state.score + GENERATION_LEAD;
|
||||
final int targetBlocks = generator.state.score + generationLead();
|
||||
|
||||
if (currentBlocks < targetBlocks) {
|
||||
final int needed = targetBlocks - currentBlocks;
|
||||
|
|
@ -101,4 +101,9 @@ public class LifecycleTickManager {
|
|||
generator.generate(Math.min(needed, MAX_GENERATE_PER_TICK));
|
||||
}
|
||||
}
|
||||
|
||||
private int generationLead() {
|
||||
final int lead = generator.profile.get("blockLead").asInt();
|
||||
return lead > 0 ? lead : GENERATION_LEAD;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,9 @@ import org.bukkit.block.Block;
|
|||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Handles cleanup operations for parkour generator.
|
||||
|
|
@ -91,26 +93,28 @@ public class GeneratorCleanup {
|
|||
}
|
||||
|
||||
/**
|
||||
* Remove trail blocks behind player at specified index.
|
||||
* Remove schematic blocks once the player completes them.
|
||||
* The end block is kept in history so the landing player can score it.
|
||||
*/
|
||||
public void removeTrailBlocks(int startIndex) {
|
||||
if (startIndex < 0 || startIndex >= generator.state.history.size()) {
|
||||
public void removeSchematicBlocks() {
|
||||
final List<Block> schematicBlocks = generator.state.schematicBlocks;
|
||||
if (schematicBlocks == null || schematicBlocks.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int endIndex = Math.min(startIndex + Option.BLOCK_CLEANUP_DISTANCE, generator.state.history.size());
|
||||
|
||||
for (int i = startIndex; i < endIndex; i++) {
|
||||
final Block block = generator.state.history.get(i);
|
||||
if (block != null && block.getType() != Material.AIR) {
|
||||
block.setType(Material.AIR);
|
||||
final Set<Block> remove = new HashSet<>();
|
||||
for (int i = 0; i < schematicBlocks.size() - 1; i++) {
|
||||
final Block block = schematicBlocks.get(i);
|
||||
if (block != null) {
|
||||
if (block.getType() != Material.AIR) {
|
||||
block.setType(Material.AIR);
|
||||
}
|
||||
remove.add(block);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove from history (synchronized to prevent concurrent index shift)
|
||||
synchronized (generator.state.history) {
|
||||
generator.state.history.subList(startIndex, endIndex).clear();
|
||||
}
|
||||
generator.state.history.removeIf(remove::contains);
|
||||
generator.state.schematicBlocks = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ public class PlayerInteractionHandler {
|
|||
private void handleBlockInteraction(@NotNull ParkourPlayer player, @NotNull Block block) {
|
||||
// Check if it's a schematic end block
|
||||
if (isSchematicEndBlock(block)) {
|
||||
handleSchematicEndBlock(block);
|
||||
handleSchematicEndBlock();
|
||||
} else if (isOnSchematicBlock(block)) {
|
||||
// Schematic block that is NOT the last — skip scoring
|
||||
return;
|
||||
|
|
@ -131,12 +131,8 @@ public class PlayerInteractionHandler {
|
|||
&& historyContains(generator.state.schematicBlocks, block);
|
||||
}
|
||||
|
||||
private void handleSchematicEndBlock(@NotNull Block block) {
|
||||
// Remove schematic blocks and continue with normal generation
|
||||
if (generator.state.schematicBlocks != null) {
|
||||
cleanup.removeTrailBlocks(0); // Remove schematic trail
|
||||
generator.state.schematicBlocks = null;
|
||||
}
|
||||
private void handleSchematicEndBlock() {
|
||||
cleanup.removeSchematicBlocks();
|
||||
|
||||
// Continue normal generation
|
||||
generator.generate(GENERATION_LEAD);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,89 @@
|
|||
package dev.loki.loparkour.mode.impl.multi;
|
||||
|
||||
import dev.loki.loparkour.config.locale.Locales;
|
||||
import dev.loki.loparkour.generator.core.coordinator.ParkourGenerator;
|
||||
import dev.loki.loparkour.mode.base.Mode;
|
||||
import dev.loki.loparkour.mode.base.Modes;
|
||||
import dev.loki.loparkour.player.core.ParkourPlayer;
|
||||
import dev.loki.loparkour.session.core.Session;
|
||||
import dev.loki.loparkour.util.text.ColorUtil;
|
||||
import org.bukkit.Sound;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Co-op generator — all players share one score; per-UUID block
|
||||
* contributions are tracked for the fall() stats message.
|
||||
*/
|
||||
class CoopGenerator extends ParkourGenerator {
|
||||
|
||||
private static final int MILESTONE_INTERVAL = 50;
|
||||
private static final int TITLE_FADE_IN = 5;
|
||||
private static final int TITLE_STAY = 40;
|
||||
private static final int TITLE_FADE_OUT = 10;
|
||||
private static final float MILESTONE_VOLUME = 0.6f;
|
||||
private static final float MILESTONE_PITCH = 1.5f;
|
||||
private static final int PERCENT_MULTIPLIER = 100;
|
||||
private final Map<UUID, Integer> contributions = new HashMap<>();
|
||||
|
||||
CoopGenerator(@NotNull Session session) {
|
||||
super(session);
|
||||
session.getPlayers().forEach(pp -> contributions.put(pp.getUUID(), 0));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void score() {
|
||||
super.score();
|
||||
|
||||
for (final ParkourPlayer pp : getPlayers()) {
|
||||
contributions.compute(pp.getUUID(), (uuid, count) -> (count == null ? 0 : count) + 1);
|
||||
}
|
||||
|
||||
if (state.score % MILESTONE_INTERVAL == 0) {
|
||||
getPlayers().forEach(pp -> {
|
||||
pp.player.sendTitle(
|
||||
ColorUtil.color(Locales.getString(pp.locale, "modes.coop.milestone_title").formatted(state.score)),
|
||||
ColorUtil.color(Locales.getString(pp.locale, "modes.coop.milestone_subtitle")),
|
||||
TITLE_FADE_IN, TITLE_STAY, TITLE_FADE_OUT);
|
||||
pp.player.playSound(pp.getLocation(),
|
||||
Sound.ENTITY_PLAYER_LEVELUP, MILESTONE_VOLUME, MILESTONE_PITCH);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a player joining mid-game so their blocks count in fall() stats.
|
||||
*/
|
||||
void onPlayerJoin(ParkourPlayer pp) {
|
||||
contributions.putIfAbsent(pp.getUUID(), 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fall() {
|
||||
getPlayers().forEach(pp -> {
|
||||
final int contrib = contributions.getOrDefault(pp.getUUID(), 0);
|
||||
final int pct = state.score > 0 ? (contrib * PERCENT_MULTIPLIER / state.score) : 0;
|
||||
pp.sendTranslated("modes.coop.stats",
|
||||
Integer.toString(state.score),
|
||||
Integer.toString(contrib),
|
||||
Integer.toString(pct));
|
||||
});
|
||||
|
||||
super.fall();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset(boolean regenerate) {
|
||||
super.reset(regenerate);
|
||||
contributions.clear();
|
||||
getPlayers().forEach(pp -> contributions.put(pp.getUUID(), 0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mode getMode() {
|
||||
return Modes.COOP;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,44 +1,23 @@
|
|||
package dev.loki.loparkour.mode.impl.multi;
|
||||
|
||||
import dev.loki.loparkour.mode.base.MultiMode;
|
||||
import dev.loki.loparkour.mode.base.ModeMessages;
|
||||
import dev.loki.loparkour.mode.base.Mode;
|
||||
import dev.loki.loparkour.mode.base.Modes;
|
||||
|
||||
import dev.loki.loparkour.config.core.Config;
|
||||
import dev.loki.loparkour.config.locale.Locales;
|
||||
import dev.loki.loparkour.generator.core.coordinator.ParkourGenerator;
|
||||
import dev.loki.loparkour.util.text.ColorUtil;
|
||||
import dev.loki.loparkour.leaderboard.core.Leaderboard;
|
||||
import dev.loki.loparkour.menu.core.Menus;
|
||||
import dev.loki.loparkour.mode.base.ModeMessages;
|
||||
import dev.loki.loparkour.mode.base.MultiMode;
|
||||
import dev.loki.loparkour.player.core.ParkourPlayer;
|
||||
import dev.loki.loparkour.player.core.ParkourUser;
|
||||
import dev.loki.loparkour.session.core.Session;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Co-op Mode — multiple players share one session and one score.
|
||||
*
|
||||
* <p>Implements {@link MultiMode} so it is excluded from the single-player
|
||||
* mode selection menu in {@link dev.loki.loparkour.menu.play.SingleMenu}.
|
||||
* Players join via /parkour coop or through a lobby invite flow.</p>
|
||||
*
|
||||
* <p>Problems fixed vs previous version:
|
||||
* <ul>
|
||||
* <li>Did NOT implement MultiMode → showed up in single-player menu</li>
|
||||
* <li>CoopGenerator was a private inner class — inaccessible from join/leave</li>
|
||||
* <li>sharedScore was a separate field that duplicated state.score</li>
|
||||
* <li>fall() called super.fall() THEN tried to clear state that was already reset</li>
|
||||
* <li>playerContributions never cleared between falls correctly</li>
|
||||
* <li>create(Player) silently created a solo session instead of rejecting</li>
|
||||
* </ul>
|
||||
*/
|
||||
public class CoopMode implements MultiMode {
|
||||
|
||||
|
|
@ -50,10 +29,6 @@ public class CoopMode implements MultiMode {
|
|||
return "coop";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns null so this mode never appears in any menu item list.
|
||||
* Players join via lobby invite or command only.
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public dev.loki.loparkour.util.item.Item getItem(String locale) {
|
||||
|
|
@ -67,23 +42,28 @@ public class CoopMode implements MultiMode {
|
|||
}
|
||||
|
||||
/**
|
||||
* Called when a player tries to start coop solo from a command/menu.
|
||||
* Opens the lobby menu so they can invite others — does NOT create a session here.
|
||||
* A session is created only once a second player joins via {@link #join}.
|
||||
* Creates a coop session with the caller as host and opens the lobby so
|
||||
* the host can invite others. Later joiners are added via {@link #join}.
|
||||
*/
|
||||
@Override
|
||||
public void create(Player player) {
|
||||
if (!ModeMessages.checkJoiningEnabled(player)) {
|
||||
return;
|
||||
}
|
||||
// Opening lobby menu lets player invite others — actual session created in join()
|
||||
|
||||
final ParkourPlayer pp = ParkourPlayer.getPlayer(player);
|
||||
if (pp != null && pp.session.generator instanceof CoopGenerator) {
|
||||
return;
|
||||
}
|
||||
|
||||
player.closeInventory();
|
||||
Session.create(CoopGenerator::new,
|
||||
session -> session.getPlayers().size() < getMaxPlayers(),
|
||||
null,
|
||||
player);
|
||||
Menus.LOBBY.open(player);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a player to an existing coop session, or creates a new session if
|
||||
* the target player is not yet in one.
|
||||
*/
|
||||
@Override
|
||||
public void join(Player joiningPlayer, Session session) {
|
||||
if (!ModeMessages.checkJoiningEnabled(joiningPlayer)) {
|
||||
|
|
@ -94,20 +74,17 @@ public class CoopMode implements MultiMode {
|
|||
return;
|
||||
}
|
||||
|
||||
// Register the joining player into the existing session
|
||||
final ParkourPlayer pp = ParkourUser.register(joiningPlayer, session);
|
||||
session.addPlayers(pp);
|
||||
pp.updateGeneratorSettings(session.generator);
|
||||
if (session.generator instanceof CoopGenerator coopGenerator) {
|
||||
coopGenerator.onPlayerJoin(pp);
|
||||
}
|
||||
|
||||
// Inform everyone
|
||||
session.getPlayers().forEach(p ->
|
||||
p.sendTranslated("modes.coop.player_joined", joiningPlayer.getName()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a player from a coop session.
|
||||
* If the last player leaves, the session ends normally.
|
||||
*/
|
||||
@Override
|
||||
public void leave(Player player, Session session) {
|
||||
final ParkourPlayer pp = ParkourPlayer.getPlayer(player);
|
||||
|
|
@ -121,79 +98,4 @@ public class CoopMode implements MultiMode {
|
|||
public int getMaxPlayers() {
|
||||
return Config.CONFIG.getInt("modes.coop.max-players");
|
||||
}
|
||||
|
||||
/** Package-accessible so Session.create() lambda and join() can use it. */
|
||||
static class CoopGenerator extends ParkourGenerator {
|
||||
|
||||
private static final int MILESTONE_INTERVAL = 50;
|
||||
private static final int TITLE_FADE_IN = 5;
|
||||
private static final int TITLE_STAY = 40;
|
||||
private static final int TITLE_FADE_OUT = 10;
|
||||
private static final float MILESTONE_VOLUME = 0.6f;
|
||||
private static final float MILESTONE_PITCH = 1.5f;
|
||||
private static final int PERCENT_MULTIPLIER = 100;
|
||||
/** Individual block contributions per player UUID. */
|
||||
private final Map<UUID, Integer> contributions = new HashMap<>();
|
||||
|
||||
CoopGenerator(@NotNull Session session) {
|
||||
super(session);
|
||||
session.getPlayers().forEach(pp -> contributions.put(pp.getUUID(), 0));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void score() {
|
||||
super.score(); // increments state.score and totalScore
|
||||
|
||||
// Track contributions for all players in session
|
||||
for (final ParkourPlayer pp : getPlayers()) {
|
||||
contributions.compute(pp.getUUID(), (uuid, count) -> (count == null ? 0 : count) + 1);
|
||||
}
|
||||
|
||||
// Milestone every 50 points
|
||||
if (state.score % MILESTONE_INTERVAL == 0) {
|
||||
getPlayers().forEach(pp -> {
|
||||
pp.player.sendTitle(
|
||||
ColorUtil.color(Locales.getString(pp.locale, "modes.coop.milestone_title").formatted(state.score)),
|
||||
ColorUtil.color(Locales.getString(pp.locale, "modes.coop.milestone_subtitle")),
|
||||
TITLE_FADE_IN, TITLE_STAY, TITLE_FADE_OUT);
|
||||
pp.player.playSound(pp.getLocation(),
|
||||
Sound.ENTITY_PLAYER_LEVELUP, MILESTONE_VOLUME, MILESTONE_PITCH);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When a new player joins mid-game, add them to contributions map.
|
||||
*/
|
||||
public void onPlayerJoin(ParkourPlayer pp) {
|
||||
contributions.putIfAbsent(pp.getUUID(), 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fall() {
|
||||
// Show each player their individual contribution before reset
|
||||
getPlayers().forEach(pp -> {
|
||||
final int contrib = contributions.getOrDefault(pp.getUUID(), 0);
|
||||
final int pct = state.score > 0 ? (contrib * PERCENT_MULTIPLIER / state.score) : 0;
|
||||
pp.sendTranslated("modes.coop.stats",
|
||||
Integer.toString(state.score),
|
||||
Integer.toString(contrib),
|
||||
Integer.toString(pct));
|
||||
});
|
||||
|
||||
super.fall(); // handles reset, leaderboard, etc.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset(boolean regenerate) {
|
||||
super.reset(regenerate);
|
||||
contributions.clear();
|
||||
getPlayers().forEach(pp -> contributions.put(pp.getUUID(), 0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mode getMode() {
|
||||
return Modes.COOP;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -150,26 +150,6 @@ jump-validation:
|
|||
max-vertical-up: 1.25
|
||||
max-vertical-down: 3.0
|
||||
|
||||
# -= Jump types options =-
|
||||
jump-types:
|
||||
enabled: false
|
||||
types:
|
||||
neo-jump:
|
||||
enabled: true
|
||||
chance: 0.05
|
||||
head-hitter:
|
||||
enabled: true
|
||||
chance: 0.08
|
||||
fence-jump:
|
||||
enabled: true
|
||||
chance: 0.10
|
||||
trapdoor-jump:
|
||||
enabled: true
|
||||
chance: 0.12
|
||||
ladder-jump:
|
||||
enabled: true
|
||||
chance: 0.07
|
||||
|
||||
# -= Memory optimization =-
|
||||
memory:
|
||||
block-cleanup-distance: 20
|
||||
|
|
|
|||
|
|
@ -34,17 +34,6 @@ generation:
|
|||
pane: 10
|
||||
fence: 10
|
||||
|
||||
# -= Jump type variations =-
|
||||
# Chances for different jump types within normal blocks
|
||||
# These add variety to the parkour generation
|
||||
jump-types:
|
||||
normal: 70
|
||||
neo: 5
|
||||
head-hitter: 5
|
||||
fence: 10
|
||||
trapdoor: 5
|
||||
ladder: 5
|
||||
|
||||
# -= Jump settings =-
|
||||
settings:
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue