it works. commands are broken.

This commit is contained in:
2025-11-29 19:22:04 +03:00
parent c7ca8aad20
commit 5a989ad053
44 changed files with 1890 additions and 1064 deletions

11
.idea/compiler.xml generated
View File

@@ -1,6 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="CompilerConfiguration"> <component name="CompilerConfiguration">
<bytecodeTargetLevel target="21" /> <annotationProcessing>
<profile name="Gradle Imported" enabled="true">
<outputRelativeToContentRoot value="true" />
<processorPath useClasspath="false">
<entry name="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.projectlombok/lombok/1.18.42/8365263844ebb62398e0dc33057ba10ba472d3b8/lombok-1.18.42.jar" />
</processorPath>
<module name="BlockAndSeek.main" />
</profile>
</annotationProcessing>
<bytecodeTargetLevel target="17" />
</component> </component>
</project> </project>

2
.idea/misc.xml generated
View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" /> <component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="21" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" /> <output url="file://$PROJECT_DIR$/out" />
</component> </component>
</project> </project>

1
.idea/modules.xml generated
View File

@@ -4,6 +4,7 @@
<modules> <modules>
<module fileurl="file://$PROJECT_DIR$/.idea/modules/BlockAndSeek.iml" filepath="$PROJECT_DIR$/.idea/modules/BlockAndSeek.iml" /> <module fileurl="file://$PROJECT_DIR$/.idea/modules/BlockAndSeek.iml" filepath="$PROJECT_DIR$/.idea/modules/BlockAndSeek.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/BlockAndSeek.main.iml" filepath="$PROJECT_DIR$/.idea/modules/BlockAndSeek.main.iml" /> <module fileurl="file://$PROJECT_DIR$/.idea/modules/BlockAndSeek.main.iml" filepath="$PROJECT_DIR$/.idea/modules/BlockAndSeek.main.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/BlockAndSeek.test.iml" filepath="$PROJECT_DIR$/.idea/modules/BlockAndSeek.test.iml" />
</modules> </modules>
</component> </component>
</project> </project>

View File

@@ -1,5 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<module version="4"> <module version="4">
<component name="AdditionalModuleElements">
<content url="file://$MODULE_DIR$/../../build/generated/sources/annotationProcessor/java/main">
<sourceFolder url="file://$MODULE_DIR$/../../build/generated/sources/annotationProcessor/java/main" isTestSource="false" generated="true" />
</content>
</component>
<component name="FacetManager"> <component name="FacetManager">
<facet type="minecraft" name="Minecraft"> <facet type="minecraft" name="Minecraft">
<configuration> <configuration>

View File

@@ -1,6 +1,9 @@
import java.nio.ByteBuffer
import java.nio.ByteOrder
plugins { plugins {
id 'java' id 'java'
id("xyz.jpenilla.run-paper") version "2.3.1" id "com.gradleup.shadow" version "9.2.2"
} }
group = 'hdvtdev' group = 'hdvtdev'
@@ -8,6 +11,7 @@ version = '0.0.1-a'
repositories { repositories {
mavenCentral() mavenCentral()
maven { url 'https://storehouse.okaeri.eu/repository/maven-releases/'}
maven { url 'https://repo.md-5.net/content/groups/public/' } maven { url 'https://repo.md-5.net/content/groups/public/' }
maven { url 'https://jitpack.io' } maven { url 'https://jitpack.io' }
maven { maven {
@@ -21,15 +25,24 @@ repositories {
maven { url 'https://libraries.minecraft.net/' } maven { url 'https://libraries.minecraft.net/' }
} }
def okaeriConfigsVersion = '5.0.13'
dependencies { dependencies {
compileOnly("io.papermc.paper:paper-api:1.20.4-R0.1-SNAPSHOT") compileOnly("io.papermc.paper:paper-api:1.20.4-R0.1-SNAPSHOT")
implementation group: 'me.libraryaddict.disguises', name: 'libsdisguises', version: '11.0.6' compileOnly group: 'me.libraryaddict.disguises', name: 'libsdisguises', version: '11.0.6'
//TODO implementation 'me.lucko:commodore:2.2' compileOnly 'org.projectlombok:lombok:1.18.42'
annotationProcessor 'org.projectlombok:lombok:1.18.42'
implementation "eu.okaeri:okaeri-configs-validator-okaeri:$okaeriConfigsVersion"
implementation "eu.okaeri:okaeri-configs-yaml-bukkit:$okaeriConfigsVersion"
implementation "eu.okaeri:okaeri-configs-serdes-bukkit:$okaeriConfigsVersion"
implementation "eu.okaeri:okaeri-configs-serdes-commons:$okaeriConfigsVersion"
implementation "org.incendo:cloud-paper:2.0.0-beta.13"
} }
def targetJavaVersion = 21 def targetJavaVersion = 17
java { java {
def javaVersion = JavaVersion.toVersion(targetJavaVersion) def javaVersion = JavaVersion.toVersion(targetJavaVersion)
sourceCompatibility = javaVersion sourceCompatibility = javaVersion
@@ -56,14 +69,83 @@ processResources {
} }
} }
jar { tasks.shadowJar {
//destinationDirectory.set(file("/home/hadvart/Documents/Minecraft/Pufferfish 1.20.4/plugins")) archiveClassifier.set("")
from sourceSets.main.output relocate("eu.okaeri", "hdvtdev.blockandseek.libs.okaeri")
from { relocate("org.incendo", "hdvtdev.blockandseek.libs.cloud")
configurations.runtimeClasspath.findAll {
it.name.startsWith('Java-Probability-Collection') || it.name.startsWith('commodore') || it.name.startsWith('brigadier')
}.collect { zipTree(it) }
}
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
} }
tasks.build {
dependsOn(tasks.shadowJar)
}
tasks.register('deploy') {
dependsOn build
doLast {
def targetDir = project.property('server.plugins.dir')
def rconHost = project.property('rcon.host')
def rconPort = project.property('rcon.port') as int
def rconPass = project.property('rcon.password')
def jarFile = tasks.jar.archiveFile.get().asFile
if (!file(targetDir).exists()) {
println "Err folder not found : $targetDir"
return
}
copy {
from jarFile
into targetDir
}
try {
sendRconCommand(rconHost, rconPort, rconPass, "plugman reload BlockAndSeek")
sendRconCommand(rconHost, rconPort, rconPass, "reload")
println "Plugin reloaded"
} catch (Exception e) {
println "RCON: ${e.message}"
}
}
}
def sendRconCommand(String host, int port, String password, String command) {
new Socket(host, port).withCloseable { socket ->
def out = socket.outputStream
def inp = socket.inputStream
def sendPacket = { int id, int type, String body ->
byte[] bodyBytes = body.getBytes("UTF-8")
int length = 4 + 4 + bodyBytes.length + 2 // id + type + body + 2 nulls
ByteBuffer buffer = ByteBuffer.allocate(4 + length)
buffer.order(ByteOrder.LITTLE_ENDIAN)
buffer.putInt(length)
buffer.putInt(id)
buffer.putInt(type)
buffer.put(bodyBytes)
buffer.put((byte) 0)
buffer.put((byte) 0)
out.write(buffer.array())
out.flush()
}
sendPacket(1, 3, password)
inp.read(new byte[4096])
sendPacket(2, 2, command)
byte[] buffer = new byte[4096]
int read = inp.read(buffer)
if (read > 12) {
String response = new String(buffer, 12, read - 12 - 2, "UTF-8")
println "Server response: $response"
}
}
}

View File

@@ -0,0 +1,7 @@
plugin.name=MySuperPlugin
server.plugins.dir=/home/hadvart/Documents/minecraft/Pufferfish/1.21.8/plugins
rcon.host=127.0.0.1
rcon.port=25575
rcon.password=14881488

View File

@@ -1,315 +0,0 @@
package hdvtdev.blockAndSeek;
import hdvtdev.blockAndSeek.managers.ConfigManager;
import hdvtdev.blockAndSeek.managers.GamesManager;
import hdvtdev.blockAndSeek.managers.ItemManager;
import hdvtdev.blockAndSeek.managers.PropManager;
import hdvtdev.blockAndSeek.roulette.RouletteCreator;
import me.libraryaddict.disguise.DisguiseAPI;
import org.bukkit.*;
import org.bukkit.entity.Player;
import org.bukkit.inventory.PlayerInventory;
import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.persistence.PersistentDataType;
import org.bukkit.scheduler.BukkitRunnable;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class BlockAndSeekGame {
private final Map<Player, PlayerType> players = new ConcurrentHashMap<>();
private final Map<Player, RouletteCreator> hiderRoulette = new HashMap<>();
private volatile boolean started = false;
private final BlockAndSeekMap map;
private final Location lobby;
private final Location spawn;
private final String name;
public BlockAndSeekGame(String name, BlockAndSeekMap map) {
this.map = map;
World world = Bukkit.getWorld(name);
world.setTime(1000);
world.setGameRule(GameRule.DO_DAYLIGHT_CYCLE, false);
world.setStorm(false);
world.setGameRule(GameRule.DO_WEATHER_CYCLE, false);
this.name = name;
this.lobby = map.getLobbyLocation(world);
this.spawn = map.getSpawnLocation(world);
this.startBukkitTask();
}
public int playerCount() {
return players.size();
}
public boolean isStarted() {
return started;
}
public int maxPlayers() {
return map.getMaxPlayers();
}
public boolean addPlayer(Player player) {
if (!started) {
players.put(player, PlayerType.HIDER);
player.getPersistentDataContainer().set(Keys.GAME, PersistentDataType.STRING, name);
player.teleport(lobby);
PlayerInventory inventory = player.getInventory();
inventory.clear();
inventory.setItem(8, Localization.translateItem(player, ItemManager.getLeaveItem()));
player.setGameMode(GameMode.SURVIVAL);
player.setInvulnerable(true);
player.setHealth(20.0);
player.setFoodLevel(20);
Localization.sendMessage(
players.keySet(),
true,
"player-join",
"{player}", player.getName(),
"{players}", players.size() + "/" + map.getMaxPlayers()
);
return true;
} else return false;
}
public void removePlayer(Player player) {
players.remove(player);
DisguiseAPI.undisguiseToAll(player);
player.getInventory().clear();
player.setGameMode(GameMode.SURVIVAL);
player.getPersistentDataContainer().remove(Keys.GAME);
Config config = ConfigManager.getConfig();
if (config.forceControl()) ItemManager.defaultInventory(player);
player.teleport(config.defaultSpawn());
Localization.sendMessage(
players.keySet(),
true,
"player-leave",
"{player}", player.getName(),
"{players}", started ? "" : players.size() + "/" + map.getMaxPlayers()
);
}
public void setSpectator(Player hider, @Nullable Player seeker) {
hider.spigot().respawn();
hider.teleport(hider.getLastDeathLocation());
players.put(hider, PlayerType.SPECTATOR);
hider.setGameMode(GameMode.SPECTATOR);
Localization.sendMessage(
players.keySet(),
true,
seeker == null ? "hider-died" : "hider-was-found",
"{hider}", hider.getName(),
"{seeker}", seeker == null ? "" : seeker.getName()
);
}
private long getHidersCount() {
return players.values().stream().filter(type -> type == PlayerType.HIDER).count();
}
private long getSeekersCount() {
return players.values().stream().filter(type -> type == PlayerType.SEEKER).count();
}
private List<Player> getSeekers() {
return players.entrySet().stream().filter(entry -> entry.getValue() == PlayerType.SEEKER).map(Map.Entry::getKey).toList();
}
private List<Player> getHiders() {
return players.entrySet().stream().filter(entry -> entry.getValue() == PlayerType.HIDER).map(Map.Entry::getKey).toList();
}
private Player getLastHider() {
return players.keySet().iterator().next();
}
private void start() {
started = true;
selectRandomSeekers((int) Math.round(players.size() * 0.25));
List<Player> hiders = getHiders();
for (Player hider : hiders) {
hider.getPersistentDataContainer().set(Keys.HIDER, PersistentDataType.BOOLEAN, true);
hider.teleport(spawn);
hider.setInvulnerable(false);
PlayerInventory inventory = hider.getInventory();
inventory.clear();
inventory.addItem(Localization.translateItem(hider, ItemManager.getFreezeItem()));
inventory.addItem(Localization.translateItem(hider, ItemManager.getFaceChangingItem()));
hiderRoulette.put(hider, new RouletteCreator(hider, map.getBlocks()));
}
}
private void end(boolean force) {
GamesManager.remove(name);
if (!force) {
Config config = ConfigManager.getConfig();
Location serverLobby = config.defaultSpawn();
boolean defaultInventory = config.forceControl();
for (Player player : players.keySet()) {
DisguiseAPI.undisguiseToAll(player);
if (defaultInventory) ItemManager.defaultInventory(player);
player.setGlowing(false);
player.setInvulnerable(false);
Utils.setLevelWithBar(player, 0);
player.setVisibleByDefault(true);
player.setHealth(20);
player.setGameMode(GameMode.SURVIVAL);
player.teleport(serverLobby);
}
}
}
private void preEnd() {
for (Player player : players.keySet()) {
PersistentDataContainer container = player.getPersistentDataContainer();
player.setInvulnerable(true);
container.remove(Keys.HIDER);
container.remove(Keys.SEEKER);
container.remove(Keys.GAME);
}
for (Player hider : getHiders()) {
hider.getInventory().clear();
PropManager.unfreezeIfFrozen(hider);
hider.setGlowing(true);
RouletteCreator rouletteCreator = hiderRoulette.get(hider);
rouletteCreator.getTask().cancelBoth();
rouletteCreator.closeInventory();
}
}
private void selectRandomSeekers(int count) {
ArrayList<Player> rawSeekers = new ArrayList<>();
Set<Player> playerSet = players.keySet();
for (Player player : playerSet) {
if (!GamesManager.triggerSeekerImmune(player)) rawSeekers.add(player);
}
Collections.shuffle(rawSeekers);
for (Player seeker : rawSeekers.subList(0, Math.min(count, playerSet.size()))) {
players.put(seeker, PlayerType.SEEKER);
ItemManager.setSeekerSet(seeker);
Utils.setLevelWithBar(seeker, 100);
seeker.setInvulnerable(false);
seeker.getPersistentDataContainer().set(Keys.SEEKER, PersistentDataType.BOOLEAN, true);
GamesManager.addSeekerImmune(seeker);
}
}
private void startBukkitTask() {
new BukkitRunnable() {
int waitTime = 30;
final int defaultWaitTime = waitTime;
int duration = map.getDuration();
final int seekerDeploy = duration - 15;
@Override
public void run() {
if (waitTime != 0) {
int playerSize = players.size();
if (playerSize >= map.getMinPlayers()) {
if (waitTime % 5 == 0 || waitTime <= 5) {
Localization.sendMessage(
players.keySet(),
true,
"wait-time-left",
"{time}", String.valueOf(waitTime)
);
}
waitTime--;
} else if (playerSize == 0) {
end(true);
this.cancel();
} else if (waitTime != defaultWaitTime) {
waitTime = defaultWaitTime;
}
} else {
if (!started) {
start();
} else {
if (players.isEmpty()) {
end(false);
this.cancel();
return;
}
if (duration > 0 && getHidersCount() == 0) {
Localization.sendTitle(
players.keySet(),
false,
"seekers-won"
);
preEnd();
duration = -1;
}
if (duration == seekerDeploy) {
for (Player seeker : getSeekers()) {
seeker.teleport(spawn);
}
}
if (duration > 0 && getSeekersCount() == 0) duration = 0;
if (duration == 0) {
preEnd();
if (getHidersCount() == 1) {
Localization.sendTitle(
players.keySet(),
false,
"hiders-solo-win",
"{hider}", getLastHider().getName()
);
} else {
Localization.sendTitle(
players.keySet(),
false,
"hiders-won"
);
}
duration--; // уменьшаем только один раз
} else if (duration > 0) {
Localization.sendActionBar(
players.keySet(),
false,
"game-time-left",
"{time}", String.valueOf(duration)
);
duration--;
} else if (duration == -10) {
end(false);
this.cancel();
} else {
duration--;
}
}
}
}
}.runTaskTimer(BlockAndSeek.getInstance(), 0L, 20L);
}
private enum PlayerType {
SEEKER,
HIDER,
SPECTATOR
}
}

View File

@@ -1,173 +0,0 @@
package hdvtdev.blockAndSeek;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
public class BlockAndSeekMap implements ConfigurationSerializable {
private List<Integer> spawn;
private List<Integer> lobby;
private int duration;
private int minPlayers;
private int maxPlayers;
private List<Block> blocks;
public BlockAndSeekMap() {
}
public BlockAndSeekMap(List<Integer> spawn, List<Integer> lobby, int duration, int minPlayers, int maxPlayers,
List<Block> blocks) {
this.spawn = spawn;
this.lobby = lobby;
this.duration = duration;
this.minPlayers = minPlayers;
this.maxPlayers = maxPlayers;
this.blocks = blocks;
}
public List<Integer> getSpawn() {
return spawn;
}
public Location getSpawnLocation(@Nullable World world) {
return new Location(world, spawn.getFirst(), spawn.get(1), spawn.get(2));
}
public void setSpawn(List<Integer> spawn) {
this.spawn = spawn;
}
public List<Integer> getLobby() {
return lobby;
}
public Location getLobbyLocation(@Nullable World world) {
return new Location(world, lobby.getFirst(), lobby.get(1), lobby.get(2));
}
public void setLobby(List<Integer> lobby) {
this.lobby = lobby;
}
public int getDuration() {
return duration;
}
public void setDuration(int duration) {
this.duration = duration;
}
public int getMinPlayers() {
return minPlayers;
}
public void setMinPlayers(int minPlayers) {
this.minPlayers = minPlayers;
}
public int getMaxPlayers() {
return maxPlayers;
}
public void setMaxPlayers(int maxPlayers) {
this.maxPlayers = maxPlayers;
}
public List<Block> getBlocks() {
return blocks;
}
public void setBlocks(List<Block> blocks) {
this.blocks = blocks;
}
public boolean isReady() {
return !spawn.isEmpty() && !lobby.isEmpty() && duration > 0 && !blocks.isEmpty() && minPlayers > 0 && maxPlayers > 0;
}
public static BlockAndSeekMap defaultMap() {
return new BlockAndSeekMap(List.of(), List.of(), 0, 0, 0, List.of());
}
public Set<MapStatus> check() {
Set<MapStatus> status = new HashSet<>();
if (spawn.isEmpty()) status.add(MapStatus.SPAWN_REQUIRED);
if (lobby.isEmpty()) status.add(MapStatus.LOBBY_REQUIRED);
if (duration <= 0) status.add(MapStatus.DURATION_REQUIRED);
if (minPlayers <= 0) status.add(MapStatus.MIN_PLAYERS_REQUIRED);
if (maxPlayers <= 0) status.add(MapStatus.MAX_PLAYERS_REQUIRED);
if (blocks.isEmpty()) status.add(MapStatus.BLOCKS_REQUIRED);
return status;
}
@Override
public @NotNull Map<String, Object> serialize() {
Map<String, Object> map = new HashMap<>();
map.put("spawn", spawn);
map.put("lobby", lobby);
map.put("duration", duration);
map.put("min-players", minPlayers);
map.put("max-players", maxPlayers);
List<Map<String, Object>> serBlocks = new ArrayList<>();
for (Block block : blocks) {
serBlocks.add(block.toMap());
}
map.put("blocks", serBlocks);
return map;
}
@NotNull
@SuppressWarnings("unchecked")
public static BlockAndSeekMap deserialize(@NotNull Map<String, Object> args) {
BlockAndSeekMap map = new BlockAndSeekMap();
map.setSpawn((List<Integer>) args.get("spawn"));
map.setLobby((List<Integer>) args.get("lobby"));
map.setDuration((int) args.get("duration"));
map.setMinPlayers((int) args.get("min-players"));
map.setMaxPlayers((int) args.get("max-players"));
map.setBlocks(((List<Map<String, Object>>) args.get("blocks")).stream().map(Block::fromMap).toList());
return map;
}
@Override
public String toString() {
return String.format("BlockAndSeekMap[spawn=%s, lobby=%s, duration=%s, minPlayers=%s, maxPlayers=%s, blocks=%s]", spawn, lobby, duration, minPlayers, maxPlayers, blocks);
}
public enum MapStatus {
SPAWN_REQUIRED,
LOBBY_REQUIRED,
DURATION_REQUIRED,
BLOCKS_REQUIRED,
MIN_PLAYERS_REQUIRED,
MAX_PLAYERS_REQUIRED
}
public record Block(@NotNull ItemStack block, int chance) {
public Map<String, Object> toMap() {
Map<String, Object> map = new HashMap<>();
map.put("block", block.getType().name());
map.put("chance", chance);
return map;
}
public static Block fromMap(Map<String, Object> map) {
return new Block(new ItemStack(Material.valueOf(((String) map.get("block")).toUpperCase())), (int) map.get("chance"));
}
}
}

View File

@@ -1,31 +1,39 @@
package hdvtdev.blockAndSeek; package hdvtdev.blockandseek;
import hdvtdev.blockAndSeek.eventListeners.RequiredEventListener; import hdvtdev.blockandseek.eventListeners.RequiredEventListener;
import hdvtdev.blockAndSeek.eventListeners.EventListener; import hdvtdev.blockandseek.eventListeners.EventListener;
import hdvtdev.blockAndSeek.eventListeners.ForceControlEventListener; import hdvtdev.blockandseek.eventListeners.ForceControlEventListener;
import hdvtdev.blockandseek.managers.*;
import hdvtdev.blockandseek.objects.*;
import hdvtdev.blockandseek.roulette.RouletteCreator;
import me.libraryaddict.disguise.LibsDisguises; import me.libraryaddict.disguise.LibsDisguises;
import org.bukkit.Bukkit; import net.kyori.adventure.text.minimessage.MiniMessage;
import org.bukkit.*;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.command.PluginCommand; import org.bukkit.command.PluginCommand;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import org.bukkit.scheduler.BukkitRunnable;
import org.incendo.cloud.Command;
import org.incendo.cloud.bukkit.CloudBukkitCapabilities; import org.incendo.cloud.bukkit.CloudBukkitCapabilities;
import org.incendo.cloud.bukkit.parser.PlayerParser; import org.incendo.cloud.bukkit.parser.location.LocationParser;
import org.incendo.cloud.execution.ExecutionCoordinator; import org.incendo.cloud.execution.ExecutionCoordinator;
import org.incendo.cloud.paper.LegacyPaperCommandManager; import org.incendo.cloud.paper.LegacyPaperCommandManager;
import org.incendo.cloud.parser.standard.IntegerParser;
import org.incendo.cloud.parser.standard.StringParser; import org.incendo.cloud.parser.standard.StringParser;
import java.io.File; import java.io.File;
import java.util.Objects; import java.util.*;
import java.util.logging.Logger; import java.util.logging.Logger;
import java.util.stream.Collectors;
public class BlockAndSeek extends JavaPlugin { public class BlockAndSeek extends JavaPlugin {
@@ -66,17 +74,13 @@ public class BlockAndSeek extends JavaPlugin {
this.init(); this.init();
if (!Config.loadConfig()) getPluginLogger().warning("Failed to load BlockAndSeek config.toml! Using default config...");
PluginCommand command = Objects.requireNonNull(getCommand("blockandseek")); PluginCommand command = Objects.requireNonNull(getCommand("blockandseek"));
PluginManager manager = getServer().getPluginManager(); PluginManager manager = getServer().getPluginManager();
manager.registerEvents(Config.forceControl() ? new ForceControlEventListener() : new EventListener(), this); boolean forceControl = Config.forceControl();
if (forceControl) getLogger().info("Using force control");
manager.registerEvents(forceControl ? new ForceControlEventListener() : new EventListener(), this);
manager.registerEvents(new RequiredEventListener(), this); manager.registerEvents(new RequiredEventListener(), this);
} }
@@ -105,6 +109,9 @@ public class BlockAndSeek extends JavaPlugin {
saveResource("languages/en_US.yml", false); saveResource("languages/en_US.yml", false);
} }
MapsManager.loadMaps();
TranslationManager.loadLanguages();
commandManager = LegacyPaperCommandManager.createNative( commandManager = LegacyPaperCommandManager.createNative(
this, this,
ExecutionCoordinator.simpleCoordinator() ExecutionCoordinator.simpleCoordinator()
@@ -130,53 +137,226 @@ public class BlockAndSeek extends JavaPlugin {
} }
private final PropManager propManager = new PropManager();
private void registerCommands() { private void registerCommands() {
Command.Builder<CommandSender> root = commandManager.commandBuilder("blockandseek"); var root = commandManager.commandBuilder("blockandseek");
var editBase = root
.literal("editMap")
.required("map_name", StringParser.stringParser(), MapsManager.mapSuggestions);
//edit commands
commandManager.command(
editBase.literal("setLobby") // Это наш <parameter> как литерал
.required("location", LocationParser.locationParser())
.handler(ctx -> {
})
);
commandManager.command(
editBase.literal("setLocation")
.required("location", LocationParser.locationParser()) // Это <value>
.handler(ctx -> {
String configName = ctx.get("config_name");
Location location = ctx.get("location");
ctx.sender().sendMessage("В конфиге " + configName + " точка установлена: " + location.toString());
})
);
commandManager.command(root
.literal("maps")
.handler(ctx -> {
ctx.sender().sendMessage(MapsManager.getMaps().stream().map(BlockAndSeekMap::getWorld).collect(Collectors.joining(", ")));
})
);
commandManager.command(root
.literal("testMessage")
.handler(ctx -> {
ctx.sender().sendMessage(MiniMessage.miniMessage().deserialize(" <gradient:#FFAA00:#FFD700><bold>System</bold></gradient> <dark_gray>»</dark_gray> <yellow>Ваш игровой режим изменен.</yellow>\n"));
})
);
commandManager.command(root
.literal("menuTest")
.handler(ctx -> {
if (ctx.sender() instanceof Player player) {
player.getInventory().addItem(ItemManager.getMenuItem());
}
})
);
commandManager.command(root commandManager.command(root
.literal("reload") .literal("localeTest")
.handler(ctx -> {
if (ctx.sender() instanceof Player player) {
player.sendMessage("Locale is: " + player.locale());
player.sendMessage(TranslationManager.get(player, TranslationKey.FREEZE_ITEM));
}
})
);
commandManager.command(root
.literal("createGame")
.permission(perm) .permission(perm)
.required("map_name", StringParser.stringParser(), MapsManager.mapSuggestions)
.handler(context -> { .handler(context -> {
String name = context.get("map_name");
GamesManager.createGame(name);
if (context.sender() instanceof Player player) {
GamesManager.get(name).addPlayer(player);
}
}) })
); );
commandManager.command(root commandManager.command(root
.literal("inttest") .literal("createMap")
.required("text", IntegerParser.integerParser(0, 64))
.handler(context -> {
String text = context.get("text");
context.sender().sendMessage(text);
})
);
commandManager.command(root
.literal("map")
.permission(perm) .permission(perm)
.required("map", StringParser.stringParser()) .required("map_name", StringParser.stringParser(), MapsManager.worldSuggestions)
.required("action", StringParser.stringParser())
.handler(context -> { .handler(context -> {
Player target = context.get("target"); String name = context.get("map_name");
int amount = context.getOrDefault("amount", 1); MapsManager.createMap(name);
context.sender().sendMessage("Map probably created");
}) })
); );
commandManager.command(root
.literal("joinGame")
.permission(perm)
.required("name", StringParser.stringParser())
.handler(context -> {
String name = context.get("name");
if (context.sender() instanceof Player player) {
BlockAndSeekGame game = GamesManager.get(name);
if (game != null) game.addPlayer(player);
}
})
);
commandManager.command(
editBase.literal("generateBlocks")
.handler(ctx -> {
if (ctx.sender() instanceof Player player) {
String mapName = ctx.get("map_name");
BlockAndSeekMap map = MapsManager.getMap(mapName);
var cached = BlocksGenerator.get(map.getSpawn().getWorld().getName());
if (cached != null) {
new RouletteCreator(player, cached);
} else {
BlocksGenerator.getSortedBlockStats(player.getLocation(), 32, (var list) -> {
List<Map.Entry<Material, Long>> selectedBlocks = new ArrayList<>(list);
// 2. Перемешиваем и берем 30 случайных (или меньше, если всего блоков меньше 30)
Collections.shuffle(selectedBlocks);
if (selectedBlocks.size() > 30) {
selectedBlocks = selectedBlocks.subList(0, 30);
}
selectedBlocks.sort((a, b) -> Long.compare(b.getValue(), a.getValue()));
List<Map.Entry<Material, Rarity>> result = new ArrayList<>();
// 4. Считаем общий вес редкостей (100 + 50 + 25 + ...) = 189
int totalWeight = Arrays.stream(Rarity.values()).mapToInt(Rarity::getChance).sum();
// Шаг веса на один блок.
// Представь длинную полоску длиной 189 единиц. Мы режем её на 30 равных частей.
// Куда попадает разрез — такая и редкость.
double weightStep = (double) totalWeight / selectedBlocks.size();
double currentWeightPosition = 0; // Текущая позиция на шкале весов
// Идем по отсортированному списку (от самых частых к самым редким)
for (Map.Entry<Material, Long> entry : selectedBlocks) {
// Определяем, в какой диапазон редкости попадает текущий блок
Rarity assignedRarity = Rarity.COMMON; // Дефолт
int weightCursor = 0;
for (Rarity r : Rarity.values()) {
weightCursor += r.getChance();
// Если текущая позиция веса меньше границы этой редкости — мы нашли её
// (Добавляем половину шага, чтобы брать "центр" диапазона блока для точности)
if ((currentWeightPosition + weightStep / 2) <= weightCursor) {
assignedRarity = r;
break;
}
}
result.add(new AbstractMap.SimpleEntry<>(entry.getKey(), assignedRarity));
// Сдвигаемся дальше по шкале
currentWeightPosition += weightStep;
}
var r = result.stream().map(e -> new PropBlock(new ItemStack(e.getKey()), e.getValue())).toList();
map.setBlocks(r);
map.save();
BlocksGenerator.addCache(player.getLocation().getWorld().getName(), r);
});
}
}
})
);
}
private void spawnSmokeCloud(Location center) {
new BukkitRunnable() {
int ticksPassed = 0;
final int maxIterations = (10 * 20) / 5;
@Override
public void run() {
if (ticksPassed >= maxIterations) {
this.cancel();
return;
}
World world = center.getWorld();
if (world == null) return;
world.spawnParticle(Particle.EXPLOSION_HUGE, center, 15, 1.2, 1.2, 1.2, 0.05);
for (var entity : world.getNearbyEntities(center, 2.5, 2.5, 2.5)) {
if (entity instanceof Player player && player.getLocation().distanceSquared(center) <= 9) {
player.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, 40, 1));
}
}
ticksPassed++;
}
}.runTaskTimer(this, 0L, 5L);
} }
} }

View File

@@ -1,50 +1,191 @@
package hdvtdev.blockAndSeek; package hdvtdev.blockandseek;
import hdvtdev.blockandseek.objects.PropBlock;
import org.bukkit.*; import org.bukkit.*;
import org.bukkit.block.data.AnaloguePowerable; import org.bukkit.block.Banner;
import org.bukkit.block.data.BlockData; import org.bukkit.block.Comparator;
import org.bukkit.block.data.Openable; import org.bukkit.block.data.*;
import org.bukkit.block.data.Powerable; import org.bukkit.block.data.type.Observer;
import org.bukkit.block.data.type.Piston;
import org.bukkit.block.data.type.RedstoneWire;
import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.scheduler.BukkitRunnable;
import org.jetbrains.annotations.ApiStatus;
import java.util.ArrayList; import org.jetbrains.annotations.ApiStatus;
import java.util.HashMap; import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Map; import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.Consumer; import java.util.function.Consumer;
@ApiStatus.Experimental @ApiStatus.Experimental
public class BlocksGenerator { public class BlocksGenerator {
private static boolean isSupported(Material type, BlockData data) { private static final Set<Material> BLACKLIST = EnumSet.noneOf(Material.class);
private static final Map<String, List<PropBlock>> cached = new HashMap<>();
if (type.isAir() || type.hasGravity() || !type.isSolid()) { static {
// --- 1. ГРАВИТАЦИЯ (Сыпучие) ---
BLACKLIST.add(Material.SAND);
BLACKLIST.add(Material.RED_SAND);
BLACKLIST.add(Material.GRAVEL);
BLACKLIST.add(Material.DRAGON_EGG);
BLACKLIST.add(Material.ANVIL);
BLACKLIST.add(Material.CHIPPED_ANVIL);
BLACKLIST.add(Material.DAMAGED_ANVIL);
BLACKLIST.add(Material.POINTED_DRIPSTONE); // Сталактит (может упасть)
BLACKLIST.add(Material.SCAFFOLDING); // Строительные леса
// --- 2. РЕДСТОУН И МЕХАНИЗМЫ ---
BLACKLIST.addAll(Tag.BUTTONS.getValues()); // Все кнопки
BLACKLIST.addAll(Tag.PRESSURE_PLATES.getValues()); // Все нажимные плиты
BLACKLIST.addAll(Tag.DOORS.getValues()); // Двери
BLACKLIST.addAll(Tag.TRAPDOORS.getValues()); // Люки
BLACKLIST.addAll(Tag.FENCE_GATES.getValues()); // Калитки
BLACKLIST.addAll(Tag.RAILS.getValues()); // Рельсы
BLACKLIST.add(Material.REDSTONE_WIRE);
BLACKLIST.add(Material.REDSTONE_TORCH);
BLACKLIST.add(Material.REDSTONE_BLOCK); // Если не нужен блок редстоуна
BLACKLIST.add(Material.REPEATER);
BLACKLIST.add(Material.COMPARATOR);
BLACKLIST.add(Material.OBSERVER);
BLACKLIST.add(Material.PISTON);
BLACKLIST.add(Material.STICKY_PISTON);
BLACKLIST.add(Material.PISTON_HEAD);
BLACKLIST.add(Material.MOVING_PISTON);
BLACKLIST.add(Material.DISPENSER);
BLACKLIST.add(Material.DROPPER);
BLACKLIST.add(Material.HOPPER);
BLACKLIST.add(Material.DAYLIGHT_DETECTOR);
BLACKLIST.add(Material.TRIPWIRE);
BLACKLIST.add(Material.TRIPWIRE_HOOK);
BLACKLIST.add(Material.LEVER);
BLACKLIST.add(Material.TNT);
BLACKLIST.add(Material.NOTE_BLOCK);
BLACKLIST.add(Material.JUKEBOX);
BLACKLIST.add(Material.SCULK_SENSOR);
BLACKLIST.add(Material.SCULK_SHRIEKER);
BLACKLIST.add(Material.SCULK_CATALYST);
BLACKLIST.add(Material.COMMAND_BLOCK);
BLACKLIST.add(Material.REPEATING_COMMAND_BLOCK);
BLACKLIST.add(Material.CHAIN_COMMAND_BLOCK);
BLACKLIST.add(Material.LIGHTNING_ROD);
BLACKLIST.add(Material.BELL); // Колокол
// --- 3. ХРУПКИЕ / ЗАВИСЯЩИЕ ОТ ОПОРЫ (Растения, декор) ---
BLACKLIST.addAll(Tag.SAPLINGS.getValues()); // Саженцы
BLACKLIST.addAll(Tag.FLOWERS.getValues()); // Цветы
BLACKLIST.addAll(Tag.CROPS.getValues()); // Пшеница, картошка и т.д.
BLACKLIST.addAll(Tag.BANNERS.getValues()); // Флаги (стоячие и настенные)
BLACKLIST.addAll(Tag.SIGNS.getValues()); // Таблички
BLACKLIST.addAll(Tag.BEDS.getValues()); // Кровати
BLACKLIST.addAll(Tag.CANDLES.getValues()); // Свечи
BLACKLIST.addAll(Tag.CAMPFIRES.getValues()); // Костры
BLACKLIST.addAll(Tag.CAULDRONS.getValues()); // Котлы
BLACKLIST.addAll(Tag.WOOL_CARPETS.getValues()); // Ковры
BLACKLIST.addAll(Tag.FLOWER_POTS.getValues()); // Горшки
BLACKLIST.add(Material.BROWN_MUSHROOM);
BLACKLIST.add(Material.RED_MUSHROOM);
BLACKLIST.add(Material.CRIMSON_FUNGUS);
BLACKLIST.add(Material.WARPED_FUNGUS);
BLACKLIST.add(Material.SUGAR_CANE);
BLACKLIST.add(Material.BAMBOO);
BLACKLIST.add(Material.BAMBOO_SAPLING);
BLACKLIST.add(Material.CACTUS);
BLACKLIST.add(Material.DEAD_BUSH);
BLACKLIST.add(Material.SWEET_BERRY_BUSH);
BLACKLIST.add(Material.PUMPKIN_STEM);
BLACKLIST.add(Material.MELON_STEM);
BLACKLIST.add(Material.NETHER_WART);
BLACKLIST.add(Material.CHORUS_PLANT);
BLACKLIST.add(Material.CHORUS_FLOWER);
BLACKLIST.add(Material.KELP);
BLACKLIST.add(Material.KELP_PLANT);
BLACKLIST.add(Material.SEAGRASS);
BLACKLIST.add(Material.TALL_SEAGRASS);
BLACKLIST.add(Material.VINE);
BLACKLIST.add(Material.WEEPING_VINES);
BLACKLIST.add(Material.TWISTING_VINES);
BLACKLIST.add(Material.LILY_PAD);
BLACKLIST.add(Material.SMALL_DRIPLEAF);
BLACKLIST.add(Material.BIG_DRIPLEAF);
BLACKLIST.add(Material.SPORE_BLOSSOM);
BLACKLIST.add(Material.HANGING_ROOTS);
BLACKLIST.add(Material.GLOW_LICHEN);
BLACKLIST.add(Material.AMETHYST_CLUSTER);
BLACKLIST.add(Material.SMALL_AMETHYST_BUD);
BLACKLIST.add(Material.MEDIUM_AMETHYST_BUD);
BLACKLIST.add(Material.LARGE_AMETHYST_BUD);
BLACKLIST.add(Material.POINTED_DRIPSTONE);
BLACKLIST.add(Material.COCOA);
BLACKLIST.add(Material.TORCH);
BLACKLIST.add(Material.SOUL_TORCH);
BLACKLIST.add(Material.LANTERN);
BLACKLIST.add(Material.SOUL_LANTERN);
BLACKLIST.add(Material.CHAIN);
BLACKLIST.add(Material.END_ROD);
BLACKLIST.add(Material.IRON_BARS); // Обычно некрасиво в топах
BLACKLIST.add(Material.LADDER);
BLACKLIST.add(Material.SNOW); // Снежный покров (тонкий)
BLACKLIST.add(Material.TURTLE_EGG);
BLACKLIST.add(Material.CAKE);
// --- 4. КОНТЕЙНЕРЫ (Если не хочешь, чтобы выпадали сундуки) ---
BLACKLIST.add(Material.CHEST);
BLACKLIST.add(Material.TRAPPED_CHEST);
BLACKLIST.add(Material.ENDER_CHEST);
BLACKLIST.add(Material.BARREL);
BLACKLIST.add(Material.FURNACE);
BLACKLIST.add(Material.BLAST_FURNACE);
BLACKLIST.add(Material.SMOKER);
BLACKLIST.add(Material.BREWING_STAND);
BLACKLIST.add(Material.LECTERN);
BLACKLIST.add(Material.COMPOSTER);
BLACKLIST.add(Material.BEEHIVE);
BLACKLIST.add(Material.BEE_NEST);
// --- 5. ТЕХНИЧЕСКИЕ И ПРОЗРАЧНЫЕ ---
BLACKLIST.add(Material.BARRIER);
BLACKLIST.add(Material.STRUCTURE_VOID);
BLACKLIST.add(Material.STRUCTURE_BLOCK);
BLACKLIST.add(Material.JIGSAW);
BLACKLIST.add(Material.LIGHT);
BLACKLIST.add(Material.SPAWNER);
BLACKLIST.add(Material.COBWEB);
BLACKLIST.add(Material.SLIME_BLOCK); // Спорно, но липкий
BLACKLIST.add(Material.HONEY_BLOCK);
// --- 6. ЗАЧИСТКА ПО ИМЕНАМ (На всякий случай) ---
// Добавляем все, что мы могли забыть, но что имеет общие корни
for (Material m : Material.values()) {
String name = m.name();
if (name.contains("POTTED") || // Горшки с цветами
name.contains("WALL_") || // Настенные варианты (факелы, знаки)
name.contains("HEAD") || // Головы
name.contains("SKULL") || // Черепа
name.contains("BANNER") || // Флаги (дублируем для надежности)
name.contains("CORAL") // Кораллы (дохнут без воды)
) {
BLACKLIST.add(m);
}
}
}
// Твой метод проверки стал очень простым
private static boolean isSupported(Material type) {
// 1. Воздух
if (type.isAir()) return false;
// 2. Черный список (самая надежная проверка)
if (BLACKLIST.contains(type)) {
return false; return false;
} }
if (data instanceof Powerable // 3. Дополнительная защита: блок должен быть твердым
|| data instanceof Openable // (Это отсечет то, что мы могли случайно забыть в списке, типа нитки/String)
|| data instanceof AnaloguePowerable) { return type.isSolid();
return false;
}
return switch (type) {
case CACTUS, FARMLAND,
TNT, DISPENSER, DROPPER, HOPPER,
PISTON, STICKY_PISTON, OBSERVER,
COMMAND_BLOCK, REPEATING_COMMAND_BLOCK,
CHAIN_COMMAND_BLOCK,
SCULK_SENSOR, SCULK_SHRIEKER,
DAYLIGHT_DETECTOR, JUKEBOX,
TURTLE_EGG, DRAGON_EGG,
BAMBOO, BAMBOO_SAPLING,
CAKE, LECTERN, COMPOSTER,
CAMPFIRE, SOUL_CAMPFIRE,
BEEHIVE, BEE_NEST
-> false;
default -> true;
};
} }
public static void getSortedBlockStats(Location center, int chunkRadius, Consumer<List<Map.Entry<Material, Long>>> callback) { public static void getSortedBlockStats(Location center, int chunkRadius, Consumer<List<Map.Entry<Material, Long>>> callback) {
@@ -81,11 +222,10 @@ public class BlocksGenerator {
for (int z = 0; z < 16; z++) { for (int z = 0; z < 16; z++) {
for (int y = minH; y < maxH; y++) { for (int y = minH; y < maxH; y++) {
BlockData data = snap.getBlockData(x, y, z); BlockData data = snap.getBlockData(x, y, z);
Material type = data.getMaterial(); Material type = data.getMaterial();
if (isSupported(type, data)) { if (isSupported(type)) {
counts.put(type, counts.getOrDefault(type, 0L) + 1); counts.put(type, counts.getOrDefault(type, 0L) + 1);
} }
} }
@@ -93,12 +233,9 @@ public class BlocksGenerator {
} }
} }
List<Map.Entry<Material, Long>> sortedList = new ArrayList<>(counts.entrySet()); List<Map.Entry<Material, Long>> sortedList = new ArrayList<>(counts.entrySet());
sortedList.sort(Map.Entry.comparingByValue()); sortedList.sort(Map.Entry.comparingByValue());
new BukkitRunnable() { new BukkitRunnable() {
@Override @Override
public void run() { public void run() {
@@ -109,5 +246,21 @@ public class BlocksGenerator {
}.runTaskAsynchronously(BlockAndSeek.getInstance()); }.runTaskAsynchronously(BlockAndSeek.getInstance());
} }
public static void addCache(String key, List<PropBlock> propBlocks) {
cached.put(key, propBlocks);
}
@Nullable
public static List<PropBlock> get(String key) {
return cached.get(key);
}
private static final ThreadLocalRandom random = ThreadLocalRandom.current();
@Nullable
public static PropBlock getRandom(String key) {
List<PropBlock> propBlocks = get(key);
return propBlocks == null ? null : propBlocks.get(random.nextInt(0, propBlocks.size()));
}
} }

View File

@@ -1,4 +1,4 @@
package hdvtdev.blockAndSeek; package hdvtdev.blockandseek;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;

View File

@@ -1,21 +0,0 @@
package hdvtdev.blockAndSeek;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
public class CommandListener implements TabExecutor {
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
return false;
}
@Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
return List.of();
}
}

View File

@@ -1,4 +1,4 @@
package hdvtdev.blockAndSeek; package hdvtdev.blockandseek;
import eu.okaeri.configs.ConfigManager; import eu.okaeri.configs.ConfigManager;
import eu.okaeri.configs.OkaeriConfig; import eu.okaeri.configs.OkaeriConfig;
@@ -8,22 +8,43 @@ import eu.okaeri.configs.serdes.commons.SerdesCommons;
import eu.okaeri.configs.yaml.bukkit.YamlBukkitConfigurer; import eu.okaeri.configs.yaml.bukkit.YamlBukkitConfigurer;
import eu.okaeri.configs.yaml.bukkit.serdes.SerdesBukkit; import eu.okaeri.configs.yaml.bukkit.serdes.SerdesBukkit;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.WorldCreator; import org.bukkit.WorldCreator;
import java.io.File; import java.io.File;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
public class Config extends OkaeriConfig { public class Config extends OkaeriConfig {
@Exclude @Exclude
private static final AtomicReference<Config> config = new AtomicReference<>(new Config()); private static Config config;
static {
try {
File configFile = new File(BlockAndSeek.getPluginDataFolder(), "config.yml");
if (!configFile.exists()) configFile.createNewFile();
config = ConfigManager.create(Config.class, (it) -> {
it.withConfigurer(
new YamlBukkitConfigurer(),
new SerdesBukkit(),
new SerdesCommons()
);
it.withBindFile(configFile);
it.withLogger(BlockAndSeek.getPluginLogger());
it.saveDefaults();
});
config.load();
config.save();
} catch (Exception e) {
BlockAndSeek.getPluginLogger().severe("Failed to load config.yml: " + e.getMessage());
e.printStackTrace();
}
}
@Comment("Server options.") @Comment("Server options.")
private Server server = new Server(false); private ServerSettings serverSettings = new ServerSettings();
@Comment("Spawn location. Useless if the Server.forceControl is false.") @Comment("Spawn location. Useless if the Server.forceControl is false.")
private Location spawn = Objects.requireNonNull(Bukkit.createWorld(new WorldCreator("world"))).getSpawnLocation(); private Location spawn = Objects.requireNonNull(Bukkit.createWorld(new WorldCreator("world"))).getSpawnLocation();
@Comment("Show hidden BlockAndSeek commands.") @Comment("Show hidden BlockAndSeek commands.")
@@ -31,50 +52,23 @@ public class Config extends OkaeriConfig {
public static boolean debugEnabled() { public static boolean debugEnabled() {
return config.get().enableDebugCommands; return config.enableDebugCommands;
} }
public static boolean forceControl() { public static boolean forceControl() {
return config.get().server.forceControl; return config.serverSettings.forceControl;
} }
public static Location spawn() { public static Location spawn() {
return config.get().spawn; return config.spawn;
} }
public static String toStaticString() {
public static boolean loadConfig() { return String.format("BlockAndSeekConfig[forceControl = %s, spawn = %s, enableDebugCommands = %s]", forceControl(), spawn(), debugEnabled());
try {
Config conf = ConfigManager.create(Config.class, (it) -> {
it.withConfigurer(
new YamlBukkitConfigurer(),
new SerdesBukkit(),
new SerdesCommons()
);
it.withBindFile(new File(BlockAndSeek.getPluginDataFolder(), "config.yml"));
it.withLogger(BlockAndSeek.getPluginLogger());
it.saveDefaults();
});
config.setRelease(conf);
} catch (Exception e) {
BlockAndSeek.getPluginLogger().severe("Failed to load config.yml: " + e.getMessage());
return false;
}
return true;
} }
private static class Server { private static class ServerSettings extends OkaeriConfig {
private boolean forceControl = false; private boolean forceControl = false;
public Server(boolean forceControl) {
this.forceControl = forceControl;
}
public boolean isForceControl() {
return forceControl;
}
} }
} }

View File

@@ -1,4 +1,4 @@
package hdvtdev.blockAndSeek; package hdvtdev.blockandseek;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryClickEvent;

View File

@@ -1,4 +1,4 @@
package hdvtdev.blockAndSeek; package hdvtdev.blockandseek;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.NamespacedKey; import org.bukkit.NamespacedKey;
@@ -8,21 +8,11 @@ import org.bukkit.scoreboard.Team;
public class Keys { public class Keys {
public static final NamespacedKey SOUND_ITEM = new NamespacedKey(BlockAndSeek.getInstance(), "BlockAndSeekSoundItem"); public static final NamespacedKey SOUND_ITEM = new NamespacedKey(BlockAndSeek.getInstance(), "BlockAndSeekSoundItem");
public static final NamespacedKey DASH_ITEM = new NamespacedKey(BlockAndSeek.getInstance(), "BlockAndSeekDashItem");
public static final NamespacedKey FREEZE_ITEM = new NamespacedKey(BlockAndSeek.getInstance(), "BlockAndSeekFreezeItem"); public static final NamespacedKey FREEZE_ITEM = new NamespacedKey(BlockAndSeek.getInstance(), "BlockAndSeekFreezeItem");
public static final NamespacedKey FACE_CHANGING_ITEM = new NamespacedKey(BlockAndSeek.getInstance(), "BlockAndSeekFaceChangingItem"); public static final NamespacedKey FACE_CHANGING_ITEM = new NamespacedKey(BlockAndSeek.getInstance(), "BlockAndSeekFaceChangingItem");
public static final NamespacedKey MENU_ITEM = new NamespacedKey(BlockAndSeek.getInstance(), "BlockAndSeekMenuItem"); public static final NamespacedKey MENU_ITEM = new NamespacedKey(BlockAndSeek.getInstance(), "BlockAndSeekMenuItem");
public static final NamespacedKey LEAVE_ITEM = new NamespacedKey(BlockAndSeek.getInstance(), "BlockAndSeekLeaveItem"); public static final NamespacedKey LEAVE_ITEM = new NamespacedKey(BlockAndSeek.getInstance(), "BlockAndSeekLeaveItem");
public static final NamespacedKey GAME_PAGE = new NamespacedKey(BlockAndSeek.getInstance(), "BlockAndSeekGamePage");
public static final NamespacedKey FROZEN_PLAYER = new NamespacedKey(BlockAndSeek.getInstance(), "BlockAndSeekFrozenPlayer");
public static final NamespacedKey GAME = new NamespacedKey(BlockAndSeek.getInstance(), "BlockAndSeekGame");
public static final NamespacedKey HIDER = new NamespacedKey(BlockAndSeek.getInstance(), "BlockAndSeekHider");
public static final NamespacedKey SEEKER = new NamespacedKey(BlockAndSeek.getInstance(), "BlockAndSeekSeeker");
public static final NamespacedKey LANG_KEY = new NamespacedKey(BlockAndSeek.getInstance(), "BlockAndSeekLangKey");
public static final Team NO_COLLIDE_TEAM; public static final Team NO_COLLIDE_TEAM;
@@ -32,7 +22,5 @@ public class Keys {
if (team == null) team = scoreboard.registerNewTeam("BlockAndSeekNoCollide"); if (team == null) team = scoreboard.registerNewTeam("BlockAndSeekNoCollide");
team.setOption(Team.Option.COLLISION_RULE, Team.OptionStatus.NEVER); team.setOption(Team.Option.COLLISION_RULE, Team.OptionStatus.NEVER);
NO_COLLIDE_TEAM = team; NO_COLLIDE_TEAM = team;
} }
} }

View File

@@ -1,83 +0,0 @@
package hdvtdev.blockAndSeek;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
public final class Localization {
private Localization() {}
private static final String defaultLanguage = "en_US";
private static final MiniMessage mm = MiniMessage.miniMessage();
private static final AtomicReference<Map<String, Map<String, String>>> translations = new AtomicReference<>();
public static Component get(Player player, String key, String... placeholders) {
return get(player.locale().toString(), key, placeholders);
}
public static Component get(String lang, String key, String... placeholders) {
String raw = translations.get().get(lang).getOrDefault(key, "?" + key + "?");
if (placeholders.length % 2 == 0) {
for (int i = 0; i < placeholders.length; i++) {
raw = raw.replace(placeholders[i], placeholders[++i]);
}
} else BlockAndSeek.getPluginLogger().warning("Wrong amount of placeholders for key: " + key);
return mm.deserialize(raw);
}
public static ItemStack translateItem(Player player, ItemStack itemStack, String key) {
ItemMeta itemMeta = itemStack.getItemMeta();
itemMeta.displayName(get(player, key));
itemStack.setItemMeta(itemMeta);
return itemStack;
}
public static boolean loadTranslations() {
/*
Path path = BlockAndSeek.getPluginDataFolder().toPath().resolve("translations");
if (Files.notExists(path)) {
try {
Files.createDirectories(path);
} catch (IOException e) {
BlockAndSeek.getPluginLogger().severe("Failed to create \"translations\" dir: " + e);
return false;
}
}
File[] files = path.toFile().listFiles();
if (files != null) {
for (File file : files) {
String lang = file.getName().split(".toml")[0];
Map<String, String> translation = new HashMap<>();
for (Map.Entry<String, Object> entry : new Toml().read(file).entrySet()) {
translation.put(entry.getKey(), entry.getValue().toString());
}
translations.get().put(lang, translation);
}
} else {
BlockAndSeek.getPluginLogger().severe("Failed to load translations! " + path + " returned null.");
}
*/
return true;
}
}

View File

@@ -1,20 +1,12 @@
package hdvtdev.blockAndSeek; package hdvtdev.blockandseek;
import me.libraryaddict.disguise.DisguiseAPI; import me.libraryaddict.disguise.DisguiseAPI;
import me.libraryaddict.disguise.disguisetypes.Disguise; import org.bukkit.GameMode;
import me.libraryaddict.disguise.disguisetypes.DisguiseType; import org.bukkit.attribute.Attribute;
import me.libraryaddict.disguise.disguisetypes.MiscDisguise;
import me.libraryaddict.disguise.disguisetypes.watchers.FallingBlockWatcher;
import org.bukkit.Bukkit;
import org.bukkit.block.data.BlockData;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.persistence.PersistentDataContainer;
import org.jetbrains.annotations.Nullable; import org.bukkit.potion.PotionEffect;
import java.io.File;
public final class Utils { public final class Utils {
@@ -26,26 +18,38 @@ public final class Utils {
p.setExp(0.9998f * ((float) level / 100)); p.setExp(0.9998f * ((float) level / 100));
} }
public static boolean isInOneTeam(Player p1, Player p2) { public static void clearPlayer(Player player) {
var c1 = p1.getPersistentDataContainer();
var c2 = p2.getPersistentDataContainer();
return (c1.has(Keys.HIDER) && c2.has(Keys.HIDER)) || (c1.has(Keys.SEEKER) && c2.has(Keys.SEEKER));
}
public static boolean playerInGame(Player player) { DisguiseAPI.undisguiseToAll(player);
return player.getPersistentDataContainer().has(Keys.GAME);
}
public static boolean hasPermsToDamage(Player p1, Player p2) { player.getInventory().clear();
PersistentDataContainer c1 = p1.getPersistentDataContainer(); player.getInventory().setArmorContents(null);
PersistentDataContainer c2 = p2.getPersistentDataContainer(); player.getInventory().setExtraContents(null);
return (c1.has(Keys.SEEKER) || c1.has(Keys.HIDER)) && (c2.has(Keys.SEEKER) || c2.has(Keys.HIDER));
}
player.setGameMode(GameMode.SURVIVAL);
if (player.getAttribute(Attribute.GENERIC_MAX_HEALTH) != null) {
player.getAttribute(Attribute.GENERIC_MAX_HEALTH).setBaseValue(20.0);
}
player.setHealth(20.0);
player.setFoodLevel(20);
player.setSaturation(5.0f);
player.setExhaustion(0f);
player.setInvulnerable(false);
player.setLevel(0);
player.setExp(0f);
player.setFireTicks(0);
player.setFallDistance(0f);
player.setWalkSpeed(0.2f);
player.setFlySpeed(0.1f);
player.setAllowFlight(false);
player.setFlying(false);
player.setGliding(false);
player.setGlowing(false);
player.closeInventory();
public static void clearPlayer(Player p) {
PersistentDataContainer container = p.getPersistentDataContainer();
container.remove(Keys.SEEKER);
container.remove(Keys.HIDER);
} }

View File

@@ -1,7 +1,7 @@
package hdvtdev.blockAndSeek.eventListeners; package hdvtdev.blockandseek.eventListeners;
import hdvtdev.blockAndSeek.Keys; import hdvtdev.blockandseek.Keys;
import hdvtdev.blockAndSeek.Utils; import hdvtdev.blockandseek.Utils;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
@@ -14,25 +14,22 @@ public class EventListener implements Listener {
@EventHandler @EventHandler
public void onDrop(PlayerDropItemEvent event) { public void onDrop(PlayerDropItemEvent event) {
if (event.getPlayer().getPersistentDataContainer().has(Keys.GAME)) event.setCancelled(true);
} }
@EventHandler @EventHandler
public void onFoodLevelChange(FoodLevelChangeEvent event) { public void onFoodLevelChange(FoodLevelChangeEvent event) {
if (event.getEntity() instanceof Player player && Utils.playerInGame(player)) {
event.setCancelled(true);
event.getEntity().setFoodLevel(20);
}
} }
@EventHandler @EventHandler
public void onBlockPlacement(BlockPlaceEvent event) { public void onBlockPlacement(BlockPlaceEvent event) {
if (event.getPlayer().getPersistentDataContainer().has(Keys.GAME)) event.setCancelled(true);
} }
@EventHandler @EventHandler
public void onBlockBreak(BlockBreakEvent event) { public void onBlockBreak(BlockBreakEvent event) {
if (event.getPlayer().getPersistentDataContainer().has(Keys.GAME)) event.setCancelled(true);
} }

View File

@@ -1,9 +1,11 @@
package hdvtdev.blockAndSeek.eventListeners; package hdvtdev.blockandseek.eventListeners;
import hdvtdev.blockAndSeek.Utils; import hdvtdev.blockandseek.Config;
import hdvtdev.blockAndSeek.managers.ItemManager; import hdvtdev.blockandseek.Utils;
import hdvtdev.blockandseek.managers.ItemManager;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent; import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockPlaceEvent; import org.bukkit.event.block.BlockPlaceEvent;
@@ -21,6 +23,9 @@ public class ForceControlEventListener implements Listener {
@EventHandler @EventHandler
public void onPlayerJoin(PlayerJoinEvent event) { public void onPlayerJoin(PlayerJoinEvent event) {
Player player = event.getPlayer(); Player player = event.getPlayer();
player.teleport(Config.spawn());
Utils.clearPlayer(player);
player.setInvulnerable(true);
ItemManager.defaultInventory(player); ItemManager.defaultInventory(player);
} }
@@ -34,17 +39,17 @@ public class ForceControlEventListener implements Listener {
if (event.getSkipReason() != TimeSkipEvent.SkipReason.COMMAND) event.setCancelled(true); if (event.getSkipReason() != TimeSkipEvent.SkipReason.COMMAND) event.setCancelled(true);
} }
@EventHandler @EventHandler(ignoreCancelled = true)
public void onBlockPlacement(BlockPlaceEvent event) { public void onBlockPlacement(BlockPlaceEvent event) {
event.setCancelled(true); event.setCancelled(true);
} }
@EventHandler @EventHandler(ignoreCancelled = true)
public void onBlockBreak(BlockBreakEvent event) { public void onBlockBreak(BlockBreakEvent event) {
event.setCancelled(true); event.setCancelled(true);
} }
@EventHandler @EventHandler(ignoreCancelled = true)
public void onFoodLevelChange(FoodLevelChangeEvent event) { public void onFoodLevelChange(FoodLevelChangeEvent event) {
if (event.getEntity() instanceof Player player) { if (event.getEntity() instanceof Player player) {
event.setCancelled(true); event.setCancelled(true);
@@ -52,22 +57,12 @@ public class ForceControlEventListener implements Listener {
} }
} }
@EventHandler @EventHandler(ignoreCancelled = true)
public void onDrop(PlayerDropItemEvent event) { public void onDrop(PlayerDropItemEvent event) {
event.setCancelled(true); event.setCancelled(true);
} }
@EventHandler @EventHandler(ignoreCancelled = true)
public void onPlayerDamage(EntityDamageByEntityEvent event) {
if (event.getDamager() instanceof Player damager && event.getEntity() instanceof Player victim) {
if (!Utils.hasPermsToDamage(damager, victim)) {
event.setCancelled(true);
}
}
}
@EventHandler
public void onFallDamage(EntityDamageEvent e) { public void onFallDamage(EntityDamageEvent e) {
if (e.getEntity() instanceof Player) { if (e.getEntity() instanceof Player) {
if (e.getCause() == EntityDamageEvent.DamageCause.FALL) { if (e.getCause() == EntityDamageEvent.DamageCause.FALL) {

View File

@@ -1,8 +1,15 @@
package hdvtdev.blockAndSeek.eventListeners; package hdvtdev.blockandseek.eventListeners;
import hdvtdev.blockAndSeek.Keys; import hdvtdev.blockandseek.BlockAndSeek;
import hdvtdev.blockAndSeek.Utils; import hdvtdev.blockandseek.Keys;
import hdvtdev.blockAndSeek.GuiHolder; import hdvtdev.blockandseek.Utils;
import hdvtdev.blockandseek.GuiHolder;
import hdvtdev.blockandseek.managers.PropManager;
import hdvtdev.blockandseek.menus.GamesMenu;
import net.kyori.adventure.text.Component;
import org.bukkit.Material;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
@@ -16,10 +23,17 @@ import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.server.ServerLoadEvent;
import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.scheduler.BukkitRunnable;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import static hdvtdev.blockAndSeek.Utils.isInOneTeam;
public class RequiredEventListener implements Listener { public class RequiredEventListener implements Listener {
@@ -34,9 +48,36 @@ public class RequiredEventListener implements Listener {
} }
//todo remove
private final PropManager propManager = new PropManager();
private final Set<UUID> coolDown = new HashSet<>();
@EventHandler @EventHandler
public void onRightClick(PlayerInteractEvent event) { public void onRightClick(PlayerInteractEvent event) {
if (event.getHand() != EquipmentSlot.HAND) return; if (event.getHand() != EquipmentSlot.HAND) return;
if (event.getAction().isLeftClick()) return;
Player player = event.getPlayer();
UUID uuid = player.getUniqueId();
ItemStack item = event.getItem();
if (item != null) {
ItemMeta meta = item.getItemMeta();
if (meta != null) {
PersistentDataContainer dataContainer = meta.getPersistentDataContainer();
if (dataContainer.has(Keys.MENU_ITEM)) {
new GamesMenu(player);
event.setCancelled(true);
}
}
}
} }
@@ -78,21 +119,7 @@ public class RequiredEventListener implements Listener {
@EventHandler @EventHandler
public void onPlayerDamage(EntityDamageByEntityEvent event) { public void onPlayerDamage(EntityDamageByEntityEvent event) {
if (event.getDamager() instanceof Player damager && event.getEntity() instanceof Player victim) {
if (isInOneTeam(damager, victim)) {
event.setCancelled(true);
} else if (victim.getPersistentDataContainer().has(Keys.SEEKER)) {
event.setDamage(0);
} else if (damager.getPersistentDataContainer().has(Keys.SEEKER)) {
double maxHealth = 20.0;
double currentHealth = damager.getHealth();
if (currentHealth < maxHealth) {
double newHealth = Math.min(currentHealth + event.getDamage(), maxHealth);
damager.setHealth(newHealth);
Utils.setLevelWithBar(damager, (int) Math.round(damager.getHealth() * 5));
}
}
}
} }
} }

View File

@@ -1,35 +1,40 @@
package hdvtdev.blockAndSeek.managers; package hdvtdev.blockandseek.managers;
import hdvtdev.blockAndSeek.objects.BlockAndSeekGame; import hdvtdev.blockandseek.objects.BlockAndSeekGame;
import org.bukkit.Bukkit;
import org.bukkit.WorldCreator; import hdvtdev.blockandseek.objects.BlockAndSeekMap;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.io.File; import java.util.*;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public class GamesManager { public class GamesManager {
private static final ConcurrentHashMap<String, BlockAndSeekGame> games = new ConcurrentHashMap<>(); private static final Map<String, BlockAndSeekGame> games = new HashMap<>();
private static final Set<Player> seekerImmune = ConcurrentHashMap.newKeySet(); private static final Set<UUID> seekerImmune = new HashSet<>();
public static boolean triggerSeekerImmune(Player player) { public static boolean triggerSeekerImmune(UUID uuid) {
return seekerImmune.remove(player); return seekerImmune.remove(uuid);
} }
public static void addSeekerImmune(Player player) { public static void addSeekerImmune(Player player) {
seekerImmune.add(player); seekerImmune.add(player.getUniqueId());
} }
public static Set<String> getAvailableGames() { public static Set<String> getAvailableGames() {
return games.keySet(); return games.keySet();
} }
public static @Nullable String createGame(String name) { public static @Nullable BlockAndSeekGame createGame(String name) {
if (games.containsKey(name)) return name; //TODO use copy or create copy
BlockAndSeekMap map = MapsManager.getMap(name);
if (map != null) {
BlockAndSeekGame game = new BlockAndSeekGame(name, map);
games.put(name, game);
return game;
}
return null; return null;
} }

View File

@@ -1,7 +1,8 @@
package hdvtdev.blockAndSeek.managers; package hdvtdev.blockandseek.managers;
import hdvtdev.blockAndSeek.Keys; import hdvtdev.blockandseek.Keys;
import hdvtdev.blockAndSeek.Localization; import hdvtdev.blockandseek.objects.TranslationKey;
import lombok.Getter;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import org.bukkit.Color; import org.bukkit.Color;
import org.bukkit.Material; import org.bukkit.Material;
@@ -14,10 +15,19 @@ import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.LeatherArmorMeta; import org.bukkit.inventory.meta.LeatherArmorMeta;
import org.bukkit.persistence.PersistentDataType; import org.bukkit.persistence.PersistentDataType;
import static hdvtdev.blockandseek.objects.TranslationKey.*;
public class ItemManager { public class ItemManager {
@Getter
private static final ItemStack freezeItem = new ItemStack(Material.HEART_OF_THE_SEA); private static final ItemStack freezeItem = new ItemStack(Material.HEART_OF_THE_SEA);
@Getter
private static final ItemStack faceChangingItem = new ItemStack(Material.NETHER_STAR); private static final ItemStack faceChangingItem = new ItemStack(Material.NETHER_STAR);
@Getter
private static final ItemStack dashItem = new ItemStack(Material.LAPIS_LAZULI);
@Getter
private static final ItemStack soundItem = new ItemStack(Material.BROWN_DYE);
private static final ItemStack seekerSword = new ItemStack(Material.WOODEN_SWORD); private static final ItemStack seekerSword = new ItemStack(Material.WOODEN_SWORD);
private static final ItemStack seekerHelmet = new ItemStack(Material.LEATHER_HELMET); private static final ItemStack seekerHelmet = new ItemStack(Material.LEATHER_HELMET);
@@ -25,9 +35,13 @@ public class ItemManager {
private static final ItemStack seekerLeggings = new ItemStack(Material.LEATHER_LEGGINGS); private static final ItemStack seekerLeggings = new ItemStack(Material.LEATHER_LEGGINGS);
private static final ItemStack seekerBoots = new ItemStack(Material.LEATHER_BOOTS); private static final ItemStack seekerBoots = new ItemStack(Material.LEATHER_BOOTS);
@Getter
private static final ItemStack menuItem = new ItemStack(Material.COMPASS); private static final ItemStack menuItem = new ItemStack(Material.COMPASS);
@Getter
private static final ItemStack games = new ItemStack(Material.BOOKSHELF); private static final ItemStack games = new ItemStack(Material.BOOKSHELF);
@Getter
private static final ItemStack createGameButton = new ItemStack(Material.SLIME_BALL); private static final ItemStack createGameButton = new ItemStack(Material.SLIME_BALL);
@Getter
private static final ItemStack leaveItem = new ItemStack(Material.RED_DYE); private static final ItemStack leaveItem = new ItemStack(Material.RED_DYE);
@@ -53,8 +67,19 @@ public class ItemManager {
menuMeta.addItemFlags(ItemFlag.HIDE_ENCHANTS); menuMeta.addItemFlags(ItemFlag.HIDE_ENCHANTS);
menuItem.setItemMeta(menuMeta); menuItem.setItemMeta(menuMeta);
ItemMeta dashMeta = dashItem.getItemMeta();
dashMeta.getPersistentDataContainer().set(Keys.DASH_ITEM, PersistentDataType.BOOLEAN, true);
dashMeta.addEnchant(Enchantment.ARROW_INFINITE, 1, true);
dashMeta.addItemFlags(ItemFlag.HIDE_ENCHANTS);
dashItem.setItemMeta(dashMeta);
ItemMeta soundMeta = soundItem.getItemMeta();
soundMeta.getPersistentDataContainer().set(Keys.SOUND_ITEM, PersistentDataType.BOOLEAN, true);
soundMeta.addEnchant(Enchantment.ARROW_INFINITE, 1, true);
soundMeta.addItemFlags(ItemFlag.HIDE_ENCHANTS);
soundItem.setItemMeta(soundMeta);
ItemMeta gamesMeta = games.getItemMeta(); ItemMeta gamesMeta = games.getItemMeta();
gamesMeta.getPersistentDataContainer().set(Keys.GAME_PAGE, PersistentDataType.BOOLEAN, true);
games.setItemMeta(gamesMeta); games.setItemMeta(gamesMeta);
ItemMeta leaveMeta = leaveItem.getItemMeta(); ItemMeta leaveMeta = leaveItem.getItemMeta();
@@ -75,12 +100,12 @@ public class ItemManager {
public static void setSeekerSet(Player seeker) { public static void setSeekerSet(Player seeker) {
PlayerInventory inventory = seeker.getInventory(); PlayerInventory inventory = seeker.getInventory();
inventory.clear(); inventory.clear();
inventory.addItem(Localization.translateItem(seeker, seekerSword, "seeker_sword")); inventory.addItem(TranslationManager.translateItem(seeker, seekerSword, TranslationKey.SEEKER_TEMPLATE));
ItemStack[] armor = new ItemStack[]{ ItemStack[] armor = new ItemStack[]{
Localization.translateItem(seeker, seekerBoots, "seeker_boots"), TranslationManager.translateItem(seeker, seekerBoots, TranslationKey.SEEKER_TEMPLATE),
Localization.translateItem(seeker, seekerLeggings, "seeker_leggings"), TranslationManager.translateItem(seeker, seekerLeggings, TranslationKey.SEEKER_TEMPLATE),
Localization.translateItem(seeker, seekerChestplate, "seeker_chestplate"), TranslationManager.translateItem(seeker, seekerChestplate, TranslationKey.SEEKER_TEMPLATE),
Localization.translateItem(seeker, seekerHelmet, "seeker_helmet") TranslationManager.translateItem(seeker, seekerHelmet, TranslationKey.SEEKER_TEMPLATE)
}; };
inventory.setArmorContents(armor); inventory.setArmorContents(armor);
@@ -94,7 +119,6 @@ public class ItemManager {
meta.setUnbreakable(true); meta.setUnbreakable(true);
meta.addEnchant(Enchantment.PROTECTION_ENVIRONMENTAL, 10, true); meta.addEnchant(Enchantment.PROTECTION_ENVIRONMENTAL, 10, true);
meta.addEnchant(Enchantment.THORNS, 3, true); meta.addEnchant(Enchantment.THORNS, 3, true);
meta.displayName(Component.text("seeker-armor"));
return meta; return meta;
} }
@@ -102,32 +126,25 @@ public class ItemManager {
public static void defaultInventory(Player player) { public static void defaultInventory(Player player) {
PlayerInventory inventory = player.getInventory(); PlayerInventory inventory = player.getInventory();
inventory.clear(); inventory.clear();
inventory.addItem(Localization.translateItem(player, menuItem, "menu_item")); inventory.addItem(TranslationManager.translateItem(player, menuItem, TranslationKey.MENU));
}
public static void hiderInventory(Player player) {
PlayerInventory playerInventory = player.getInventory();
playerInventory.clear();
playerInventory.addItem(
TranslationManager.translateItem(player, freezeItem, FREEZE_ITEM),
TranslationManager.translateItem(player, faceChangingItem, FACE_CHANGING_ITEM),
TranslationManager.translateItem(player, soundItem, SOUND_ITEM),
TranslationManager.translateItem(player, dashItem, DASH_ITEM)
);
} }
public static void giveFaceChangingItem(Player player) { public static void giveFaceChangingItem(Player player) {
player.getInventory().addItem(Localization.translateItem(player, faceChangingItem, "face_changing_item")); player.getInventory().addItem(TranslationManager.translateItem(player, faceChangingItem, TranslationKey.SEEKER_TEMPLATE));
} }
public static ItemStack getLeaveItem() {
return leaveItem;
}
public static ItemStack getCreateGameButton() {
return createGameButton;
}
public static ItemStack getGamesPageItem() {
return games;
}
public static ItemStack getMenuItem() {
return menuItem;
}
public static ItemStack getFreezeItem() {
return freezeItem;
}
} }

View File

@@ -1,54 +1,135 @@
package hdvtdev.blockAndSeek.managers; package hdvtdev.blockandseek.managers;
import eu.okaeri.configs.ConfigManager;
import eu.okaeri.configs.serdes.commons.SerdesCommons;
import eu.okaeri.configs.yaml.bukkit.YamlBukkitConfigurer;
import eu.okaeri.configs.yaml.bukkit.serdes.SerdesBukkit;
import hdvtdev.blockandseek.BlockAndSeek;
import hdvtdev.blockandseek.objects.BlockAndSeekMap;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.WorldCreator;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.incendo.cloud.suggestion.Suggestion; import org.incendo.cloud.suggestion.Suggestion;
import org.incendo.cloud.suggestion.SuggestionProvider; import org.incendo.cloud.suggestion.SuggestionProvider;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.*;
import java.util.List;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
public final class MapsManager { public final class MapsManager {
private MapsManager() { private static final Map<String, BlockAndSeekMap> maps = new HashMap<>();
private static final File MAPS_DIR = new File(BlockAndSeek.getPluginDataFolder(), "maps");
private MapsManager() {}
public static void loadMaps() {
if (!MAPS_DIR.exists()) {
MAPS_DIR.mkdirs();
} }
File[] files = MAPS_DIR.listFiles((dir, name) -> name.endsWith(".yml"));
if (files == null) return;
for (File file : files) {
String name = file.getName().replace(".yml", "");
try {
BlockAndSeekMap map = loadConfig(file);
maps.put(name, map);
} catch (Exception e) {
BlockAndSeek.getPluginLogger().severe("Error loading map " + name + ": " + e.getMessage());
e.printStackTrace();
}
}
BlockAndSeek.getPluginLogger().info("Loaded " + maps.size() + " maps.");
}
public static BlockAndSeekMap getMap(String name) {
return maps.get(name);
}
public static Collection<BlockAndSeekMap> getMaps() {
return maps.values();
}
public static void createMap(String name) {
File mapFile = new File(MAPS_DIR, name + ".yml");
if (mapFile.exists()) {
throw new IllegalArgumentException("Map already exists!");
}
BlockAndSeekMap map = loadConfig(mapFile);
map.setWorld(name);
if (Bukkit.getWorld(name) != null) {
Location spawn = Bukkit.getWorld(name).getSpawnLocation();
map.setLobby(spawn);
map.setSpawn(spawn);
}
map.save();
maps.put(name, map);
}
private static BlockAndSeekMap loadConfig(File file) {
var map = ConfigManager.create(BlockAndSeekMap.class, (it) -> {
it.withConfigurer(
new YamlBukkitConfigurer(),
new SerdesBukkit(),
new SerdesCommons()
);
it.withBindFile(file);
it.withLogger(BlockAndSeek.getPluginLogger());
it.load(true);
});
String worldName = map.getWorld();
if (worldName == null) {
BlockAndSeek.getPluginLogger().warning("Map " + file.getName() + " has no world defined!");
return map;
}
World world = Bukkit.getWorld(worldName);
if (world == null) {
File worldFolder = new File(Bukkit.getWorldContainer(), worldName);
if (worldFolder.exists() && worldFolder.isDirectory()) {
BlockAndSeek.getPluginLogger().info("World '" + worldName + "' is not loaded. Loading now...");
world = Bukkit.createWorld(new WorldCreator(worldName));
}
}
map.getLobby().setWorld(world);
map.getSpawn().setWorld(world);
map.save();
return map;
}
// --- Suggestions ---
public static SuggestionProvider<CommandSender> worldSuggestions = (context, input) -> public static SuggestionProvider<CommandSender> worldSuggestions = (context, input) ->
CompletableFuture.supplyAsync(() -> { CompletableFuture.supplyAsync(() -> {
List<Suggestion> suggestions = new ArrayList<>(); // Лучше брать загруженные миры Bukkit, если карта должна быть в активном мире
// Если нужны папки миров:
File container = Bukkit.getWorldContainer(); File container = Bukkit.getWorldContainer();
File[] files = container.listFiles((dir, name) -> new File(dir, name + "/level.dat").exists());
File[] files = container.listFiles(); if (files == null) return Collections.emptyList();
if (files != null) {
for (File file : files) { return Arrays.stream(files)
if (file.isDirectory() && new File(file, "level.dat").exists()) { .map(File::getName)
suggestions.add(Suggestion.suggestion(file.getName())); .map(Suggestion::suggestion)
} .collect(Collectors.toList());
}
}
return suggestions;
}); });
public static SuggestionProvider<CommandSender> mapSuggestions = (context, input) -> public static SuggestionProvider<CommandSender> mapSuggestions = (context, input) ->
CompletableFuture.supplyAsync(() -> { CompletableFuture.supplyAsync(() -> maps.keySet().stream()
List<Suggestion> suggestions = new ArrayList<>(); .map(Suggestion::suggestion)
.collect(Collectors.toList()));
File container = new File(Bukkit.getPluginsFolder(), "maps");
File[] files = container.listFiles();
if (files != null) {
for (File file : files) {
String name = file.getName();
if (file.isFile() && name.endsWith(".toml")) {
suggestions.add(Suggestion.suggestion(name.replace(".toml", ""))); //baddd
}
}
}
return suggestions;
});
} }

View File

@@ -1,20 +1,23 @@
package hdvtdev.blockAndSeek.managers; package hdvtdev.blockandseek.managers;
import hdvtdev.blockAndSeek.BlockAndSeek; import hdvtdev.blockandseek.BlockAndSeek;
import me.libraryaddict.disguise.DisguiseAPI; import me.libraryaddict.disguise.DisguiseAPI;
import me.libraryaddict.disguise.disguisetypes.Disguise; import me.libraryaddict.disguise.disguisetypes.Disguise;
import me.libraryaddict.disguise.disguisetypes.DisguiseType; import me.libraryaddict.disguise.disguisetypes.DisguiseType;
import me.libraryaddict.disguise.disguisetypes.MiscDisguise; import me.libraryaddict.disguise.disguisetypes.MiscDisguise;
import me.libraryaddict.disguise.disguisetypes.watchers.FallingBlockWatcher; import me.libraryaddict.disguise.disguisetypes.watchers.FallingBlockWatcher;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.GameMode;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.block.Block; import org.bukkit.block.Block;
import org.bukkit.block.data.BlockData; import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.Openable;
import org.bukkit.entity.ArmorStand; import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.EntityType; import org.bukkit.entity.EntityType;
import org.bukkit.entity.FallingBlock;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.scheduler.BukkitRunnable;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.HashMap; import java.util.HashMap;
@@ -24,30 +27,105 @@ import java.util.UUID;
public final class PropManager { public final class PropManager {
private final Map<UUID, PropData> players = new HashMap<>(); private final Map<UUID, PropData> players = new HashMap<>();
private final Map<Location, UUID> props = new HashMap<>();
public void removePlayer(Player player) {
}
public boolean isProped(UUID uuid) {
return props.containsValue(uuid);
}
public void forceUnfreeze(Player player) {
if (players.containsKey(player.getUniqueId())) {
freezeOrUnfreeze(player);
}
}
public void forceUnfreeze(UUID uuid) {
if (players.containsKey(uuid)) {
freezeOrUnfreeze(Bukkit.getPlayer(uuid));
}
}
@Nullable
public UUID getProp(Location location) {
return props.remove(location);
}
//fixme: replace body with freezeOrUnfreeze(Player player) body
/*
public PropState freezeOrUnfreeze(UUID uuid) {
Player player = Bukkit.getPlayer(uuid);
if (player != null) {
return freezeOrUnfreeze(player);
}
return PropState.FAILED;
}
*/
//i dont know how dis works xd
public PropState freezeOrUnfreeze(Player player) { public PropState freezeOrUnfreeze(Player player) {
UUID uuid = player.getUniqueId(); UUID uuid = player.getUniqueId();
PropData propData = players.remove(uuid); PropData propData = players.remove(uuid);
Location location = player.getLocation(); Location location = player.getLocation().toCenterLocation();
Block originalBlock = location.getBlock();
if (propData != null) { if (propData != null) {
ArmorStand armorStand = propData.armorStand;
location.getWorld().setBlockData(location, propData.blockData);
location.setY(Math.floor(location.getY()));
player.setInvulnerable(false);
armorStand.removePassenger(player);
armorStand.remove();
originalBlock.setBlockData(propData.blockData);
return PropState.UNFROZEN;
} else { } else {
BlockData originalBlockData = originalBlock.getBlockData(); Block block = location.getBlock();
BlockData blockData = block.getBlockData();
Location blockLocation = block.getLocation();
Location centerLocation = blockLocation.toCenterLocation();
Location upperBlockLocation = centerLocation.clone();
upperBlockLocation.setY(upperBlockLocation.getY() + 1);
if (!upperBlockLocation.getBlock().isSolid() && !blockLocation.getBlock().isSolid()) {
props.put(blockLocation, uuid);
BlockData disguiseBlockData = getPlayerDisguiseData(player); BlockData disguiseBlockData = getPlayerDisguiseData(player);
location.getWorld().setBlockData(blockLocation, disguiseBlockData == null ? Material.TARGET.createBlockData() : disguiseBlockData);
centerLocation.setY(centerLocation.getY() - 0.85);
player.setInvulnerable(true);
player.setFreezeTicks(40);
ArmorStand armorStand = location.getWorld().spawn(centerLocation, ArmorStand.class, stand -> {
stand.setVisible(false);
stand.setVisible(false);
stand.setCollidable(false);
stand.setGravity(true);
stand.setSmall(true);
stand.setCanMove(false);
stand.addPassenger(player);
stand.setInvulnerable(true);
});
players.put(uuid, new PropData(armorStand, blockData));
return PropState.FROZEN;
}
players.put(uuid, new PropData(null, originalBlockData, null));
} }
return PropState.FAILED; return PropState.FAILED;
} }
@@ -68,7 +146,7 @@ public final class PropManager {
FAILED FAILED
} }
private record PropData(ArmorStand armorStand, BlockData blockData, Disguise disguise) { private record PropData(ArmorStand armorStand, BlockData blockData) {
} }

View File

@@ -1,6 +1,6 @@
package hdvtdev.blockAndSeek.managers; package hdvtdev.blockandseek.managers;
import hdvtdev.blockAndSeek.objects.BlockAndSeekGame; import hdvtdev.blockandseek.objects.BlockAndSeekGame;
import java.util.*; import java.util.*;
@@ -26,6 +26,14 @@ public class StateManager {
stateToPlayers.computeIfAbsent(newState, k -> new HashSet<>()).add(uuid); stateToPlayers.computeIfAbsent(newState, k -> new HashSet<>()).add(uuid);
} }
public Set<UUID> getPlayers() {
return playerToState.keySet();
}
public boolean hasPlayer(UUID uuid) {
return playerToState.containsKey(uuid);
}
public int playerCount() { public int playerCount() {
return playerToState.size(); return playerToState.size();
} }

View File

@@ -0,0 +1,94 @@
package hdvtdev.blockandseek.managers;
import eu.okaeri.configs.ConfigManager;
import eu.okaeri.configs.OkaeriConfig;
import eu.okaeri.configs.annotation.Exclude;
import eu.okaeri.configs.serdes.commons.SerdesCommons;
import eu.okaeri.configs.yaml.bukkit.YamlBukkitConfigurer;
import hdvtdev.blockandseek.BlockAndSeek;
import hdvtdev.blockandseek.objects.Translation;
import hdvtdev.blockandseek.objects.TranslationKey;
import lombok.Getter;
import lombok.Setter;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import java.io.File;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
public final class TranslationManager {
private TranslationManager() {}
public static final String defaultLanguage = "en_US";
public static final MiniMessage mm = MiniMessage.miniMessage();
public static final PlainTextComponentSerializer plaintText = PlainTextComponentSerializer.plainText();
public static final String prefix = "<gradient:#FFAA00:#FFD700><bold>BlockAndSeek</bold></gradient> <dark_gray>»</dark_gray>";
public static final String bracedPrefix = "<gradient:#FFAA00:#FFD700><bold>[BlockAndSeek]</bold></gradient>";
private static final Map<String, EnumMap<TranslationKey, String>> translations = new HashMap<>();
public static Component get(Player player, TranslationKey key, String... placeholders) {
return get(player.locale().toString(), key, placeholders);
}
public static Component get(String lang, TranslationKey key, String... placeholders) {
String raw = translations.getOrDefault(lang, translations.get(defaultLanguage)).getOrDefault(key, "?" + key.toString() + "?");
if (placeholders.length % 2 == 0) {
for (int i = 0; i < placeholders.length; i++) {
raw = raw.replace(placeholders[i], placeholders[++i]);
}
} else BlockAndSeek.getPluginLogger().warning("Wrong amount of placeholders for key: " + key);
return mm.deserialize(raw);
}
public static ItemStack translateItem(Player player, ItemStack itemStack, TranslationKey key, String... placeholders) {
ItemMeta itemMeta = itemStack.getItemMeta();
itemMeta.displayName(get(player, key, placeholders));
itemStack.setItemMeta(itemMeta);
return itemStack;
}
public static void loadLanguages() {
File langDir = new File(BlockAndSeek.getPluginDataFolder(), "languages");
if (!langDir.exists()) langDir.mkdirs();
if (!new File(langDir, "en_US.yml").exists()) BlockAndSeek.saveResource("languages/en_US.yml");
if (!new File(langDir, "ru_RU.yml").exists()) BlockAndSeek.saveResource("languages/ru_RU.yml");
BlockAndSeek.getInstance().saveResource("languages/README.txt", true);
for (File lang : langDir.listFiles()) {
String name = lang.getName();
if (!name.endsWith(".yml")) continue;
Translation config = ConfigManager.create(Translation.class, (it) -> {
it.withConfigurer(new YamlBukkitConfigurer(), new SerdesCommons());
it.withBindFile(new File(langDir, name));
it.load(true);
});
EnumMap<TranslationKey, String> enumMap = new EnumMap<>(TranslationKey.class);
enumMap.putAll(config.getMessages());
translations.put(name.replace(".yml", ""), enumMap);
BlockAndSeek.getPluginLogger().info("Loaded " + name + " translation");
}
}
}

View File

@@ -1,7 +1,7 @@
package hdvtdev.blockAndSeek.managers; package hdvtdev.blockandseek.managers;
import hdvtdev.blockAndSeek.BlockAndSeek; import hdvtdev.blockandseek.BlockAndSeek;
import io.papermc.paper.threadedregions.scheduler.AsyncScheduler; import io.papermc.paper.threadedregions.scheduler.AsyncScheduler;

View File

@@ -1,31 +1,73 @@
package hdvtdev.blockAndSeek.menus; package hdvtdev.blockandseek.menus;
import hdvtdev.blockAndSeek.Localization; import hdvtdev.blockandseek.managers.TranslationManager;
import hdvtdev.blockAndSeek.GuiHolder; import hdvtdev.blockandseek.GuiHolder;
import hdvtdev.blockandseek.managers.GamesManager;
import hdvtdev.blockandseek.managers.ItemManager;
import hdvtdev.blockandseek.objects.TranslationKey;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.Inventory; import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.List;
public class GamesMenu implements GuiHolder { public class GamesMenu implements GuiHolder {
private final Inventory inventory; private final Inventory inventory;
public GamesMenu(Player player) { public GamesMenu(Player player) {
Component title = Localization.get(player, "games_menu"); Component title = TranslationManager.get(player, TranslationKey.GAMES_MENU);
this.inventory = Bukkit.createInventory(this, 54, title); this.inventory = Bukkit.createInventory(this, 54, title);
initInventory(); initInventory();
showInventory(player);
} }
private void initInventory() { private void initInventory() {
inventory.setItem(53, ItemManager.getCreateGameButton());
for (String gameName : GamesManager.getAvailableGames()) {
ItemStack gameItem = new ItemStack(Material.AMETHYST_BLOCK);
ItemMeta itemMeta = gameItem.getItemMeta();
itemMeta.displayName(TranslationManager.get(TranslationManager.defaultLanguage, TranslationKey.GAME, "%name%", gameName));
var game = GamesManager.get(gameName);
itemMeta.lore(List.of(Component.text(game.players())));
gameItem.setItemMeta(itemMeta);
inventory.addItem(gameItem);
}
} }
@Override @Override
public void onClick(InventoryClickEvent event) { public void onClick(InventoryClickEvent event) {
int slot = event.getSlot();
Player player = (Player) event.getWhoClicked();
event.setCancelled(true);
if (slot == 53) {
new MapsMenu(player);
} else {
ItemStack item = event.getCurrentItem();
if (item != null) {
String gameName = TranslationManager.plaintText.serialize(item.displayName()).replaceAll("[\\[\\]]", "");
var game = GamesManager.get(gameName);
if (game != null) {
if (!game.addPlayer(player)) {
player.sendMessage(TranslationManager.get(player, TranslationKey.GAME_IS_FULL, "%game%", gameName));
}
}
event.getInventory().close();
}
}
} }

View File

@@ -0,0 +1,66 @@
package hdvtdev.blockandseek.menus;
import hdvtdev.blockandseek.GuiHolder;
import hdvtdev.blockandseek.managers.GamesManager;
import hdvtdev.blockandseek.managers.ItemManager;
import hdvtdev.blockandseek.managers.MapsManager;
import hdvtdev.blockandseek.managers.TranslationManager;
import hdvtdev.blockandseek.objects.BlockAndSeekMap;
import hdvtdev.blockandseek.objects.TranslationKey;
import net.kyori.adventure.text.Component;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.jetbrains.annotations.NotNull;
import java.util.stream.Collectors;
import static hdvtdev.blockandseek.objects.TranslationKey.UNKNOWN_MAP;
public class MapsMenu implements GuiHolder {
private final Inventory inventory;
public MapsMenu(Player player) {
Component title = TranslationManager.get(player, TranslationKey.GAMES_MENU);
this.inventory = Bukkit.createInventory(this, 54, title);
init();
showInventory(player);
}
private void init() {
for (BlockAndSeekMap map : MapsManager.getMaps()) {
ItemStack gameItem = new ItemStack(Material.MAP);
ItemMeta itemMeta = gameItem.getItemMeta();
itemMeta.displayName(TranslationManager.get(TranslationManager.defaultLanguage, TranslationKey.MAP, "%name%", map.getWorld()));
gameItem.setItemMeta(itemMeta);
inventory.addItem(gameItem);
}
}
@Override
public void onClick(InventoryClickEvent event) {
ItemStack item = event.getCurrentItem();
event.setCancelled(true);
if (item != null) {
Player player = (Player) event.getWhoClicked();
String mapName = TranslationManager.plaintText.serialize(item.displayName()).replaceAll("[\\[\\]]", "");
var map = MapsManager.getMap(mapName);
if (map != null) {
GamesManager.createGame(mapName).addPlayer(player);
} else {
player.sendMessage(TranslationManager.get(player, UNKNOWN_MAP, "%map%", mapName, "%maps%", MapsManager.getMaps().stream().map(BlockAndSeekMap::getWorld).collect(Collectors.joining(", "))));
}
player.closeInventory();
}
}
@Override
public @NotNull Inventory getInventory() {
return inventory;
}
}

View File

@@ -1,33 +1,121 @@
package hdvtdev.blockAndSeek.objects; package hdvtdev.blockandseek.objects;
import hdvtdev.blockandseek.BlockAndSeek;
import hdvtdev.blockandseek.Config;
import hdvtdev.blockandseek.Keys;
import hdvtdev.blockandseek.managers.TranslationManager;
import hdvtdev.blockandseek.Utils;
import hdvtdev.blockandseek.managers.GamesManager;
import hdvtdev.blockandseek.managers.ItemManager;
import hdvtdev.blockandseek.managers.PropManager;
import hdvtdev.blockandseek.managers.StateManager;
import hdvtdev.blockandseek.menus.GamesMenu;
import hdvtdev.blockandseek.roulette.RouletteCreator;
import me.libraryaddict.disguise.DisguiseAPI;
import me.libraryaddict.disguise.disguisetypes.DisguiseType;
import me.libraryaddict.disguise.disguisetypes.MiscDisguise;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.title.Title;
import hdvtdev.blockAndSeek.managers.StateManager;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.World; import org.bukkit.GameMode;
import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockDamageEvent;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.entity.*;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.event.player.PlayerDropItemEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.plugin.Plugin;
import org.bukkit.scheduler.BukkitRunnable;
import java.util.*; import java.util.*;
public class BlockAndSeekGame { public class BlockAndSeekGame {
private final BlockAndSeekMap map; private final BlockAndSeekMap map;
private final World world;
private final StateManager stateManager = new StateManager(); private final StateManager stateManager = new StateManager();
private final PropManager propManager = new PropManager();
private final String name;
public BlockAndSeekGame(BlockAndSeekMap blockAndSeekMap) { private GamePhase phase;
public BlockAndSeekGame(String name, BlockAndSeekMap blockAndSeekMap) {
this.name = name;
this.map = blockAndSeekMap; this.map = blockAndSeekMap;
this.world = Objects.requireNonNull(Bukkit.getWorld(blockAndSeekMap.world())); start();
} }
public boolean addPlayer(Player player) { public boolean addPlayer(Player player) {
if (stateManager.playerCount() < map.maxPlayers()) { if (phase instanceof LobbyPhase && stateManager.playerCount() < map.getMaxPlayers()) {
stateManager.setPlayerState(player.getUniqueId(), PlayerType.NONE); stateManager.setPlayerState(player.getUniqueId(), PlayerType.NONE);
player.teleport(map.getLobby());
player.setInvulnerable(true);
return true; return true;
} }
return false; return false;
} }
public void removePlayer(Player player) { public String players() {
stateManager.removePlayer(player.getUniqueId()); return stateManager.getPlayers().size() + "/" + map.getMaxPlayers();
}
private void start() {
phase = new LobbyPhase();
phase.onEnable();
new BukkitRunnable() {
@Override
public void run() {
if (phase != null) {
phase.tick();
} else this.cancel();
}
}.runTaskTimer(BlockAndSeek.getInstance(), 0, 20);
}
private void endGame() {
phase = null;
GamesManager.remove(name);
}
private void sendToAll(TranslationKey key, String... placeholders) {
for (UUID uuid : stateManager.getPlayers()) {
Player player = Bukkit.getPlayer(uuid);
if (player != null) {
player.sendMessage(TranslationManager.get(player, key, placeholders));
}
}
}
private void broadcastToAll(TranslationKey key, String... placeholders) {
for (UUID uuid : stateManager.getPlayers()) {
Player player = Bukkit.getPlayer(uuid);
if (player != null) {
player.showTitle(Title.title(TranslationManager.get(player, key, placeholders), Component.empty()));
}
}
}
private void actionBarToAll(TranslationKey key, String... placeholders) {
for (UUID uuid : stateManager.getPlayers()) {
Player player = Bukkit.getPlayer(uuid);
if (player != null) {
player.sendActionBar(TranslationManager.get(player, key, placeholders));
}
}
} }
public enum PlayerType { public enum PlayerType {
@@ -37,6 +125,343 @@ public class BlockAndSeekGame {
SPECTATOR SPECTATOR
} }
//fixme: player leave event: count == 0 -> phase = null -> delete game
private class LobbyPhase extends GamePhase {
private static final int WAIT_TIME = 30;
@Override
public void onTick() {
if (map.getMinPlayers() > stateManager.playerCount()) {
super.tick = 0;
} else {
int timeLeft = WAIT_TIME - tick;
if (timeLeft <= 0) {
this.onDisable();
}
if (timeLeft == 30 || timeLeft == 15 || timeLeft == 10 || timeLeft <= 5) {
sendToAll(TranslationKey.TIME_TO_START, "%time%", String.valueOf(timeLeft));
}
}
}
@Override
public void onEnable() {
sendToAll(TranslationKey.WAITING_FOR_PLAYERS);
}
@Override
public void onDisable() {
phase = new StartedGamePhase();
phase.onEnable();
}
}
private class StartedGamePhase extends GamePhase implements Listener {
private long gameDuration = map.getGameDuration().toSeconds();
@Override
public void onTick() {
if (stateManager.getPlayersInState(PlayerType.SEEKER).isEmpty() || gameDuration == 0) {
var props = stateManager.getPlayersInState(PlayerType.PROP);
if (props.size() == 1) {
Player player = Bukkit.getPlayer(props.iterator().next());
if (player != null) {
broadcastToAll(TranslationKey.HIDER_SOLO_WIN, "%player%", player.getName());
} else broadcastToAll(TranslationKey.HIDERS_WIN);
} else broadcastToAll(TranslationKey.HIDERS_WIN);
this.onDisable();
} else if (stateManager.getPlayersInState(PlayerType.PROP).isEmpty()) {
broadcastToAll(TranslationKey.SEEKERS_WIN);
this.onDisable();
} else {
actionBarToAll(TranslationKey.TIME_LEFT, "%time%", String.valueOf(gameDuration));
}
gameDuration--;
}
@Override
public void onEnable() {
Plugin instance = BlockAndSeek.getInstance();
instance.getServer().getPluginManager().registerEvents(this, instance);
selectSeekers(1 + (stateManager.playerCount() / 7));
for (UUID uuid : stateManager.getPlayersInState(PlayerType.NONE)) {
Player player = Bukkit.getPlayer(uuid);
if (player != null) {
stateManager.setPlayerState(uuid, PlayerType.PROP);
ItemManager.hiderInventory(player);
player.teleport(map.getSpawn());
new RouletteCreator(player, map.getBlocks());
player.setInvulnerable(false);
} else BlockAndSeek.getPluginLogger().warning("Player is null. ");
}
}
private void selectSeekers(int count) {
ArrayList<UUID> rawSeekers = new ArrayList<>();
Set<UUID> playerSet = stateManager.getPlayers();
for (UUID uuid : playerSet) {
if (!GamesManager.triggerSeekerImmune(uuid)) rawSeekers.add(uuid);
}
Collections.shuffle(rawSeekers);
var seekers = rawSeekers.subList(0, Math.min(count, playerSet.size()));
for (UUID seeker : seekers) {
Player player = Bukkit.getPlayer(seeker);
if (player != null) {
stateManager.setPlayerState(seeker, PlayerType.SEEKER);
ItemManager.setSeekerSet(player);
Utils.setLevelWithBar(player, 100);
GamesManager.addSeekerImmune(player);
}
}
new BukkitRunnable() {
@Override
public void run() {
for (UUID seeker : seekers) {
Player player = Bukkit.getPlayer(seeker);
if (player != null) {
player.teleport(map.getSpawn());
player.setInvulnerable(false);
}
}
}
}.runTaskLater(BlockAndSeek.getInstance(), map.getSeekerSpawnDelay().toSeconds() * 20);
}
@Override
public void onDisable() {
HandlerList.unregisterAll(this);
phase = new EndedGamePhase();
phase.onEnable();
}
@EventHandler
private void onPlayerLeaveEvent(PlayerQuitEvent event) {
Player player = event.getPlayer();
UUID uuid = player.getUniqueId();
if (stateManager.hasPlayer(uuid)) {
stateManager.removePlayer(uuid);
Utils.clearPlayer(player);
}
}
@EventHandler
private void onItemDrop(PlayerDropItemEvent event) {
if (stateManager.hasPlayer(event.getPlayer().getUniqueId())) {
event.setCancelled(true);
}
}
@EventHandler
private void onPlayerDamage(EntityDamageEvent event) {
if (event.getEntity() instanceof Player player) {
if (stateManager.hasPlayer(player.getUniqueId())) {
if (event.getCause() == EntityDamageEvent.DamageCause.FALL) {
event.setCancelled(true);
}
}
}
}
@EventHandler
private void onPlayerDeath(PlayerDeathEvent event) {
Player player = event.getPlayer();
UUID uuid = player.getUniqueId();
if (stateManager.hasPlayer(uuid)) {
stateManager.setPlayerState(uuid, PlayerType.SPECTATOR);
player.setGameMode(GameMode.SPECTATOR);
event.setCancelled(true);
}
}
@EventHandler
private void onBlockBreak(BlockBreakEvent event) {
Player player = event.getPlayer();
if (stateManager.hasPlayer(player.getUniqueId())) {
event.setCancelled(true);
}
}
@EventHandler
public void onRegainHealth(EntityRegainHealthEvent event) {
if (event.getEntity() instanceof Player player) {
UUID uuid = player.getUniqueId();
PlayerType playerType = stateManager.getState(uuid);
if (playerType == PlayerType.SEEKER) {
event.setCancelled(true);
} else if (playerType == PlayerType.PROP) {
if (!propManager.isProped(uuid)) {
event.setCancelled(true);
}
}
}
}
@EventHandler
public void onEntityDismount(EntityDismountEvent event) {
if (event.getEntity() instanceof Player player && event.getDismounted() instanceof ArmorStand) {
if (stateManager.hasPlayer(player.getUniqueId())) {
propManager.forceUnfreeze(player);
}
}
}
@EventHandler
private void onBlockPlace(BlockPlaceEvent event) {
Player player = event.getPlayer();
if (stateManager.hasPlayer(player.getUniqueId())) {
event.setCancelled(true);
}
}
@EventHandler
private void onPlayerDamageByPlayer(EntityDamageByEntityEvent event) {
if (event.getDamager() instanceof Player damager && event.getEntity() instanceof Player victim) {
PlayerType damagerType = stateManager.getState(damager.getUniqueId());
PlayerType victimType = stateManager.getState(victim.getUniqueId());
if (damagerType == victimType) {
event.setCancelled(true);
} else {
if (damagerType == PlayerType.SEEKER) {
double currentHealth = damager.getHealth();
if (currentHealth < 20) {
double newHealth = Math.min(currentHealth + event.getDamage(), 20);
damager.setHealth(newHealth);
Utils.setLevelWithBar(damager, (int) Math.round(damager.getHealth() * 5));
}
}
}
}
}
@EventHandler
private void onInventoryClose(InventoryCloseEvent event) {
Inventory inventory = event.getInventory();
if (inventory.getHolder() instanceof RouletteCreator rouletteCreator && event.getReason() != InventoryCloseEvent.Reason.PLUGIN) {
Player player = (Player) event.getPlayer();
DisguiseAPI.disguiseToAll(player, new MiscDisguise(DisguiseType.FALLING_BLOCK, rouletteCreator.randomPropItem()));
DisguiseAPI.setActionBarShown(player, false);
}
}
@EventHandler
public void onBlockDamage(BlockDamageEvent event) {
Player player = event.getPlayer();
UUID uuid = player.getUniqueId();
if (PlayerType.SEEKER.equals(stateManager.getState(uuid))) {
UUID prop = propManager.getProp(event.getBlock().getLocation());
if (prop != null) {
propManager.forceUnfreeze(prop);
} else {
player.damage(2);
Utils.setLevelWithBar(player, (int) Math.round(player.getHealth() * 5));
}
}
}
@EventHandler
private void onInventoryClick(InventoryClickEvent event) {
Inventory inventory = event.getInventory();
if (inventory.getHolder() instanceof RouletteCreator) {
ItemStack itemStack = event.getCurrentItem();
int slot = event.getSlot();
if (itemStack != null && (slot == 21 || slot == 23 || slot == 25)) {
Player player = (Player) event.getWhoClicked();
player.closeInventory(InventoryCloseEvent.Reason.PLUGIN);
DisguiseAPI.disguiseToAll(player, new MiscDisguise(DisguiseType.FALLING_BLOCK, itemStack));
DisguiseAPI.setActionBarShown(player, false);
}
event.setCancelled(true);
}
}
private final Set<UUID> coolDown = new HashSet<>();
@EventHandler
public void onRightClick(PlayerInteractEvent event) {
if (event.getHand() != EquipmentSlot.HAND) return;
if (event.getAction().isLeftClick()) return;
Player player = event.getPlayer();
UUID uuid = player.getUniqueId();
ItemStack item = event.getItem();
if (item != null) {
ItemMeta meta = item.getItemMeta();
if (meta != null) {
PersistentDataContainer dataContainer = meta.getPersistentDataContainer();
if (dataContainer.has(Keys.FREEZE_ITEM)) {
if (!coolDown.contains(uuid)) {
player.sendActionBar(Component.text(propManager.freezeOrUnfreeze(event.getPlayer()).toString()));
coolDown.add(uuid);
new BukkitRunnable() {
@Override
public void run() {
coolDown.remove(uuid);
}
}.runTaskLater(BlockAndSeek.getInstance(), 2);
}
}
}
}
}
}
private class EndedGamePhase extends GamePhase {
private final long endTick = map.getDelayBeforeGameEnd().toSeconds();
@Override
public void onTick() {
if (super.tick == endTick) {
this.onDisable();
}
}
@Override
public void onEnable() {
for (UUID uuid : stateManager.getPlayers()) {
Player player = Bukkit.getPlayer(uuid);
if (player != null) {
propManager.forceUnfreeze(player);
player.setInvulnerable(true);
player.setGlowing(true);
}
}
}
@Override
public void onDisable() {
for (UUID uuid : stateManager.getPlayers()) {
Player player = Bukkit.getPlayer(uuid);
if (player != null) {
//propManager.removePlayer(player);
Utils.clearPlayer(player);
player.setInvulnerable(true);
ItemManager.defaultInventory(player);
player.teleport(Config.spawn());
}
}
endGame();
}
}

View File

@@ -1,17 +1,59 @@
package hdvtdev.blockAndSeek.objects; package hdvtdev.blockandseek.objects;
import org.bukkit.Bukkit; import eu.okaeri.configs.OkaeriConfig;
import org.bukkit.World; import eu.okaeri.configs.annotation.Comment;
import eu.okaeri.configs.annotation.CustomKey;
import eu.okaeri.validator.annotation.Min;
public record BlockAndSeekMap(String world, Cords spawn, Cords lobby, import lombok.Getter;
int minPlayers, int maxPlayers, int duration, int seekerSpawnDelay, int delayBeforeEnd) { import lombok.Setter;
public static void prepareWorld(World world) { import org.bukkit.Location;
if (Integer.parseInt(Bukkit.getMinecraftVersion().replaceAll("[^0-9]", "")) >= 1215) { import org.bukkit.Material;
String cmd = "execute in " + world.getKey() + " run gamerule locatorBar true"; import org.bukkit.inventory.ItemStack;
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), cmd);
}
}
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
@Getter
@Setter
public class BlockAndSeekMap extends OkaeriConfig {
@Comment("The name of the world where the game takes place")
private String world;
@Comment("The main spawn point inside the arena")
private Location spawn;
@Comment("The waiting lobby location where players join before the match")
private Location lobby;
@Comment("Minimum number of players required to start the game")
@Min(2)
@CustomKey("min-players")
private int minPlayers = 2;
@Comment("Maximum number of players allowed in the game")
@CustomKey("max-players")
private int maxPlayers = 12;
@Comment("Total duration of the match. Format examples: 10m, 300s, 1h")
@CustomKey("game-duration")
private Duration gameDuration = Duration.ofMinutes(10);
@Comment("How long seekers must wait before they are released")
@CustomKey("seeker-spawn-delay")
private Duration seekerSpawnDelay = Duration.ofSeconds(30);
@Comment("Time to stay in the arena after the game ends (Post-game phase)")
@Comment("Allows players to look around, check positions and chat before being sent to lobby")
@CustomKey("delay-before-game-end")
private Duration delayBeforeGameEnd = Duration.ofSeconds(10);
@Comment("Available blocks for hiders")
private List<PropBlock> blocks = new ArrayList<>(List.of(new PropBlock(new ItemStack(Material.TARGET), Rarity.LEGENDARY)));
} }

View File

@@ -1,4 +1,18 @@
package hdvtdev.blockandseek.objects; package hdvtdev.blockandseek.objects;
public interface GamePhase { import org.jetbrains.annotations.Nullable;
public abstract class GamePhase {
protected int tick = 0;
public final void tick() {
onTick();
tick++;
}
protected abstract void onTick();
protected abstract void onEnable();
protected abstract void onDisable();
} }

View File

@@ -1,9 +1,25 @@
package hdvtdev.blockAndSeek.objects; package hdvtdev.blockandseek.objects;
import eu.okaeri.configs.OkaeriConfig;
import eu.okaeri.configs.annotation.Comment;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
public record PropBlock(ItemStack block, int chance) { public class PropBlock extends OkaeriConfig {
public PropBlock(ItemStack block, Rarity rarity) {
this(block, rarity.toChance()); private ItemStack block;
private Rarity rarity = Rarity.COMMON;
public ItemStack getBlock() {
return block;
} }
public Rarity getRarity() {
return rarity;
}
public PropBlock(ItemStack itemStack, Rarity rarity) {
this.block = itemStack;
this.rarity = rarity;
}
} }

View File

@@ -1,23 +1,20 @@
package hdvtdev.blockAndSeek.objects; package hdvtdev.blockandseek.objects;
public enum Rarity { public enum Rarity {
COMMON(100), // Очень часто (как грязь)
UNCOMMON(50), // Часто (половина от обычного)
RARE(25), // Редко
EPIC(10), // Очень редко
MYTHIC(3), // Почти невозможно
LEGENDARY(1); // Чудо
COMMON,// 38% private final int weight;
UNCOMMON,// 27%
RARE,// 19%
EPIC,// 10%
MYTHIC,// 4%
LEGENDARY; // 2%
public int toChance() { Rarity(int weight) {
return switch (this.ordinal()) { this.weight = weight;
case 1 -> 27;
case 2 -> 19;
case 3 -> 10;
case 4 -> 4;
case 5 -> 2;
default -> 38;
};
} }
public int getChance() {
return weight;
}
} }

View File

@@ -0,0 +1,17 @@
package hdvtdev.blockandseek.objects;
import eu.okaeri.configs.OkaeriConfig;
import eu.okaeri.configs.annotation.Header;
import lombok.Getter;
import java.util.EnumMap;
import java.util.Map;
@Header("Translations lives here")
@Getter
public class Translation extends OkaeriConfig {
private Map<TranslationKey, String> messages = new EnumMap<>(TranslationKey.class);
}

View File

@@ -0,0 +1,40 @@
package hdvtdev.blockandseek.objects;
public enum TranslationKey {
// Misc
UNKNOWN_COMMAND,
SEEKER_TEMPLATE,
// Maps management
UNKNOWN_MAP,
SUCCESSFUL_MAP_CREATION,
// Menus
MENU,
GAMES_MENU,
MAPS_MENU,
// Menus buttons
GAME,
CREATE_GAME,
MAP,
// Game
TIME_LEFT,
TIME_TO_START,
PLAYER_JOINED,
PLAYER_LEFT,
SEEKERS_WIN,
HIDERS_WIN,
HIDER_SOLO_WIN,
ROULETTE,
GAME_IS_FULL,
WAITING_FOR_PLAYERS,
// Items
FREEZE_ITEM,
FACE_CHANGING_ITEM,
SOUND_ITEM,
LEAVE_ITEM,
DASH_ITEM;
}

View File

@@ -1,4 +1,11 @@
package hdvtdev.blockAndSeek.roulette; package hdvtdev.blockandseek.roulette;
import hdvtdev.blockandseek.BlockAndSeek;
import hdvtdev.blockandseek.managers.TranslationManager;
import hdvtdev.blockandseek.objects.PropBlock;
import hdvtdev.blockandseek.objects.TranslationKey;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Sound; import org.bukkit.Sound;
@@ -7,15 +14,14 @@ import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.scheduler.BukkitTask;
import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Random; import java.util.Random;
/*
public final class RouletteCreator implements InventoryHolder { public final class RouletteCreator implements InventoryHolder {
@@ -24,146 +30,116 @@ public final class RouletteCreator implements InventoryHolder {
private static final Random random = new Random(); private static final Random random = new Random();
private final Inventory roulette; private final Inventory roulette;
private Task task; private boolean isRunning = true;
private volatile Boolean closedByPlayer = true;
public RouletteCreator(@NotNull Player player, List<BlockAndSeekMap.Block> blocks) { public RouletteCreator(@NotNull Player player, @NotNull List<PropBlock> blocks) {
roulette = Bukkit.createInventory(this, 45, Localization.getComponent(player, "roulette-title")); roulette = Bukkit.createInventory(this, 45, TranslationManager.get(player, TranslationKey.ROULETTE));
this.createUnoptimizedRoulette(roulette, player, blocks); this.createSmoothRoulette(roulette, player, blocks);
} }
public @NotNull Task getTask() {
return task;
}
public @Nullable Boolean isClosedByPlayer() {
return closedByPlayer;
}
@Override @Override
public @NotNull Inventory getInventory() { public @NotNull Inventory getInventory() {
return roulette; return roulette;
} }
public void closeInventoryBySystem() {
closedByPlayer = false;
roulette.close();
}
public void closeInventory() {
closedByPlayer = null;
roulette.close();
}
public ItemStack randomPropItem() { public ItemStack randomPropItem() {
return roulette.getItem(slots[random.nextInt(0, 15)]); return isRunning ? roulette.getItem(slots[random.nextInt(0, 15)]) : roulette.getItem(midSlots[random.nextInt(0, 3)]);
}
public ItemStack randomMidPropItem() {
return roulette.getItem(midSlots[random.nextInt(0, 3)]);
} }
@ApiStatus.Experimental @ApiStatus.Experimental
private void createUnoptimizedRoulette(Inventory gui, Player player, List<BlockAndSeekMap.Block> blocks) { private void createSmoothRoulette(Inventory gui, Player player, List<PropBlock> blocks) {
player.openInventory(gui);
BukkitTask rouletteTask = new BukkitRunnable() {
new BukkitRunnable() {
final RouletteGenerator rouletteGenerator = new RouletteGenerator(blocks); final RouletteGenerator rouletteGenerator = new RouletteGenerator(blocks);
final List<RouletteList<ItemStack>> rows = List.of( final List<RouletteList<ItemStack>> sources = List.of(
new RouletteList<>(rouletteGenerator.getRandomRow(15)), new RouletteList<>(rouletteGenerator.getRandomRow(15)),
new RouletteList<>(rouletteGenerator.getRandomRow(15)), new RouletteList<>(rouletteGenerator.getRandomRow(15)),
new RouletteList<>(rouletteGenerator.getRandomRow(15)) new RouletteList<>(rouletteGenerator.getRandomRow(15))
); );
final List<ItemStack[]> columns = new ArrayList<>();
final List<ItemStack[]> items;
{ {
List<ItemStack[]> rawItems = new ArrayList<>(); for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) { ItemStack[] col = new ItemStack[5];
ItemStack[] itemStacks = new ItemStack[5]; for (int row = 0; row < 5; row++) {
for (int l = 0; l < 5; l++) { col[row] = sources.get(i).next();
itemStacks[l] = rows.get(j).next();
} }
rawItems.add(j, itemStacks); columns.add(col);
} }
items = rawItems;
} }
final long startTime = System.currentTimeMillis(); final int[] colStartIndices = {3, 5, 7};
double currentSpeed = 0;
int i = 0;
int ticksAlive = 0; // Общее время работы анимации
int ticksUntilNextMove = 0; // Обратный отсчет до следующего кадра
@Override @Override
public void run() { public void run() {
// Безопасность
if (!player.isOnline() || !player.getOpenInventory().getTopInventory().equals(gui)) {
this.cancel();
return;
}
long now = System.currentTimeMillis(); // Увеличиваем общий счетчик жизни таймера
double elapsed = (now - startTime) / 1000.0; ticksAlive++;
if (elapsed >= 5) { // Если время ожидания еще не вышло — ждем
if (ticksUntilNextMove > 0) {
ticksUntilNextMove--;
return;
}
updateRoulette();
if (ticksAlive < 30) { // 0 - 1.5 сек:
ticksUntilNextMove = 1; // Каждые 2 тика (быстро, но не мыло)
} else if (ticksAlive < 50) { // 1.5 - 2.5 сек:
ticksUntilNextMove = 2; // Чуть медленнее (каждые 3 тика)
} else if (ticksAlive < 65) { // 2.5 - 3.2 сек:
ticksUntilNextMove = 3; // Еще медленнее (каждые 4 тика)
} else if (ticksAlive < 75) { // 3.2 - 3.7 сек:
ticksUntilNextMove = 5; // Заметное торможение
} else if (ticksAlive < 85) { // 3.7 - 4.2 сек:
ticksUntilNextMove = 8; // Почти конец
} else if (ticksAlive < 95) { // Последние вздохи
ticksUntilNextMove = 12;
} else if (ticksAlive < 110) {
ticksUntilNextMove = 20; // Финальный медленный шаг
} else {
// КОНЕЦ
isRunning = false;
player.playSound(player.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1f, 2f);
this.cancel(); this.cancel();
} }
double speed;
if (elapsed < 2) speed = 1.0;
else if (elapsed < 2.2) speed = 0.8;
else if (elapsed < 2.4) speed = 0.6;
else if (elapsed < 2.6) speed = 0.5;
else if (elapsed < 2.8) speed = 0.4;
else if (elapsed < 3) speed = 0.33;
else if (elapsed < 3.2) speed = 0.28;
else if (elapsed < 3.5) speed = 0.22;
else if (elapsed < 3.8) speed = 0.15;
else speed = 0.1;
task(speed);
i++;
} }
private void task(double speed) { private void updateRoulette() {
if (currentSpeed >= 1) { player.playSound(player.getLocation(), Sound.UI_BUTTON_CLICK, 0.5f, 1f);
currentSpeed = 0;
for (int j = 0; j < 5; j++) { for (int colIndex = 0; colIndex < 3; colIndex++) {
gui.setItem(3 + j * 9, items.getFirst()[j]); ItemStack[] colItems = columns.get(colIndex);
gui.setItem(5 + j * 9, items.get(1)[j]);
gui.setItem(7 + j * 9, items.get(2)[j]); for (int row = 0; row < 5; row++) {
int slot = colStartIndices[colIndex] + (row * 9);
gui.setItem(slot, colItems[row]);
} }
player.playSound(player.getLocation(), Sound.UI_BUTTON_CLICK, 0.5f, 2f); System.arraycopy(colItems, 0, colItems, 1, 4);
colItems[0] = sources.get(colIndex).next();
for (int j = 4; j >= 1; j--) {
items.getFirst()[j] = items.getFirst()[j - 1];
items.get(1)[j] = items.get(1)[j - 1];
items.get(2)[j] = items.get(2)[j - 1];
}
items.getFirst()[0] = rows.getFirst().next();
items.get(1)[0] = rows.get(1).next();
items.get(2)[0] = rows.get(2).next();
} else currentSpeed += speed;
}
}.runTaskTimer(BlockAndSeek.getInstance(), 0, 1);
task = new Task(rouletteTask, Bukkit.getScheduler().runTaskLater(BlockAndSeek.getInstance(), this::closeInventoryBySystem, 300));
player.openInventory(gui);
}
public record Task(BukkitTask rouletteTask, BukkitTask autoCloseTask) {
public void cancelBoth() {
rouletteTask.cancel();
autoCloseTask.cancel();
} }
} }
}.runTaskTimer(BlockAndSeek.getInstance(), 0L, 1L);
}
}
}
*/

View File

@@ -1,18 +1,22 @@
package hdvtdev.blockAndSeek.roulette; package hdvtdev.blockandseek.roulette;
import com.lewdev.probabilitylib.ProbabilityCollection; import com.lewdev.probabilitylib.ProbabilityCollection;
import hdvtdev.blockandseek.objects.PropBlock;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
/*
public class RouletteGenerator { public class RouletteGenerator {
private final ProbabilityCollection<ItemStack> probabilityCollection = new ProbabilityCollection<>(); private final ProbabilityCollection<ItemStack> probabilityCollection = new ProbabilityCollection<>();
public RouletteGenerator(List<BlockAndSeekMap.Block> blocks) { public RouletteGenerator(List<PropBlock> blocks) {
for (BlockAndSeekMap.Block block : blocks) { for (PropBlock block : blocks) {
probabilityCollection.add(block.block(), block.chance()); probabilityCollection.add(block.getBlock(), block.getRarity().getChance());
} }
} }
@@ -22,7 +26,6 @@ public class RouletteGenerator {
return items; return items;
} }
} }
*/

View File

@@ -1,4 +1,4 @@
package hdvtdev.blockAndSeek.roulette; package hdvtdev.blockandseek.roulette;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;

View File

@@ -0,0 +1,2 @@
Place your translations in language folder. Translation file name must be a language tag and file extension must be .yml.
Use en_US.yml as example.

View File

@@ -0,0 +1,26 @@
messages:
UNKNOWN_COMMAND: "<gold>Unknown command: <red>%command%</red>."
SEEKER_TEMPLATE: "<gradient:#8B0000:#B22222:#DC143C><bold>%template%</bold></gradient>"
UNKNOWN_MAP: "<gold>Unknown map: <red>%map%</red>. Available maps: </gold><dark_aqua><b>%maps%</b></dark_aqua>"
SUCCESSFUL_MAP_CREATION: "<gold>Map <dark_aqua><b>%map%</b></dark_aqua> was <green>successfully</green> created. Use <b>/blockandseek map <dark_aqua>%map%</dark_aqua> to edit</b>"
MENU: <yellow>menu</yellow>
GAMES_MENU: "<gold>games</gold>"
MAPS_MENU: "<gold>maps</gold>"
GAME: "<gradient:#52e555:#20962d>%name%</gradient>"
CREATE_GAME: "<gold><b>Create game</b></gold>"
MAP: "<gradient:#20e3b2:#29ffc6>%name%</gradient>"
TIME_LEFT: "<gold>Time left %time%</gold>"
PLAYER_JOINED: "<gold><b>%player%</b></gold><yellow> joined. <b>%count%/%max%<b>"
PLAYER_LEFT: "<gold><b>%player%</b></gold><yellow> <red>left</red>. <b>%count%/%max%<b>"
SEEKERS_WIN: "<b><red>Seekers won this game!</red></b>"
HIDERS_WIN: "<b><gold>Hiders won this game!</gold></b>"
HIDER_SOLO_WIN: "<b><gold><aqua>%player%</aqua> won this game!</gold></b>"
FREEZE_ITEM: "<gradient:#00c6fb:#005bea>Freezer 3000</gradient>"
SOUND_ITEM: "<gradient:#f3e6ff:#dcb3ff>Sounder 3000</gradient>"
LEAVE_ITEM: "<red><b>Leave</red></b>"
DASH_ITEM: "<gradient:#43cea2:#185a9d>Dash</gradient>"
ROULETTE: "<b><gold>Blocks roulette</b></gold>"
TIME_TO_START: "<gold>Game starts in <b>%time%</b></gold>"
GAME_IS_FULL: "<yellow><red>failed></red> to join %game%. Game is full."
FACE_CHANGING_ITEM: "FACE_CHANGING_ITEM: todo"
WAITING_FOR_PLAYERS: "<gold>Waiting for players</gold>"

View File

@@ -0,0 +1,23 @@
messages:
UNKNOWN_COMMAND: "<gold>Неизвестная команда: <red>%command%</red>."
SEEKER_TEMPLATE: <gradient:#8B0000:#B22222:#DC143C><bold>%template%</bold></gradient>
UNKNOWN_MAP: "<gold>Неизвестная карта: <red>%map%</red>. Доступные карты: </gold><dark_aqua><b>%maps%</b></dark_aqua>"
SUCCESSFUL_MAP_CREATION: <gold>Карта <dark_aqua><b>%map%</b></dark_aqua> была <green>успешно</green> создана. Используйте <b>/blockandseek map <dark_aqua>%map%</dark_aqua>, чтобы редактировать</b>
MENU: <yellow>меню</yellow>
GAMES_MENU: <gold>игры</gold>
MAPS_MENU: <gold>карты</gold>
GAME: <gradient:#52e555:#20962d>%name%</gradient>
CREATE_GAME: <gold><b>Создать игру</b></gold>
MAP: <gradient:#20e3b2:#29ffc6>%name%</gradient>
TIME_LEFT: "<gold>Осталось времени: %time%</gold>"
PLAYER_JOINED: <gold><b>%player%</b></gold><yellow> присоединился. <b>%count%/%max%<b>
PLAYER_LEFT: <gold><b>%player%</b></gold><yellow> <red>вышел</red>. <b>%count%/%max%<b>
SEEKERS_WIN: <b><red>Искатели победили!</red></b>
HIDERS_WIN: <b><gold>Прячущиеся победили!</gold></b>
HIDER_SOLO_WIN: <b><gold><aqua>%player%</aqua> победил!</gold></b>
FREEZE_ITEM: <gradient:#00c6fb:#005bea>Замораживатель 3000</gradient>
SOUND_ITEM: <gradient:#f3e6ff:#dcb3ff>Шумелка 3000</gradient>
LEAVE_ITEM: <red><b>Выйти</red></b>
DASH_ITEM: <gradient:#43cea2:#185a9d>Рывок</gradient>
ROULETTE: <b><gold>Рулетка блоков</b></gold>
TIME_TO_START: <gold>Игра начнется через <b>%time%</b></gold>

View File

@@ -1,7 +1,11 @@
name: BlockAndSeek name: BlockAndSeek
version: '0.0.1-a' version: '0.0.1-a'
main: hdvtdev.blockAndSeek.BlockAndSeek main: hdvtdev.blockandseek.BlockAndSeek
api-version: '1.20' api-version: '1.20'
load: POSTWORLD
depend:
- LibsDisguises
permissions: permissions:
blockandseek.manage: blockandseek.manage: