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_HORIZONTAL_DISTANCE;
|
||||||
public static double MAX_VERTICAL_UP;
|
public static double MAX_VERTICAL_UP;
|
||||||
public static double MAX_VERTICAL_DOWN;
|
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
|
// General
|
||||||
public static double BORDER_SIZE;
|
public static double BORDER_SIZE;
|
||||||
|
|
@ -154,9 +151,6 @@ public class Option {
|
||||||
MAX_HORIZONTAL_DISTANCE = OptionGeneration.MAX_HORIZONTAL_DISTANCE;
|
MAX_HORIZONTAL_DISTANCE = OptionGeneration.MAX_HORIZONTAL_DISTANCE;
|
||||||
MAX_VERTICAL_UP = OptionGeneration.MAX_VERTICAL_UP;
|
MAX_VERTICAL_UP = OptionGeneration.MAX_VERTICAL_UP;
|
||||||
MAX_VERTICAL_DOWN = OptionGeneration.MAX_VERTICAL_DOWN;
|
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
|
// General
|
||||||
BORDER_SIZE = OptionGeneral.BORDER_SIZE;
|
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.LoParkour;
|
||||||
import dev.loki.loparkour.config.core.Config;
|
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 {
|
public class OptionGeneration {
|
||||||
|
|
||||||
|
|
@ -62,14 +59,8 @@ public class OptionGeneration {
|
||||||
public static double MAX_VERTICAL_UP;
|
public static double MAX_VERTICAL_UP;
|
||||||
public static double MAX_VERTICAL_DOWN;
|
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() {
|
public static void init() {
|
||||||
initJumpValidation();
|
initJumpValidation();
|
||||||
initJumpTypes();
|
|
||||||
initMemory();
|
initMemory();
|
||||||
initGhostMode();
|
initGhostMode();
|
||||||
initGenerationProbabilities();
|
initGenerationProbabilities();
|
||||||
|
|
@ -88,23 +79,6 @@ public class OptionGeneration {
|
||||||
? Config.CONFIG.getDouble("jump-validation.max-vertical-down") : DEFAULT_MAX_VERTICAL_DOWN;
|
? 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() {
|
private static void initMemory() {
|
||||||
BLOCK_CLEANUP_DISTANCE = Config.CONFIG.isPath("memory.block-cleanup-distance")
|
BLOCK_CLEANUP_DISTANCE = Config.CONFIG.isPath("memory.block-cleanup-distance")
|
||||||
? Config.CONFIG.getInt("memory.block-cleanup-distance") : DEFAULT_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.SPECIAL, (double) specialChance);
|
||||||
generator.state.defaultChances.put(ParkourGenerator.BlockGenerationType.SCHEMATIC, (double) schematicChance);
|
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);
|
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;
|
package dev.loki.loparkour.generator.jump.calculation;
|
||||||
|
|
||||||
|
import dev.loki.loparkour.config.options.Option;
|
||||||
import dev.loki.loparkour.util.misc.MaterialUtil;
|
import dev.loki.loparkour.util.misc.MaterialUtil;
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
import org.bukkit.util.Vector;
|
import org.bukkit.util.Vector;
|
||||||
|
|
@ -22,7 +23,12 @@ public class JumpValidator {
|
||||||
private final double maxVerticalDown;
|
private final double maxVerticalDown;
|
||||||
|
|
||||||
public JumpValidator() {
|
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) {
|
public JumpValidator(double maxDistance, double maxHorizontal, double maxVerticalUp, double maxVerticalDown) {
|
||||||
|
|
|
||||||
|
|
@ -93,12 +93,17 @@ public class LifecycleTickManager {
|
||||||
|
|
||||||
private void maintainBlockLead() {
|
private void maintainBlockLead() {
|
||||||
final int currentBlocks = generator.state.generatedBlocks;
|
final int currentBlocks = generator.state.generatedBlocks;
|
||||||
final int targetBlocks = generator.state.score + GENERATION_LEAD;
|
final int targetBlocks = generator.state.score + generationLead();
|
||||||
|
|
||||||
if (currentBlocks < targetBlocks) {
|
if (currentBlocks < targetBlocks) {
|
||||||
final int needed = targetBlocks - currentBlocks;
|
final int needed = targetBlocks - currentBlocks;
|
||||||
// Cap generation per tick to prevent runaway generation in multiplayer
|
// Cap generation per tick to prevent runaway generation in multiplayer
|
||||||
generator.generate(Math.min(needed, MAX_GENERATE_PER_TICK));
|
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 org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles cleanup operations for parkour generator.
|
* 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) {
|
public void removeSchematicBlocks() {
|
||||||
if (startIndex < 0 || startIndex >= generator.state.history.size()) {
|
final List<Block> schematicBlocks = generator.state.schematicBlocks;
|
||||||
|
if (schematicBlocks == null || schematicBlocks.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final int endIndex = Math.min(startIndex + Option.BLOCK_CLEANUP_DISTANCE, generator.state.history.size());
|
final Set<Block> remove = new HashSet<>();
|
||||||
|
for (int i = 0; i < schematicBlocks.size() - 1; i++) {
|
||||||
for (int i = startIndex; i < endIndex; i++) {
|
final Block block = schematicBlocks.get(i);
|
||||||
final Block block = generator.state.history.get(i);
|
if (block != null) {
|
||||||
if (block != null && block.getType() != Material.AIR) {
|
if (block.getType() != Material.AIR) {
|
||||||
block.setType(Material.AIR);
|
block.setType(Material.AIR);
|
||||||
|
}
|
||||||
|
remove.add(block);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove from history (synchronized to prevent concurrent index shift)
|
generator.state.history.removeIf(remove::contains);
|
||||||
synchronized (generator.state.history) {
|
generator.state.schematicBlocks = null;
|
||||||
generator.state.history.subList(startIndex, endIndex).clear();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -112,7 +112,7 @@ public class PlayerInteractionHandler {
|
||||||
private void handleBlockInteraction(@NotNull ParkourPlayer player, @NotNull Block block) {
|
private void handleBlockInteraction(@NotNull ParkourPlayer player, @NotNull Block block) {
|
||||||
// Check if it's a schematic end block
|
// Check if it's a schematic end block
|
||||||
if (isSchematicEndBlock(block)) {
|
if (isSchematicEndBlock(block)) {
|
||||||
handleSchematicEndBlock(block);
|
handleSchematicEndBlock();
|
||||||
} else if (isOnSchematicBlock(block)) {
|
} else if (isOnSchematicBlock(block)) {
|
||||||
// Schematic block that is NOT the last — skip scoring
|
// Schematic block that is NOT the last — skip scoring
|
||||||
return;
|
return;
|
||||||
|
|
@ -131,13 +131,9 @@ public class PlayerInteractionHandler {
|
||||||
&& historyContains(generator.state.schematicBlocks, block);
|
&& historyContains(generator.state.schematicBlocks, block);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleSchematicEndBlock(@NotNull Block block) {
|
private void handleSchematicEndBlock() {
|
||||||
// Remove schematic blocks and continue with normal generation
|
cleanup.removeSchematicBlocks();
|
||||||
if (generator.state.schematicBlocks != null) {
|
|
||||||
cleanup.removeTrailBlocks(0); // Remove schematic trail
|
|
||||||
generator.state.schematicBlocks = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Continue normal generation
|
// Continue normal generation
|
||||||
generator.generate(GENERATION_LEAD);
|
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;
|
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.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.leaderboard.core.Leaderboard;
|
||||||
import dev.loki.loparkour.menu.core.Menus;
|
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.ParkourPlayer;
|
||||||
import dev.loki.loparkour.player.core.ParkourUser;
|
import dev.loki.loparkour.player.core.ParkourUser;
|
||||||
import dev.loki.loparkour.session.core.Session;
|
import dev.loki.loparkour.session.core.Session;
|
||||||
import org.bukkit.Sound;
|
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
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.
|
* Co-op Mode — multiple players share one session and one score.
|
||||||
*
|
*
|
||||||
* <p>Implements {@link MultiMode} so it is excluded from the single-player
|
* <p>Implements {@link MultiMode} so it is excluded from the single-player
|
||||||
* mode selection menu in {@link dev.loki.loparkour.menu.play.SingleMenu}.
|
* mode selection menu in {@link dev.loki.loparkour.menu.play.SingleMenu}.
|
||||||
* Players join via /parkour coop or through a lobby invite flow.</p>
|
* 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 {
|
public class CoopMode implements MultiMode {
|
||||||
|
|
||||||
|
|
@ -50,10 +29,6 @@ public class CoopMode implements MultiMode {
|
||||||
return "coop";
|
return "coop";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns null so this mode never appears in any menu item list.
|
|
||||||
* Players join via lobby invite or command only.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public dev.loki.loparkour.util.item.Item getItem(String locale) {
|
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.
|
* Creates a coop session with the caller as host and opens the lobby so
|
||||||
* Opens the lobby menu so they can invite others — does NOT create a session here.
|
* the host can invite others. Later joiners are added via {@link #join}.
|
||||||
* A session is created only once a second player joins via {@link #join}.
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void create(Player player) {
|
public void create(Player player) {
|
||||||
if (!ModeMessages.checkJoiningEnabled(player)) {
|
if (!ModeMessages.checkJoiningEnabled(player)) {
|
||||||
return;
|
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);
|
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
|
@Override
|
||||||
public void join(Player joiningPlayer, Session session) {
|
public void join(Player joiningPlayer, Session session) {
|
||||||
if (!ModeMessages.checkJoiningEnabled(joiningPlayer)) {
|
if (!ModeMessages.checkJoiningEnabled(joiningPlayer)) {
|
||||||
|
|
@ -94,20 +74,17 @@ public class CoopMode implements MultiMode {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register the joining player into the existing session
|
|
||||||
final ParkourPlayer pp = ParkourUser.register(joiningPlayer, session);
|
final ParkourPlayer pp = ParkourUser.register(joiningPlayer, session);
|
||||||
session.addPlayers(pp);
|
session.addPlayers(pp);
|
||||||
pp.updateGeneratorSettings(session.generator);
|
pp.updateGeneratorSettings(session.generator);
|
||||||
|
if (session.generator instanceof CoopGenerator coopGenerator) {
|
||||||
|
coopGenerator.onPlayerJoin(pp);
|
||||||
|
}
|
||||||
|
|
||||||
// Inform everyone
|
|
||||||
session.getPlayers().forEach(p ->
|
session.getPlayers().forEach(p ->
|
||||||
p.sendTranslated("modes.coop.player_joined", joiningPlayer.getName()));
|
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
|
@Override
|
||||||
public void leave(Player player, Session session) {
|
public void leave(Player player, Session session) {
|
||||||
final ParkourPlayer pp = ParkourPlayer.getPlayer(player);
|
final ParkourPlayer pp = ParkourPlayer.getPlayer(player);
|
||||||
|
|
@ -121,79 +98,4 @@ public class CoopMode implements MultiMode {
|
||||||
public int getMaxPlayers() {
|
public int getMaxPlayers() {
|
||||||
return Config.CONFIG.getInt("modes.coop.max-players");
|
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-up: 1.25
|
||||||
max-vertical-down: 3.0
|
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 optimization =-
|
||||||
memory:
|
memory:
|
||||||
block-cleanup-distance: 20
|
block-cleanup-distance: 20
|
||||||
|
|
|
||||||
|
|
@ -34,17 +34,6 @@ generation:
|
||||||
pane: 10
|
pane: 10
|
||||||
fence: 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 =-
|
# -= Jump settings =-
|
||||||
settings:
|
settings:
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue