Merge pull request 'dev' (#1) from dev into main

Reviewed-on: #1
This commit is contained in:
2025-11-30 19:48:06 +03:00
60 changed files with 2685 additions and 2165 deletions

1
.idea/.name generated Normal file
View File

@@ -0,0 +1 @@
BlockAndSeek

11
.idea/compiler.xml generated
View File

@@ -1,6 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<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>
</project>

14
.idea/discord.xml generated Normal file
View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DiscordProjectSettings">
<option name="show" value="PROJECT" />
<option name="description" value="" />
<option name="applicationTheme" value="default" />
<option name="iconsTheme" value="default" />
<option name="button1Title" value="" />
<option name="button1Url" value="" />
<option name="button2Title" value="" />
<option name="button2Url" value="" />
<option name="customApplicationId" value="" />
</component>
</project>

2
.idea/misc.xml generated
View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<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" />
</component>
</project>

1
.idea/modules.xml generated
View File

@@ -4,6 +4,7 @@
<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.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>
</component>
</project>

View File

@@ -1,5 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<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">
<facet type="minecraft" name="Minecraft">
<configuration>

13
.idea/modules/BlockAndSeek.test.iml generated Normal file
View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<module version="4">
<component name="FacetManager">
<facet type="minecraft" name="Minecraft">
<configuration>
<autoDetectTypes>
<platformType>ADVENTURE</platformType>
</autoDetectTypes>
<projectReimportVersion>1</projectReimportVersion>
</configuration>
</facet>
</component>
</module>

View File

@@ -1,6 +1,9 @@
import java.nio.ByteBuffer
import java.nio.ByteOrder
plugins {
id 'java'
id("xyz.jpenilla.run-paper") version "2.3.1"
id "com.gradleup.shadow" version "9.2.2"
}
group = 'hdvtdev'
@@ -8,6 +11,7 @@ version = '0.0.1-a'
repositories {
mavenCentral()
maven { url 'https://storehouse.okaeri.eu/repository/maven-releases/'}
maven { url 'https://repo.md-5.net/content/groups/public/' }
maven { url 'https://jitpack.io' }
maven {
@@ -21,15 +25,24 @@ repositories {
maven { url 'https://libraries.minecraft.net/' }
}
def okaeriConfigsVersion = '5.0.13'
dependencies {
compileOnly("io.papermc.paper:paper-api:1.20.4-R0.1-SNAPSHOT")
implementation group: 'me.libraryaddict.disguises', name: 'libsdisguises', version: '11.0.6'
//TODO implementation 'me.lucko:commodore:2.2'
compileOnly group: 'me.libraryaddict.disguises', name: 'libsdisguises', version: '11.0.6'
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 {
def javaVersion = JavaVersion.toVersion(targetJavaVersion)
sourceCompatibility = javaVersion
@@ -56,14 +69,83 @@ processResources {
}
}
jar {
//destinationDirectory.set(file("/home/hadvart/Documents/Minecraft/Pufferfish 1.20.4/plugins"))
from sourceSets.main.output
from {
configurations.runtimeClasspath.findAll {
it.name.startsWith('Java-Probability-Collection') || it.name.startsWith('commodore') || it.name.startsWith('brigadier')
}.collect { zipTree(it) }
}
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
tasks.shadowJar {
archiveClassifier.set("")
relocate("eu.okaeri", "hdvtdev.blockandseek.libs.okaeri")
relocate("org.incendo", "hdvtdev.blockandseek.libs.cloud")
}
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,84 +0,0 @@
package hdvtdev.blockAndSeek;
import hdvtdev.blockAndSeek.eventListeners.DefaultEventListener;
import hdvtdev.blockAndSeek.eventListeners.EventListener;
import hdvtdev.blockAndSeek.eventListeners.ForceControlEventListener;
import hdvtdev.blockAndSeek.managers.ConfigManager;
import me.libraryaddict.disguise.LibsDisguises;
import org.bukkit.Bukkit;
import org.bukkit.command.PluginCommand;
import org.bukkit.configuration.serialization.ConfigurationSerialization;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Objects;
import java.util.logging.Logger;
public class BlockAndSeek extends JavaPlugin {
private static JavaPlugin javaPlugin;
public static Plugin getInstance() {
return javaPlugin;
}
public static File getPluginDataFolder() {
return javaPlugin.getDataFolder();
}
public static File getServerDataFolder() {
return javaPlugin.getServer().getPluginsFolder().getParentFile();
}
public static InputStream getPluginResource(String resource) {
return javaPlugin.getResource(resource);
}
public static void saveResource(String file) {
javaPlugin.saveResource(file, false);
}
public static Logger getPluginLogger() {
return javaPlugin.getLogger();
}
@Override
public void onEnable() {
javaPlugin = this;
ConfigurationSerialization.registerClass(BlockAndSeekMap.class, "BlockAndSeekMap");
ConfigurationSerialization.registerClass(Config.class, "BlockAndSeekConfig");
LibsDisguises libsDisguises = (LibsDisguises) Bukkit.getPluginManager().getPlugin("LibsDisguises");
if (libsDisguises == null) {
getLogger().severe("LibsDisguises not found! It's required for the plugin to work!");
super.onDisable();
}
try {
ConfigManager.loadAll();
} catch (IOException e) {
getLogger().severe("Failed to save some .yml configs!");
}
PluginCommand command = Objects.requireNonNull(getCommand("blockandseek"));
command.setExecutor(new CommandListener());
PluginManager manager = getServer().getPluginManager();
manager.registerEvents(ConfigManager.getConfig().forceControl() ? new ForceControlEventListener() : new EventListener(), this);
manager.registerEvents(new DefaultEventListener(), this);
}
@Override
public void onDisable() {
}
}

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,280 +0,0 @@
package hdvtdev.blockAndSeek;
//import com.mojang.brigadier.arguments.StringArgumentType;
//import com.mojang.brigadier.builder.LiteralArgumentBuilder;
//import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import hdvtdev.blockAndSeek.managers.*;
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.minimessage.MiniMessage;
import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.attribute.Attribute;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.persistence.PersistentDataType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.*;
public class CommandListener implements TabExecutor {
private static final MiniMessage miniMessage = MiniMessage.miniMessage();
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
int argsLen = args.length;
if (argsLen == 0) {
sender.sendMessage(Localization.get("bs-usage"));
return true;
}
switch (args[0]) {
case "test" -> {
if (sender instanceof Player player) {
GamesManager.createGame("dust2");
GamesManager.get("dust2").addPlayer(player);
}
}
case "locale" -> {
if (sender instanceof Player player) {
player.sendMessage(player.locale().toLanguageTag());
player.sendMessage("eq: " + player.locale().toLanguageTag().equals("en-US"));
}
}
case "menu" -> {
if (sender instanceof Player player) {
GuiManager.Menu.open(player);
}
}
case "morph" -> {
if (sender instanceof Player player && args.length == 2) {
Material material = Material.valueOf(args[1].toUpperCase());
PropManager.addPlayerDisguise(player, material.createBlockData());
DisguiseAPI.disguiseToAll(player, new MiscDisguise(DisguiseType.FALLING_BLOCK, new ItemStack(material)));
}
}
case "damage" -> {
if (sender instanceof Player player && args.length == 2) {
player.damage(Double.parseDouble(args[1]));
}
}
case "health" -> {
if (sender instanceof Player player && args.length == 2) {
player.setHealth(Double.parseDouble(args[1]));
}
}
case "new" -> {
if (sender instanceof Player player) {
new RouletteCreator(player, List.of(
new BlockAndSeekMap.Block(new ItemStack(Material.STONE), 10),
new BlockAndSeekMap.Block(new ItemStack(Material.DIRT), 10),
new BlockAndSeekMap.Block(new ItemStack(Material.LANTERN), 10),
new BlockAndSeekMap.Block(new ItemStack(Material.TARGET), 10),
new BlockAndSeekMap.Block(new ItemStack(Material.SCULK_SHRIEKER), 10),
new BlockAndSeekMap.Block(new ItemStack(Material.FLOWER_POT), 10),
new BlockAndSeekMap.Block(new ItemStack(Material.AZALEA_LEAVES), 10),
new BlockAndSeekMap.Block(new ItemStack(Material.PUMPKIN), 10),
new BlockAndSeekMap.Block(new ItemStack(Material.MELON), 10),
new BlockAndSeekMap.Block(new ItemStack(Material.DIAMOND_BLOCK), 10)
));
}
}
case "foo" -> {
if (sender instanceof Player player) {
ItemStack foo = new ItemStack(Material.BROWN_DYE);
ItemMeta fooMeta = foo.getItemMeta();
fooMeta.displayName(MiniMessage.miniMessage().deserialize("<gradient:#00FF00:#01A368><bold>SoundMaker3000"));
fooMeta.getPersistentDataContainer().set(Keys.SOUND_ITEM, PersistentDataType.BOOLEAN, true);
foo.setItemMeta(fooMeta);
player.getInventory().addItem(foo);
}
}
case "bbb" -> {
if (sender instanceof Player player) {
PropManager.changePropDirection(player);
}
}
case "container" -> {
if (sender instanceof Player player) {
var container = player.getPersistentDataContainer();
player.sendMessage("containers: " + container.getKeys());
}
}
case "def" -> {
if (sender instanceof Player player) {
player.setVisibleByDefault(true);
player.setInvisible(false);
player.setInvulnerable(false);
player.setGameMode(GameMode.SURVIVAL);
player.setGlowing(false);
player.clearActivePotionEffects();
player.setHealth(player.getAttribute(Attribute.GENERIC_MAX_HEALTH).getDefaultValue());
player.setFoodLevel(20);
Utils.setLevelWithBar(player, 0);
DisguiseAPI.undisguiseToAll(player);
PersistentDataContainer dataContainer = player.getPersistentDataContainer();
for (var key : dataContainer.getKeys()) {
dataContainer.remove(key);
}
}
}
case "map" -> {
if (sender.hasPermission("blockandseek.manage")) {
if (argsLen > 1) {
switch (args[1]) {
case "list" -> {
StringBuilder buffer = new StringBuilder(Localization.get("maps-available"));
String listElement = Localization.get("maps-available-element");
Set<String> readyMaps = null; //TODO ConfigManager.getReadyMaps();
for (String map : ConfigManager.getAllMaps()) {
buffer.append("\n").append(listElement.replace("{map}", map).replace("{color-status}",
readyMaps.contains(map) ? "<green>" : "<red>"));
}
sender.sendMessage(miniMessage.deserialize(buffer.toString()));
}
default -> {
if (ConfigManager.getAllMaps().contains(args[1])) {
if (argsLen > 3) {
switch (args[2]) {
case "setduration" -> {
try {
int duration = Integer.parseInt(args[3]);
if (duration > 0) {
sender.sendMessage(args[1]);
var map = MapsManager.getMap(args[1]);
map.setDuration(duration);
MapsManager.saveMap(args[1], map);
sender.sendMessage("duration set");
} else sender.sendMessage("idk");
} catch (NumberFormatException | IOException e) {
sender.sendMessage("idk");
}
}
}
}
}
}
}
} else
sender.sendMessage(Localization.getComponent("not-enough-arguments", "{command}", "/blockandseek map", "{help}", "[MAP NAME | COMMANDS]"));
} else sender.sendMessage(Localization.getComponent("not-enough-permissions"));
}
case "reload" -> {
if (sender.hasPermission("blockandseek.manage")) {
if (argsLen > 1) {
switch (args[1]) {
case "localization" -> {
try {
ConfigManager.load("localization.yml");
sender.sendMessage(Localization.getComponentWithPrefix("successful-reload", "{config}", "localization.yml"));
} catch (IOException e) {
sender.sendMessage(Localization.getComponentWithPrefix("failed-reload", "{config}", "localization.yml", "{e}", e.getMessage()));
}
}
case "maps" -> {
try {
ConfigManager.load(null);
} catch (IOException e) {
throw new RuntimeException(e);
}
sender.sendMessage(Localization.get("maps-reload"));
}
}
} else
sender.sendMessage(Localization.get("not-enough-arguments", "{usage}", "<green>/blockandseek reload <gold>[messages | maps]<white>"));
} else sender.sendMessage(Localization.get("not-enough-permissions"));
}
}
return true;
}
@Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
return !sender.hasPermission("blockandseek.manage") ? List.of() : switch (args.length) {
case 1 -> List.of("info", "map", "reload");
case 2 -> switch (args[0]) {
case "reload" -> List.of("localization", "config");
case "map" -> {
List<String> mapCommands = new ArrayList<>(MapsManager.getAllMaps());
mapCommands.add("create");
mapCommands.add("list");
yield mapCommands;
}
default -> List.of();
};
case 3 -> {
if (MapsManager.getAllMaps().contains(args[1])) {
yield List.of("status", "setspawn", "setlobby", "setduration", "setminplayers", "setmaxplayers", "addblock", "deleteblock");
}
yield List.of();
}
case 4 -> switch (args[2]) {
case "setspawn", "setlobby" -> {
if (sender instanceof Player player) {
Location location = player.getLocation().toCenterLocation();
yield List.of(String.format("%s %s %s", location.getX(), location.getY(), location.getZ()));
} else yield List.of();
}
case "setduration" -> List.of("[<duration>]");
case "setminplayers", "setmaxplayers" -> List.of("[<count>]");
case "addblock", "removeblock" ->
Arrays.stream(Material.values()).map(Objects::toString).map(String::toLowerCase).toList();
default -> List.of();
};
case 5 -> args[2].equalsIgnoreCase("addblock") ? List.of("[<chance>]") : List.of();
default -> List.of();
};
}
/* TODO
public static void registerCompletions(Commodore commodore, PluginCommand command) {
commodore.register(command, LiteralArgumentBuilder.literal("blockandseek")
.then(RequiredArgumentBuilder.argument("idk", StringArgumentType.greedyString()).suggests((a, b) -> {
b.suggest("sometextidk");
return b.buildFuture();
}).executes(o -> {
System.out.println(23238923);
return 1;
})));
}
*/
}

View File

@@ -1,41 +0,0 @@
package hdvtdev.blockAndSeek;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.WorldCreator;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public record Config(@NotNull Location defaultSpawn, boolean forceControl) implements ConfigurationSerializable {
@Override
public @NotNull Map<String, Object> serialize() {
Map<String, Object> map = new HashMap<>();
map.put("default-spawn", defaultSpawn.serialize());
map.put("force-control", Boolean.valueOf(forceControl));
return map;
}
@NotNull
@SuppressWarnings("unchecked")
public static Config deserialize(@NotNull Map<String, Object> args) {
Map<String, Object> location = (Map<String, Object>) args.get("default-spawn");
List<Integer> cords = location == null || location.isEmpty() ? List.of() : (List<Integer>) location.get("location");
World world = location == null || location.isEmpty() ? null : Bukkit.getWorld((String) location.get("world"));
if (world == null) {
world = Bukkit.createWorld(new WorldCreator("world"));
}
if (cords.isEmpty() || cords.size() < 3) {
Location spawn = world.getSpawnLocation();
cords = List.of((int) spawn.getX(), (int) spawn.getY(), (int) spawn.getZ());
}
return new Config(new Location(world, cords.getFirst(), cords.get(1), cords.get(2)), (Boolean) args.get("force-control"));
}
}

View File

@@ -1,109 +0,0 @@
package hdvtdev.blockAndSeek;
import hdvtdev.blockAndSeek.managers.ConfigManager;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import net.kyori.adventure.title.Title;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.jetbrains.annotations.NotNull;
import java.util.Collections;
import java.util.Map;
public class Localization {
private static volatile Map<String, Map<String, String>> localization = Collections.unmodifiableMap(ConfigManager.getLocalization());
private static final MiniMessage miniMessage = MiniMessage.miniMessage();
private static final PlainTextComponentSerializer plainText = PlainTextComponentSerializer.plainText();
private static final Component prefix = miniMessage.deserialize("<gold>[<bold><blue>BlockAndSeek<reset><gold>] ");
private static final Component empty = Component.empty();
public static void update() {
localization = Collections.unmodifiableMap(ConfigManager.getLocalization());
}
public static ItemStack translateItem(@NotNull Player player, @NotNull ItemStack itemStack, @NotNull String... replacements) {
ItemStack translatedItem = itemStack.clone();
ItemMeta itemMeta = translatedItem.getItemMeta();
String key = plainText.serialize(itemMeta.displayName());
itemMeta.displayName(getComponent(player.locale().toLanguageTag(), key, replacements));
translatedItem.setItemMeta(itemMeta);
return translatedItem;
}
public static void sendMessage(@NotNull Iterable<Player> players, boolean addPrefix, @NotNull String key, @NotNull String... replacements) {
for (Player player : players) {
sendMessage(player, addPrefix, key, replacements);
}
}
public static void sendMessage(@NotNull Player player, boolean addPrefix, @NotNull String key, @NotNull String... replacements) {
Component component = getComponent(player.locale().toLanguageTag(), key, replacements);
player.sendMessage(addPrefix ? prefix.append(component) : component);
}
public static void sendActionBar(@NotNull Iterable<Player> players, boolean addPrefix, @NotNull String key, @NotNull String... replacements) {
for (Player player : players) {
sendActionBar(player, addPrefix, key, replacements);
}
}
@Deprecated
public static String get(String a) {
return "";
}
@Deprecated
public static Component getComponent(String a) {
return empty;
}
@Deprecated
public static Component getComponentWithPrefix(String... a) {
return empty;
}
public static void sendActionBar(@NotNull Player player, boolean addPrefix, @NotNull String key, @NotNull String... replacements) {
Component component = getComponent(player.locale().toLanguageTag(), key, replacements);
player.sendActionBar(addPrefix ? prefix.append(component) : component);
}
public static void sendTitle(@NotNull Iterable<Player> players, boolean addPrefix, @NotNull String key, @NotNull String... replacements) {
for (Player player : players) {
sendTitle(player, addPrefix, key, replacements);
}
}
public static void sendTitle(@NotNull Player player, boolean addPrefix, @NotNull String key, @NotNull String... replacements) {
Component component = getComponent(player.locale().toLanguageTag(), key, replacements);
player.showTitle(Title.title(addPrefix ? prefix.append(component) : component, empty));
}
public static @NotNull Component getComponent(@NotNull Player p, @NotNull String key, @NotNull String... replacements) {
return getComponent(p.locale().toLanguageTag(), key, replacements);
}
public static @NotNull Component getComponent(@NotNull String lang, @NotNull String key, @NotNull String... replacements) {
return miniMessage.deserialize(get(lang, key, replacements));
}
public static @NotNull String get(@NotNull String lang, @NotNull String key, @NotNull String... replacements) {
String s = localization.getOrDefault(lang, localization.get("en-US")).get(key);
if (s != null) {
for (int i = 0; i < replacements.length; i += 2) {
s = s.replace(replacements[i], replacements[i + 1]);
}
} else return "Unknown localization: " + key;
return s;
}
}

View File

@@ -1,42 +0,0 @@
package hdvtdev.blockAndSeek;
import hdvtdev.blockAndSeek.managers.PropManager;
import me.libraryaddict.disguise.DisguiseAPI;
import me.libraryaddict.disguise.disguisetypes.DisguiseType;
import me.libraryaddict.disguise.disguisetypes.MiscDisguise;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.persistence.PersistentDataContainer;
public final class Utils {
private Utils() {
}
public static void setLevelWithBar(Player p, int level) {
p.setLevel(level);
p.setExp(0.9998f * ((float) level / 100));
}
public static boolean isInOneTeam(Player p1, Player p2) {
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) {
return player.getPersistentDataContainer().has(Keys.GAME);
}
public static void firstDisguise(Player player, ItemStack prop) {
DisguiseAPI.disguiseToAll(player, new MiscDisguise(DisguiseType.FALLING_BLOCK, prop));
PropManager.addPlayerDisguise(player, prop.getType().createBlockData());
}
public static boolean hasPermsToDamage(Player p1, Player p2) {
PersistentDataContainer c1 = p1.getPersistentDataContainer();
PersistentDataContainer c2 = p2.getPersistentDataContainer();
return (c1.has(Keys.SEEKER) || c1.has(Keys.HIDER)) && (c2.has(Keys.SEEKER) || c2.has(Keys.HIDER));
}
}

View File

@@ -1,279 +0,0 @@
package hdvtdev.blockAndSeek.eventListeners;
import hdvtdev.blockAndSeek.BlockAndSeek;
import hdvtdev.blockAndSeek.Keys;
import hdvtdev.blockAndSeek.Utils;
import hdvtdev.blockAndSeek.managers.GamesManager;
import hdvtdev.blockAndSeek.managers.GuiManager;
import hdvtdev.blockAndSeek.managers.PropManager;
import hdvtdev.blockAndSeek.roulette.RouletteCreator;
import org.bukkit.Bukkit;
import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.block.BlockDamageEvent;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.entity.EntityDismountEvent;
import org.bukkit.event.entity.EntityRegainHealthEvent;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.persistence.PersistentDataType;
import org.bukkit.scheduler.BukkitScheduler;
import org.bukkit.scheduler.BukkitTask;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import static hdvtdev.blockAndSeek.Utils.isInOneTeam;
import static hdvtdev.blockAndSeek.Utils.playerInGame;
public class DefaultEventListener implements Listener {
private static final BukkitScheduler scheduler = Bukkit.getScheduler();
private static final Set<Player> coolDown = ConcurrentHashMap.newKeySet();
@Deprecated(forRemoval = true)
private static final ConcurrentHashMap<Player, BukkitTask> tasks = new ConcurrentHashMap<>();
@Deprecated(forRemoval = true)
public static void createTask(Player player, BukkitTask bukkitTask) {
tasks.put(player, bukkitTask);
}
@Deprecated(forRemoval = true)
public static void stopTask(Player player) {
BukkitTask task = tasks.remove(player);
task.cancel();
}
@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
Player player = event.getPlayer();
if (player.getVehicle() instanceof ArmorStand armorStand) {
PersistentDataContainer armorStandContainer = armorStand.getPersistentDataContainer();
if (armorStandContainer.has(Keys.FROZEN_PLAYER)) {
armorStandContainer.remove(Keys.FROZEN_PLAYER);
armorStand.remove();
}
}
}
@EventHandler
public void onBlockDamage(BlockDamageEvent event) {
Player player = event.getPlayer();
if (player.getPersistentDataContainer().has(Keys.SEEKER)) {
if (PropManager.unfreeze(event.getBlock().getBlockData())) {
event.setCancelled(true);
} else {
player.damage(2);
Utils.setLevelWithBar(player, (int) Math.round(player.getHealth() * 5));
}
}
}
@EventHandler
public void onRightClick(PlayerInteractEvent event) {
if (event.getHand() != EquipmentSlot.HAND) return;
Action action = event.getAction();
Player player = event.getPlayer();
if (action.isRightClick() && !coolDown.contains(player)) {
ItemStack itemInHand = player.getInventory().getItemInMainHand();
ItemMeta meta = itemInHand.getItemMeta();
if (meta != null) {
PersistentDataContainer itemData = meta.getPersistentDataContainer();
if (itemData.has(Keys.FREEZE_ITEM) && playerInGame(player)) {
coolDown.add(player);
scheduler.runTaskLater(BlockAndSeek.getInstance(), () -> coolDown.remove(player), 3L);
PropManager.freeze(player);
event.setCancelled(true);
} else if (itemData.has(Keys.MENU_ITEM)) {
GuiManager.Menu.open(player);
event.setCancelled(true);
} else if (itemData.has(Keys.LEAVE_ITEM)) {
String game = player.getPersistentDataContainer().get(Keys.GAME, PersistentDataType.STRING);
GamesManager.get(game).removePlayer(player);
event.setCancelled(true);
} else if (itemData.has(Keys.FACE_CHANGING_ITEM)) {
PropManager.changePropDirection(player);
event.setCancelled(true);
}
}
}
}
@EventHandler
public void onEntityDismount(EntityDismountEvent event) {
Player player = (Player) event.getEntity();
if (event.getDismounted() instanceof ArmorStand armorStand && armorStand.getPersistentDataContainer().has(Keys.FROZEN_PLAYER)) {
PropManager.freeze(player);
}
}
@EventHandler
public void onPlayerQuit(PlayerQuitEvent event) {
Player player = event.getPlayer();
PersistentDataContainer container = player.getPersistentDataContainer();
String arena = container.get(Keys.GAME, PersistentDataType.STRING);
if (arena != null) {
GamesManager.get(arena).removePlayer(player);
PropManager.removePlayerDisguise(player);
}
}
@EventHandler
public void onPlayerDeath(PlayerDeathEvent event) {
Player player = event.getPlayer();
String game = player.getPersistentDataContainer().get(Keys.GAME, PersistentDataType.STRING);
if (game != null) {
event.deathMessage(null);
event.setDroppedExp(0);
event.getDrops().clear();
scheduler.runTask(BlockAndSeek.getInstance(), () -> GamesManager.get(game).setSpectator(player, player.getKiller()));
//without scheduler strange things are happening idk
}
}
@EventHandler
public void onInventoryClose(InventoryCloseEvent event) {
Player player = (Player) event.getPlayer();
InventoryHolder holder = event.getInventory().getHolder();
if (holder instanceof RouletteCreator rouletteCreator) {
Boolean isClosedByPlayer = rouletteCreator.isClosedByPlayer();
if (isClosedByPlayer != null) {
RouletteCreator.Task task = rouletteCreator.getTask();
if (isClosedByPlayer) {
if (!task.rouletteTask().isCancelled()) {
Utils.firstDisguise(player, rouletteCreator.randomPropItem());
} else Utils.firstDisguise(player, rouletteCreator.randomMidPropItem());
task.cancelBoth();
} else Utils.firstDisguise(player, rouletteCreator.randomMidPropItem());
}
}
}
@EventHandler
public void onInventoryClick(InventoryClickEvent event) {
Player player = (Player) event.getWhoClicked();
Inventory inventory = event.getClickedInventory();
if (inventory != null) {
InventoryHolder holder = inventory.getHolder();
int slot = event.getSlot();
ItemStack item = inventory.getItem(slot);
if (holder instanceof RouletteCreator rouletteCreator) {
RouletteCreator.Task task = rouletteCreator.getTask();
if (task.rouletteTask().isCancelled()) {
if (slot == 21 || slot == 23 || slot == 25) {
task.autoCloseTask().cancel();
Utils.firstDisguise(player, inventory.getItem(slot));
rouletteCreator.closeInventory();
}
event.setCancelled(true);
return;
} else {
if (slot == 36) {
task.cancelBoth();
Utils.firstDisguise(player, rouletteCreator.randomPropItem());
rouletteCreator.closeInventory();
event.setCancelled(true);
return;
}
}
}
if (item != null) {
ItemMeta meta = item.getItemMeta();
if (meta != null) {
PersistentDataContainer itemData = meta.getPersistentDataContainer();
if (itemData.has(Keys.GAME_PAGE)) {
GuiManager.Menu.Games.open(player);
event.setCancelled(true);
} else if (itemData.has(Keys.GAME)) {
String game = itemData.get(Keys.GAME, PersistentDataType.STRING);
if (game != null) {
GamesManager.get(game).addPlayer(player);
event.setCancelled(true);
}
}
}
}
}
//TODO MOVE TO EVENT LISTENER
if (player.getPersistentDataContainer().has(Keys.GAME)) {
event.setCancelled(true);
}
}
@EventHandler
public void onRegainHealth(EntityRegainHealthEvent event) {
if (event.getEntity() instanceof Player player) {
PersistentDataContainer container = player.getPersistentDataContainer();
if (container.has(Keys.SEEKER)) {
event.setCancelled(true);
} else if (container.has(Keys.HIDER)) {
if (!PropManager.isPlayerDisguised(player)) event.setCancelled(true);
}
}
}
@EventHandler
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,134 +0,0 @@
package hdvtdev.blockAndSeek.managers;
import hdvtdev.blockAndSeek.BlockAndSeek;
import hdvtdev.blockAndSeek.BlockAndSeekMap;
import hdvtdev.blockAndSeek.Config;
import hdvtdev.blockAndSeek.Localization;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public class ConfigManager {
private static volatile Map<String, Map<String, String>> localization;
private static volatile Config config;
private static Map<String, BlockAndSeekMap> maps = new ConcurrentHashMap<>();
private static final File mapsFile = new File(BlockAndSeek.getPluginDataFolder(), "maps.yml");
public static Config getConfig() {
return config;
}
public static Set<String> getAllMaps() {
return YamlConfiguration.loadConfiguration(mapsFile).getKeys(false);
}
public static void loadAll() throws IOException {
load("config.yml");
load("localization.yml");
load("maps.yml");
}
public static void load(String file) throws IOException {
File conf = new File(BlockAndSeek.getPluginDataFolder(), file);
YamlConfiguration defaultConfiguration = YamlConfiguration.loadConfiguration(
new InputStreamReader(BlockAndSeek.getPluginResource(file)));
if (!conf.exists()) {
BlockAndSeek.saveResource(file);
/*
switch (file) {
case "config.yml" -> config = defaultConfiguration.getSerializable("config", Config.class);
case "localization.yml" -> {
Map<String, Map<String, String>> confMap = new HashMap<>();
Map<String, String> lang = new HashMap<>();
for (String key : defaultConfiguration.getConfigurationSection("en-US").getKeys(false)) {
lang.put(key, defaultConfiguration.getString(key, "NULL"));
}
confMap.put("en-US", lang);
localization = confMap;
Localization.update();
}
}
*/
}
switch (file) {
case "config.yml" -> loadConfig(conf, defaultConfiguration);
case "localization.yml" -> loadLocalization(conf, defaultConfiguration);
case "maps.yml" -> MapsManager.loadMaps();
}
}
private static void loadConfig(File configurationFile, YamlConfiguration defaultConfiguration) {
YamlConfiguration configuration = YamlConfiguration.loadConfiguration(configurationFile);
configuration.setDefaults(defaultConfiguration);
config = configuration.getSerializable("config", Config.class);
}
private static void loadLocalization(File configurationFile, YamlConfiguration defaultConfiguration) throws IOException {
Map<String, Map<String, String>> confMap = new HashMap<>();
YamlConfiguration configuration = YamlConfiguration.loadConfiguration(configurationFile);
ConfigurationSection defaultSection = defaultConfiguration.getConfigurationSection("en-US");
for (String langKey : configuration.getKeys(false)) {
ConfigurationSection configSection = configuration.getConfigurationSection(langKey);
Map<String, String> langMap = new HashMap<>();
if (configSection != null) {
for (String key : defaultSection.getKeys(false)) {
if (configSection.contains(key)) {
langMap.put(key, configSection.getString(key, defaultSection.getString(key, "NULL")));
} else {
String value = defaultSection.getString(key, "NULL");
configSection.set(key, value);
langMap.put(key, value);
}
}
} else {
BlockAndSeek.getPluginLogger().warning("No any language found in the configuration! Using default en-US.");
for (String key : defaultSection.getKeys(false)) {
langMap.put(key, defaultSection.getString(key, "NULL"));
}
}
confMap.put(langKey, langMap);
}
configuration.save(configurationFile);
localization = confMap;
Localization.update();
}
public static Map<String, Map<String, String>> getLocalization() {
return localization;
}
}

View File

@@ -1,57 +0,0 @@
package hdvtdev.blockAndSeek.managers;
import hdvtdev.blockAndSeek.BlockAndSeek;
import hdvtdev.blockAndSeek.BlockAndSeekGame;
import hdvtdev.blockAndSeek.BlockAndSeekMap;
import org.bukkit.Bukkit;
import org.bukkit.WorldCreator;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public class GamesManager {
private static final ConcurrentHashMap<String, BlockAndSeekGame> games = new ConcurrentHashMap<>();
private static final Set<Player> seekerImmune = ConcurrentHashMap.newKeySet();
public static boolean triggerSeekerImmune(Player player) {
return seekerImmune.remove(player);
}
public static void addSeekerImmune(Player player) {
seekerImmune.add(player);
}
public static Set<String> getAvailableGames() {
return games.keySet();
}
public static @Nullable String createGame(String name) {
if (games.containsKey(name)) return name; //TODO use copy or create copy
if (Bukkit.getWorld(name) == null) {
if (new File(BlockAndSeek.getServerDataFolder(), name).exists()) {
Bukkit.createWorld(new WorldCreator(name));
} else return null;
}
BlockAndSeekMap map = MapsManager.getMap(name);
BlockAndSeekGame game = new BlockAndSeekGame(name, map);
games.put(name, game);
return null;
}
public static void remove(String name) {
games.remove(name);
}
public static BlockAndSeekGame get(String name) {
return games.get(name);
}
}

View File

@@ -1,115 +0,0 @@
package hdvtdev.blockAndSeek.managers;
import hdvtdev.blockAndSeek.BlockAndSeekGame;
import hdvtdev.blockAndSeek.Keys;
import hdvtdev.blockAndSeek.Localization;
import net.kyori.adventure.text.Component;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.persistence.PersistentDataType;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class GuiManager {
private static final ItemStack filler1 = new ItemStack(Material.ORANGE_STAINED_GLASS_PANE);
private static final ItemStack filler2 = new ItemStack(Material.BLUE_STAINED_GLASS_PANE);
private static final ItemStack filler3 = new ItemStack(Material.GREEN_STAINED_GLASS_PANE);
private GuiManager() {
}
public static void fill(Inventory inventory) {
int size = inventory.getSize();
for (int i = 0; i < size; i++) {
inventory.setItem(i, i % 2 == 0 ? filler1 : filler2);
}
}
public static void fillAlt(Inventory inventory) {
int size = inventory.getSize();
for (int i = 0; i < size; i++) {
inventory.setItem(i, i % 2 == 0 ? filler3 : filler2);
}
}
public static class Menu implements InventoryHolder {
public static final Menu instance = new Menu();
private Menu() {
}
public static void open(Player player) {
Inventory inventory = Bukkit.createInventory(instance, 27, Localization.getComponent(player, "menu-item"));
inventory.setItem(13, Localization.translateItem(player, ItemManager.getGamesPageItem()));
player.openInventory(inventory);
}
@Override
public @NotNull Inventory getInventory() {
return Bukkit.createInventory(instance, 27, Component.text("raw"));
}
public static class Games implements InventoryHolder {
private static final ItemStack defaultGameItem = new ItemStack(Material.CLOCK);
private Games() {
}
public static void open(Player player) {
Inventory gamesMenu = Bukkit.createInventory(new Games(), 45, Localization.getComponent(player, "games-page-item"));
for (String game : GamesManager.getAvailableGames()) {
BlockAndSeekGame blockAndSeekGame = GamesManager.get(game);
ItemStack gameItem = defaultGameItem.clone();
ItemMeta meta = gameItem.getItemMeta();
meta.getPersistentDataContainer().set(Keys.GAME, PersistentDataType.STRING, game);
meta.displayName(Component.text("game-name"));
meta.lore(List.of(Localization.getComponent(
player,
"game-player-count",
"{players}", String.valueOf(blockAndSeekGame.playerCount()),
"{max-players}", String.valueOf(blockAndSeekGame.maxPlayers())
)));
gameItem.setItemMeta(meta);
gamesMenu.addItem(Localization.translateItem(player, gameItem, "{name}", game));
}
if (player.hasPermission("blockandseek.manage")) {
gamesMenu.setItem(44, ItemManager.getCreateGameButton());
}
player.openInventory(gamesMenu);
}
@Override
public @NotNull Inventory getInventory() {
return Bukkit.createInventory(null, 9);
}
public static abstract class Maps implements InventoryHolder {
}
}
}
}

View File

@@ -1,53 +0,0 @@
package hdvtdev.blockAndSeek.managers;
import hdvtdev.blockAndSeek.BlockAndSeek;
import hdvtdev.blockAndSeek.BlockAndSeekMap;
import org.bukkit.configuration.file.YamlConfiguration;
import java.io.File;
import java.io.IOException;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public class MapsManager {
private static volatile Map<String, BlockAndSeekMap> allMaps = new ConcurrentHashMap<>();
private static final Map<String, BlockAndSeekMap> readyMaps = new ConcurrentHashMap<>();
private static final File mapsFile = new File(BlockAndSeek.getPluginDataFolder(), "maps.yml");
public static Set<String> getAllMaps() {
return allMaps.keySet();
}
public static BlockAndSeekMap getMap(String name) {
return allMaps.get(name);
}
public static void saveMap(String name, BlockAndSeekMap map) throws IOException {
var conf = YamlConfiguration.loadConfiguration(mapsFile);
if (conf.contains(name)) conf.set(name, null);
conf.set(name, map);
conf.save(mapsFile);
}
public static void loadMaps() {
Map<String, BlockAndSeekMap> confMap = new ConcurrentHashMap<>();
YamlConfiguration configuration = YamlConfiguration.loadConfiguration(mapsFile);
for (String map : configuration.getKeys(false)) {
BlockAndSeekMap blockAndSeekMap = configuration.getSerializable(map, BlockAndSeekMap.class, BlockAndSeekMap.defaultMap());
if (blockAndSeekMap.isReady()) readyMaps.put(map, blockAndSeekMap);
confMap.put(map, blockAndSeekMap);
}
allMaps = confMap;
}
}

View File

@@ -1,159 +0,0 @@
package hdvtdev.blockAndSeek.managers;
import hdvtdev.blockAndSeek.Keys;
import me.libraryaddict.disguise.DisguiseAPI;
import me.libraryaddict.disguise.disguisetypes.Disguise;
import me.libraryaddict.disguise.disguisetypes.DisguiseType;
import me.libraryaddict.disguise.disguisetypes.MiscDisguise;
import org.bukkit.Location;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.Directional;
import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.Player;
import org.bukkit.persistence.PersistentDataType;
import org.bukkit.util.Vector;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class PropManager {
private static final Map<Player, FreezeData> frozenPlayers = new ConcurrentHashMap<>();
private static final Map<Player, BlockData> playerDisguise = new ConcurrentHashMap<>();
private static final Map<BlockData, Player> disguisePlayer = new ConcurrentHashMap<>();
private static final Disguise hideDisguise = new MiscDisguise(DisguiseType.LLAMA_SPIT);
private static final Vector zeroVelocity = new Vector(0, 0, 0);
public static boolean isPlayerDisguised(Player player) {
return frozenPlayers.containsKey(player);
}
public static void addPlayerDisguise(Player player, BlockData blockData) {
disguisePlayer.put(blockData, player);
playerDisguise.put(player, blockData);
}
public static void removePlayerDisguise(Player player) {
disguisePlayer.remove(playerDisguise.remove(player));
}
public static void changePropDirection(Player player) {
if (frozenPlayers.containsKey(player)) {
Block block = player.getLocation().getBlock();
BlockData blockData = block.getBlockData();
if (blockData instanceof Directional directional) {
BlockFace face = switch (directional.getFacing()) {
case NORTH -> BlockFace.EAST;
case EAST -> BlockFace.SOUTH;
case SOUTH -> BlockFace.WEST;
case WEST -> directional.getFaces().contains(BlockFace.UP) ? BlockFace.UP : BlockFace.NORTH;
case UP -> BlockFace.DOWN;
case DOWN -> BlockFace.NORTH;
default -> null;
};
if (face != null && directional.getFaces().contains(face)) {
directional.setFacing(face);
block.setBlockData(blockData);
}
}
}
}
public static void unfreezeIfFrozen(Player player) {
Location location = player.getLocation();
FreezeData data = frozenPlayers.remove(player);
if (data != null) {
unfreeze(player, location, data);
}
}
public static boolean unfreeze(BlockData blockData) {
Player player = disguisePlayer.get(blockData);
if (player != null) {
freeze(player);
return true;
} else return false;
}
private static void unfreeze(Player player, Location location, FreezeData data) {
Location blockLocation = location.getBlock().getLocation();
location.getWorld().setBlockData(blockLocation, data.blockData);
player.getPersistentDataContainer().remove(Keys.FROZEN_PLAYER);
player.setFreezeTicks(0);
data.armorStand.remove();
player.setInvulnerable(false);
Keys.NO_COLLIDE_TEAM.removeEntry(player.getName());
if (data.disguise != null) DisguiseAPI.disguiseToAll(player, data.disguise);
}
public static boolean freeze(Player player) {
Location location = player.getLocation();
FreezeData data = frozenPlayers.remove(player);
if (data != null) {
unfreeze(player, location, data);
} else {
Block block = location.getBlock();
BlockData blockData = block.getBlockData();
Location blockLocation = block.getLocation();
Location centerLocation = blockLocation.toCenterLocation();
Location upperBlockLocation = centerLocation.clone();
upperBlockLocation.setY(upperBlockLocation.getY() + 0.25);
if (!upperBlockLocation.getBlock().isSolid() && !blockLocation.getBlock().isSolid()) {
location.getWorld().setBlockData(blockLocation, playerDisguise.get(player));
centerLocation.setY(centerLocation.getY() - 0.85);
player.setVelocity(zeroVelocity);
player.setInvulnerable(true);
player.getPersistentDataContainer().set(Keys.FROZEN_PLAYER, PersistentDataType.BOOLEAN, true);
Keys.NO_COLLIDE_TEAM.addEntry(player.getName());
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.getPersistentDataContainer().set(Keys.FROZEN_PLAYER, PersistentDataType.BOOLEAN, true);
stand.setInvulnerable(true);
});
Disguise disguise = DisguiseAPI.getDisguise(player);
DisguiseAPI.disguiseToAll(player, hideDisguise);
player.setFreezeTicks(40);
frozenPlayers.put(player, new FreezeData(armorStand, blockData, disguise));
} else return false;
}
return true;
}
private record FreezeData(ArmorStand armorStand, BlockData blockData, Disguise disguise) {
}
}

View File

@@ -1,169 +0,0 @@
package hdvtdev.blockAndSeek.roulette;
import hdvtdev.blockAndSeek.BlockAndSeek;
import hdvtdev.blockAndSeek.BlockAndSeekMap;
import hdvtdev.blockAndSeek.Localization;
import org.bukkit.Bukkit;
import org.bukkit.Sound;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.scheduler.BukkitTask;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public final class RouletteCreator implements InventoryHolder {
private static final int[] slots = {3, 5, 7, 12, 14, 16, 21, 23, 25, 30, 32, 34, 39, 41, 43};
private static final int[] midSlots = {21, 23, 25};
private static final Random random = new Random();
private final Inventory roulette;
private Task task;
private volatile Boolean closedByPlayer = true;
public RouletteCreator(@NotNull Player player, List<BlockAndSeekMap.Block> blocks) {
roulette = Bukkit.createInventory(this, 45, Localization.getComponent(player, "roulette-title"));
this.createUnoptimizedRoulette(roulette, player, blocks);
}
public @NotNull Task getTask() {
return task;
}
public @Nullable Boolean isClosedByPlayer() {
return closedByPlayer;
}
@Override
public @NotNull Inventory getInventory() {
return roulette;
}
public void closeInventoryBySystem() {
closedByPlayer = false;
roulette.close();
}
public void closeInventory() {
closedByPlayer = null;
roulette.close();
}
public ItemStack randomPropItem() {
return roulette.getItem(slots[random.nextInt(0, 15)]);
}
public ItemStack randomMidPropItem() {
return roulette.getItem(midSlots[random.nextInt(0, 3)]);
}
@ApiStatus.Experimental
private void createUnoptimizedRoulette(Inventory gui, Player player, List<BlockAndSeekMap.Block> blocks) {
BukkitTask rouletteTask = new BukkitRunnable() {
final RouletteGenerator rouletteGenerator = new RouletteGenerator(blocks);
final List<RouletteList<ItemStack>> rows = List.of(
new RouletteList<>(rouletteGenerator.getRandomRow(15)),
new RouletteList<>(rouletteGenerator.getRandomRow(15)),
new RouletteList<>(rouletteGenerator.getRandomRow(15))
);
final List<ItemStack[]> items;
{
List<ItemStack[]> rawItems = new ArrayList<>();
for (int j = 0; j < 3; j++) {
ItemStack[] itemStacks = new ItemStack[5];
for (int l = 0; l < 5; l++) {
itemStacks[l] = rows.get(j).next();
}
rawItems.add(j, itemStacks);
}
items = rawItems;
}
final long startTime = System.currentTimeMillis();
double currentSpeed = 0;
int i = 0;
@Override
public void run() {
long now = System.currentTimeMillis();
double elapsed = (now - startTime) / 1000.0;
if (elapsed >= 5) {
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) {
if (currentSpeed >= 1) {
currentSpeed = 0;
for (int j = 0; j < 5; j++) {
gui.setItem(3 + j * 9, items.getFirst()[j]);
gui.setItem(5 + j * 9, items.get(1)[j]);
gui.setItem(7 + j * 9, items.get(2)[j]);
}
player.playSound(player.getLocation(), Sound.UI_BUTTON_CLICK, 0.5f, 2f);
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();
}
}
}

View File

@@ -0,0 +1,362 @@
package hdvtdev.blockandseek;
import hdvtdev.blockandseek.eventListeners.RequiredEventListener;
import hdvtdev.blockandseek.eventListeners.EventListener;
import hdvtdev.blockandseek.eventListeners.ForceControlEventListener;
import hdvtdev.blockandseek.managers.*;
import hdvtdev.blockandseek.objects.*;
import hdvtdev.blockandseek.roulette.RouletteCreator;
import me.libraryaddict.disguise.LibsDisguises;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.bukkit.*;
import org.bukkit.command.CommandSender;
import org.bukkit.command.PluginCommand;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;
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.bukkit.CloudBukkitCapabilities;
import org.incendo.cloud.bukkit.parser.location.LocationParser;
import org.incendo.cloud.execution.ExecutionCoordinator;
import org.incendo.cloud.paper.LegacyPaperCommandManager;
import org.incendo.cloud.parser.standard.StringParser;
import java.io.File;
import java.util.*;
import java.util.logging.Logger;
import java.util.stream.Collectors;
public class BlockAndSeek extends JavaPlugin {
private static JavaPlugin javaPlugin;
private static LegacyPaperCommandManager<CommandSender> commandManager;
private static final String perm = "blockandseek.manage";
public static Plugin getInstance() {
return javaPlugin;
}
public static File getPluginDataFolder() {
return javaPlugin.getDataFolder();
}
public static File getServerDataFolder() {
return javaPlugin.getServer().getPluginsFolder().getParentFile();
}
public static void saveResource(String file) {
javaPlugin.saveResource(file, false);
}
public static Logger getPluginLogger() {
return javaPlugin.getLogger();
}
@Override
public void onEnable() {
javaPlugin = this;
LibsDisguises libsDisguises = (LibsDisguises) Bukkit.getPluginManager().getPlugin("LibsDisguises");
if (libsDisguises == null) {
getLogger().severe("LibsDisguises not found! It's required for the plugin to work!");
super.onDisable();
}
this.init();
PluginCommand command = Objects.requireNonNull(getCommand("blockandseek"));
PluginManager manager = getServer().getPluginManager();
boolean forceControl = Config.forceControl();
if (forceControl) getLogger().info("Using force control");
manager.registerEvents(forceControl ? new ForceControlEventListener() : new EventListener(), this);
manager.registerEvents(new RequiredEventListener(), this);
}
private void init() {
try {
File dataFolder = getDataFolder();
if (!dataFolder.exists()) {
dataFolder.mkdirs();
}
File mapsFolder = new File(dataFolder, "maps");
if (!mapsFolder.exists()) {
mapsFolder.mkdirs();
}
File langsFolder = new File(dataFolder, "languages");
if (!langsFolder.exists()) {
langsFolder.mkdirs();
}
File defaultLangFile = new File(langsFolder, "en_US.yml");
if (!defaultLangFile.exists()) {
saveResource("languages/en_US.yml", false);
}
MapsManager.loadMaps();
TranslationManager.loadLanguages();
commandManager = LegacyPaperCommandManager.createNative(
this,
ExecutionCoordinator.simpleCoordinator()
);
if (commandManager.hasCapability(CloudBukkitCapabilities.NATIVE_BRIGADIER)) {
try {
commandManager.registerBrigadier();
} catch (IllegalStateException ignored) {
}
}
this.registerCommands();
} catch (Exception e) {
getLogger().severe("Cloud err: " + e.getMessage());
e.printStackTrace();
}
}
private final PropManager propManager = new PropManager();
private void registerCommands() {
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
.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)
.required("map_name", StringParser.stringParser(), MapsManager.mapSuggestions)
.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
.literal("createMap")
.permission(perm)
.required("map_name", StringParser.stringParser(), MapsManager.worldSuggestions)
.handler(context -> {
String name = context.get("map_name");
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

@@ -0,0 +1,266 @@
package hdvtdev.blockandseek;
import hdvtdev.blockandseek.objects.PropBlock;
import org.bukkit.*;
import org.bukkit.block.Banner;
import org.bukkit.block.Comparator;
import org.bukkit.block.data.*;
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.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.Consumer;
@ApiStatus.Experimental
public class BlocksGenerator {
private static final Set<Material> BLACKLIST = EnumSet.noneOf(Material.class);
private static final Map<String, List<PropBlock>> cached = new HashMap<>();
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;
}
// 3. Дополнительная защита: блок должен быть твердым
// (Это отсечет то, что мы могли случайно забыть в списке, типа нитки/String)
return type.isSolid();
}
public static void getSortedBlockStats(Location center, int chunkRadius, Consumer<List<Map.Entry<Material, Long>>> callback) {
World world = center.getWorld();
if (world == null) return;
List<ChunkSnapshot> snapshots = new ArrayList<>();
int centerX = center.getBlockX() >> 4;
int centerZ = center.getBlockZ() >> 4;
for (int x = centerX - chunkRadius; x <= centerX + chunkRadius; x++) {
for (int z = centerZ - chunkRadius; z <= centerZ + chunkRadius; z++) {
if (world.isChunkLoaded(x, z)) {
Chunk chunk = world.getChunkAt(x, z);
snapshots.add(chunk.getChunkSnapshot(false, false, false));
}
}
}
new BukkitRunnable() {
@Override
public void run() {
Map<Material, Long> counts = new HashMap<>();
int minH = world.getMinHeight();
int maxH = world.getMaxHeight();
for (ChunkSnapshot snap : snapshots) {
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
for (int y = minH; y < maxH; y++) {
BlockData data = snap.getBlockData(x, y, z);
Material type = data.getMaterial();
if (isSupported(type)) {
counts.put(type, counts.getOrDefault(type, 0L) + 1);
}
}
}
}
}
List<Map.Entry<Material, Long>> sortedList = new ArrayList<>(counts.entrySet());
sortedList.sort(Map.Entry.comparingByValue());
new BukkitRunnable() {
@Override
public void run() {
callback.accept(sortedList);
}
}.runTask(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

@@ -0,0 +1,58 @@
package hdvtdev.blockandseek;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
public class CommandBuilder {
private final List<Node> forest = new ArrayList<>();
public Node addNode(String name) {
Node node = new Node(name);
forest.add(node);
return node;
}
@Nullable
public String tryGet(String fullPath) {
return null;
}
public static void printTree(Node node, String indent) {
System.out.println(indent + "- " + node.getName());
for (Node child : node.getChildren()) {
printTree(child, indent + " ");
}
}
public static class Node {
private final String name;
private final List<Node> children;
public Node(String name) {
this.name = name;
this.children = new ArrayList<>();
}
public void addChild(Node child) {
this.children.add(child);
}
public List<Node> getChildren() {
return children;
}
public String getName() {
return name;
}
@Override
public String toString() {
return name;
}
}
}

View File

@@ -0,0 +1,74 @@
package hdvtdev.blockandseek;
import eu.okaeri.configs.ConfigManager;
import eu.okaeri.configs.OkaeriConfig;
import eu.okaeri.configs.annotation.Comment;
import eu.okaeri.configs.annotation.Exclude;
import eu.okaeri.configs.serdes.commons.SerdesCommons;
import eu.okaeri.configs.yaml.bukkit.YamlBukkitConfigurer;
import eu.okaeri.configs.yaml.bukkit.serdes.SerdesBukkit;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.WorldCreator;
import java.io.File;
import java.util.Objects;
public class Config extends OkaeriConfig {
@Exclude
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.")
private ServerSettings serverSettings = new ServerSettings();
@Comment("Spawn location. Useless if the Server.forceControl is false.")
private Location spawn = Objects.requireNonNull(Bukkit.createWorld(new WorldCreator("world"))).getSpawnLocation();
@Comment("Show hidden BlockAndSeek commands.")
private boolean enableDebugCommands = false;
public static boolean debugEnabled() {
return config.enableDebugCommands;
}
public static boolean forceControl() {
return config.serverSettings.forceControl;
}
public static Location spawn() {
return config.spawn;
}
public static String toStaticString() {
return String.format("BlockAndSeekConfig[forceControl = %s, spawn = %s, enableDebugCommands = %s]", forceControl(), spawn(), debugEnabled());
}
private static class ServerSettings extends OkaeriConfig {
private boolean forceControl = false;
}
}

View File

@@ -0,0 +1,12 @@
package hdvtdev.blockandseek;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.InventoryHolder;
public interface GuiHolder extends InventoryHolder {
void onClick(InventoryClickEvent event);
default void showInventory(Player player) {
player.openInventory(getInventory());
}
}

View File

@@ -1,4 +1,4 @@
package hdvtdev.blockAndSeek;
package hdvtdev.blockandseek;
import org.bukkit.Bukkit;
import org.bukkit.NamespacedKey;
@@ -8,17 +8,11 @@ import org.bukkit.scoreboard.Team;
public class Keys {
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 FACE_CHANGING_ITEM = new NamespacedKey(BlockAndSeek.getInstance(), "BlockAndSeekFaceChangingItem");
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 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 Team NO_COLLIDE_TEAM;
@@ -28,7 +22,5 @@ public class Keys {
if (team == null) team = scoreboard.registerNewTeam("BlockAndSeekNoCollide");
team.setOption(Team.Option.COLLISION_RULE, Team.OptionStatus.NEVER);
NO_COLLIDE_TEAM = team;
}
}

View File

@@ -0,0 +1,56 @@
package hdvtdev.blockandseek;
import me.libraryaddict.disguise.DisguiseAPI;
import org.bukkit.GameMode;
import org.bukkit.attribute.Attribute;
import org.bukkit.entity.Player;
import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.potion.PotionEffect;
public final class Utils {
private Utils() {
}
public static void setLevelWithBar(Player p, int level) {
p.setLevel(level);
p.setExp(0.9998f * ((float) level / 100));
}
public static void clearPlayer(Player player) {
DisguiseAPI.undisguiseToAll(player);
player.getInventory().clear();
player.getInventory().setArmorContents(null);
player.getInventory().setExtraContents(null);
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();
}
}

View File

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

View File

@@ -1,13 +1,16 @@
package hdvtdev.blockAndSeek.eventListeners;
package hdvtdev.blockandseek.eventListeners;
import hdvtdev.blockAndSeek.Utils;
import hdvtdev.blockAndSeek.managers.ItemManager;
import hdvtdev.blockandseek.Config;
import hdvtdev.blockandseek.Utils;
import hdvtdev.blockandseek.managers.ItemManager;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.entity.FoodLevelChangeEvent;
import org.bukkit.event.player.PlayerDropItemEvent;
import org.bukkit.event.player.PlayerJoinEvent;
@@ -20,6 +23,9 @@ public class ForceControlEventListener implements Listener {
@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
Player player = event.getPlayer();
player.teleport(Config.spawn());
Utils.clearPlayer(player);
player.setInvulnerable(true);
ItemManager.defaultInventory(player);
}
@@ -33,17 +39,17 @@ public class ForceControlEventListener implements Listener {
if (event.getSkipReason() != TimeSkipEvent.SkipReason.COMMAND) event.setCancelled(true);
}
@EventHandler
@EventHandler(ignoreCancelled = true)
public void onBlockPlacement(BlockPlaceEvent event) {
event.setCancelled(true);
}
@EventHandler
@EventHandler(ignoreCancelled = true)
public void onBlockBreak(BlockBreakEvent event) {
event.setCancelled(true);
}
@EventHandler
@EventHandler(ignoreCancelled = true)
public void onFoodLevelChange(FoodLevelChangeEvent event) {
if (event.getEntity() instanceof Player player) {
event.setCancelled(true);
@@ -51,16 +57,16 @@ public class ForceControlEventListener implements Listener {
}
}
@EventHandler
@EventHandler(ignoreCancelled = true)
public void onDrop(PlayerDropItemEvent event) {
event.setCancelled(true);
}
@EventHandler
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(ignoreCancelled = true)
public void onFallDamage(EntityDamageEvent e) {
if (e.getEntity() instanceof Player) {
if (e.getCause() == EntityDamageEvent.DamageCause.FALL) {
e.setCancelled(true);
}
}
}

View File

@@ -0,0 +1,125 @@
package hdvtdev.blockandseek.eventListeners;
import hdvtdev.blockandseek.BlockAndSeek;
import hdvtdev.blockandseek.Keys;
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.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockDamageEvent;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.entity.EntityDismountEvent;
import org.bukkit.event.entity.EntityRegainHealthEvent;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.server.ServerLoadEvent;
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;
public class RequiredEventListener implements Listener {
@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
}
@EventHandler
public void onBlockDamage(BlockDamageEvent event) {
}
//todo remove
private final PropManager propManager = new PropManager();
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.MENU_ITEM)) {
new GamesMenu(player);
event.setCancelled(true);
}
}
}
}
@EventHandler
public void onEntityDismount(EntityDismountEvent event) {
}
@EventHandler
public void onPlayerQuit(PlayerQuitEvent event) {
}
@EventHandler
public void onPlayerDeath(PlayerDeathEvent event) {
}
@EventHandler
public void onInventoryClose(InventoryCloseEvent event) {
}
@EventHandler
public void onInventoryClick(InventoryClickEvent e) {
if (e.getInventory().getHolder() instanceof GuiHolder gui) {
e.setCancelled(true);
gui.onClick(e);
}
}
@EventHandler
public void onRegainHealth(EntityRegainHealthEvent event) {
}
@EventHandler
public void onPlayerDamage(EntityDamageByEntityEvent event) {
}
}

View File

@@ -0,0 +1,51 @@
package hdvtdev.blockandseek.managers;
import hdvtdev.blockandseek.objects.BlockAndSeekGame;
import hdvtdev.blockandseek.objects.BlockAndSeekMap;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.Nullable;
import java.util.*;
public class GamesManager {
private static final Map<String, BlockAndSeekGame> games = new HashMap<>();
private static final Set<UUID> seekerImmune = new HashSet<>();
public static boolean triggerSeekerImmune(UUID uuid) {
return seekerImmune.remove(uuid);
}
public static void addSeekerImmune(Player player) {
seekerImmune.add(player.getUniqueId());
}
public static Set<String> getAvailableGames() {
return games.keySet();
}
public static @Nullable BlockAndSeekGame createGame(String name) {
BlockAndSeekMap map = MapsManager.getMap(name);
if (map != null) {
BlockAndSeekGame game = new BlockAndSeekGame(name, map);
games.put(name, game);
return game;
}
return null;
}
public static void remove(String name) {
games.remove(name);
}
public static BlockAndSeekGame get(String name) {
return games.get(name);
}
}

View File

@@ -1,7 +1,8 @@
package hdvtdev.blockAndSeek.managers;
package hdvtdev.blockandseek.managers;
import hdvtdev.blockAndSeek.Keys;
import hdvtdev.blockAndSeek.Localization;
import hdvtdev.blockandseek.Keys;
import hdvtdev.blockandseek.objects.TranslationKey;
import lombok.Getter;
import net.kyori.adventure.text.Component;
import org.bukkit.Color;
import org.bukkit.Material;
@@ -14,10 +15,19 @@ import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.LeatherArmorMeta;
import org.bukkit.persistence.PersistentDataType;
import static hdvtdev.blockandseek.objects.TranslationKey.*;
public class ItemManager {
@Getter
private static final ItemStack freezeItem = new ItemStack(Material.HEART_OF_THE_SEA);
@Getter
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 seekerHelmet = new ItemStack(Material.LEATHER_HELMET);
@@ -25,50 +35,58 @@ public class ItemManager {
private static final ItemStack seekerLeggings = new ItemStack(Material.LEATHER_LEGGINGS);
private static final ItemStack seekerBoots = new ItemStack(Material.LEATHER_BOOTS);
@Getter
private static final ItemStack menuItem = new ItemStack(Material.COMPASS);
@Getter
private static final ItemStack games = new ItemStack(Material.BOOKSHELF);
@Getter
private static final ItemStack createGameButton = new ItemStack(Material.SLIME_BALL);
@Getter
private static final ItemStack leaveItem = new ItemStack(Material.RED_DYE);
static {
ItemMeta freezeMeta = freezeItem.getItemMeta();
freezeMeta.displayName(Component.text("freeze-item"));
freezeMeta.getPersistentDataContainer().set(Keys.FREEZE_ITEM, PersistentDataType.BOOLEAN, true);
freezeItem.setItemMeta(freezeMeta);
ItemMeta faceChangingMeta = faceChangingItem.getItemMeta();
faceChangingMeta.displayName(Component.text("face-changing-item"));
faceChangingMeta.getPersistentDataContainer().set(Keys.FACE_CHANGING_ITEM, PersistentDataType.BOOLEAN, true);
faceChangingItem.setItemMeta(faceChangingMeta);
ItemMeta swordMeta = seekerSword.getItemMeta();
swordMeta.displayName(Component.text("seeker-sword"));
swordMeta.addEnchant(Enchantment.DAMAGE_ALL, 2, false);
swordMeta.setUnbreakable(true);
swordMeta.addItemFlags(ItemFlag.HIDE_ENCHANTS, ItemFlag.HIDE_ATTRIBUTES);
seekerSword.setItemMeta(swordMeta);
ItemMeta menuMeta = menuItem.getItemMeta();
menuMeta.displayName(Component.text("menu-item"));
menuMeta.getPersistentDataContainer().set(Keys.MENU_ITEM, PersistentDataType.BOOLEAN, true);
menuMeta.addEnchant(Enchantment.ARROW_INFINITE, 1, true);
menuMeta.addItemFlags(ItemFlag.HIDE_ENCHANTS);
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();
gamesMeta.displayName(Component.text("games-page-item"));
gamesMeta.getPersistentDataContainer().set(Keys.GAME_PAGE, PersistentDataType.BOOLEAN, true);
games.setItemMeta(gamesMeta);
ItemMeta leaveMeta = leaveItem.getItemMeta();
leaveMeta.displayName(Component.text("leave-item"));
leaveMeta.getPersistentDataContainer().set(Keys.LEAVE_ITEM, PersistentDataType.BOOLEAN, true);
leaveItem.setItemMeta(leaveMeta);
ItemMeta createGameButtonMeta = createGameButton.getItemMeta();
createGameButtonMeta.displayName(Component.text("create-game-item"));
createGameButton.setItemMeta(createGameButtonMeta);
@@ -82,12 +100,12 @@ public class ItemManager {
public static void setSeekerSet(Player seeker) {
PlayerInventory inventory = seeker.getInventory();
inventory.clear();
inventory.addItem(Localization.translateItem(seeker, seekerSword));
inventory.addItem(TranslationManager.translateItem(seeker, seekerSword, TranslationKey.SEEKER_TEMPLATE));
ItemStack[] armor = new ItemStack[]{
Localization.translateItem(seeker, seekerBoots),
Localization.translateItem(seeker, seekerLeggings),
Localization.translateItem(seeker, seekerChestplate),
Localization.translateItem(seeker, seekerHelmet)
TranslationManager.translateItem(seeker, seekerBoots, TranslationKey.SEEKER_TEMPLATE),
TranslationManager.translateItem(seeker, seekerLeggings, TranslationKey.SEEKER_TEMPLATE),
TranslationManager.translateItem(seeker, seekerChestplate, TranslationKey.SEEKER_TEMPLATE),
TranslationManager.translateItem(seeker, seekerHelmet, TranslationKey.SEEKER_TEMPLATE)
};
inventory.setArmorContents(armor);
@@ -101,7 +119,6 @@ public class ItemManager {
meta.setUnbreakable(true);
meta.addEnchant(Enchantment.PROTECTION_ENVIRONMENTAL, 10, true);
meta.addEnchant(Enchantment.THORNS, 3, true);
meta.displayName(Component.text("seeker-armor"));
return meta;
}
@@ -109,32 +126,25 @@ public class ItemManager {
public static void defaultInventory(Player player) {
PlayerInventory inventory = player.getInventory();
inventory.clear();
inventory.addItem(Localization.translateItem(player, menuItem));
inventory.addItem(TranslationManager.translateItem(player, menuItem, TranslationKey.MENU));
}
public static ItemStack getFaceChangingItem() {
return faceChangingItem;
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 ItemStack getLeaveItem() {
return leaveItem;
public static void giveFaceChangingItem(Player player) {
player.getInventory().addItem(TranslationManager.translateItem(player, faceChangingItem, TranslationKey.SEEKER_TEMPLATE));
}
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

@@ -0,0 +1,135 @@
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.Location;
import org.bukkit.World;
import org.bukkit.WorldCreator;
import org.bukkit.command.CommandSender;
import org.incendo.cloud.suggestion.Suggestion;
import org.incendo.cloud.suggestion.SuggestionProvider;
import java.io.File;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
public final class 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) ->
CompletableFuture.supplyAsync(() -> {
// Лучше брать загруженные миры Bukkit, если карта должна быть в активном мире
// Если нужны папки миров:
File container = Bukkit.getWorldContainer();
File[] files = container.listFiles((dir, name) -> new File(dir, name + "/level.dat").exists());
if (files == null) return Collections.emptyList();
return Arrays.stream(files)
.map(File::getName)
.map(Suggestion::suggestion)
.collect(Collectors.toList());
});
public static SuggestionProvider<CommandSender> mapSuggestions = (context, input) ->
CompletableFuture.supplyAsync(() -> maps.keySet().stream()
.map(Suggestion::suggestion)
.collect(Collectors.toList()));
}

View File

@@ -0,0 +1,154 @@
package hdvtdev.blockandseek.managers;
import hdvtdev.blockandseek.BlockAndSeek;
import me.libraryaddict.disguise.DisguiseAPI;
import me.libraryaddict.disguise.disguisetypes.Disguise;
import me.libraryaddict.disguise.disguisetypes.DisguiseType;
import me.libraryaddict.disguise.disguisetypes.MiscDisguise;
import me.libraryaddict.disguise.disguisetypes.watchers.FallingBlockWatcher;
import org.bukkit.Bukkit;
import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.Openable;
import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.scheduler.BukkitRunnable;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public final class PropManager {
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) {
UUID uuid = player.getUniqueId();
PropData propData = players.remove(uuid);
Location location = player.getLocation().toCenterLocation();
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();
return PropState.UNFROZEN;
} else {
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);
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;
}
}
return PropState.FAILED;
}
@Nullable
private static BlockData getPlayerDisguiseData(Player player) {
Disguise disguise = DisguiseAPI.getDisguise(player);
if (disguise instanceof MiscDisguise miscDisguise && miscDisguise.getType() == DisguiseType.FALLING_BLOCK) {
FallingBlockWatcher watcher = (FallingBlockWatcher) miscDisguise.getWatcher();
return watcher.getBlockData();
}
return null;
}
public enum PropState {
FROZEN,
UNFROZEN,
FAILED
}
private record PropData(ArmorStand armorStand, BlockData blockData) {
}
}

View File

@@ -0,0 +1,65 @@
package hdvtdev.blockandseek.managers;
import hdvtdev.blockandseek.objects.BlockAndSeekGame;
import java.util.*;
public class StateManager {
private final Map<UUID, BlockAndSeekGame.PlayerType> playerToState = new HashMap<>();
private final Map<BlockAndSeekGame.PlayerType, Set<UUID>> stateToPlayers = new HashMap<>();
public void setPlayerState(UUID uuid, BlockAndSeekGame.PlayerType newState) {
BlockAndSeekGame.PlayerType oldState = playerToState.get(uuid);
if (oldState != null && oldState.equals(newState)) {
return;
}
if (oldState != null) {
removeUuidFromStateSet(oldState, uuid);
}
playerToState.put(uuid, newState);
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() {
return playerToState.size();
}
public BlockAndSeekGame.PlayerType getState(UUID uuid) {
return playerToState.get(uuid);
}
public Set<UUID> getPlayersInState(BlockAndSeekGame.PlayerType state) {
return Collections.unmodifiableSet(stateToPlayers.getOrDefault(state, Collections.emptySet()));
}
public void removePlayer(UUID uuid) {
BlockAndSeekGame.PlayerType oldState = playerToState.remove(uuid);
if (oldState != null) {
removeUuidFromStateSet(oldState, uuid);
}
}
private void removeUuidFromStateSet(BlockAndSeekGame.PlayerType state, UUID uuid) {
Set<UUID> uuids = stateToPlayers.get(state);
if (uuids != null) {
uuids.remove(uuid);
if (uuids.isEmpty()) {
stateToPlayers.remove(state);
}
}
}
}

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,8 +1,10 @@
package hdvtdev.blockAndSeek.managers;
package hdvtdev.blockandseek.managers;
import hdvtdev.blockAndSeek.BlockAndSeek;
import hdvtdev.blockandseek.BlockAndSeek;
import io.papermc.paper.threadedregions.scheduler.AsyncScheduler;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.WorldCreator;

View File

@@ -0,0 +1,80 @@
package hdvtdev.blockandseek.menus;
import hdvtdev.blockandseek.managers.TranslationManager;
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.minimessage.MiniMessage;
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.List;
public class GamesMenu implements GuiHolder {
private final Inventory inventory;
public GamesMenu(Player player) {
Component title = TranslationManager.get(player, TranslationKey.GAMES_MENU);
this.inventory = Bukkit.createInventory(this, 54, title);
initInventory();
showInventory(player);
}
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
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();
}
}
}
@Override
public @NotNull Inventory getInventory() {
return inventory;
}
}

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

@@ -0,0 +1,468 @@
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 org.bukkit.Bukkit;
import org.bukkit.GameMode;
import org.bukkit.entity.ArmorStand;
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.*;
public class BlockAndSeekGame {
private final BlockAndSeekMap map;
private final StateManager stateManager = new StateManager();
private final PropManager propManager = new PropManager();
private final String name;
private GamePhase phase;
public BlockAndSeekGame(String name, BlockAndSeekMap blockAndSeekMap) {
this.name = name;
this.map = blockAndSeekMap;
start();
}
public boolean addPlayer(Player player) {
if (phase instanceof LobbyPhase && stateManager.playerCount() < map.getMaxPlayers()) {
stateManager.setPlayerState(player.getUniqueId(), PlayerType.NONE);
player.teleport(map.getLobby());
player.setInvulnerable(true);
return true;
}
return false;
}
public String players() {
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 {
NONE,
PROP,
SEEKER,
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

@@ -0,0 +1,59 @@
package hdvtdev.blockandseek.objects;
import eu.okaeri.configs.OkaeriConfig;
import eu.okaeri.configs.annotation.Comment;
import eu.okaeri.configs.annotation.CustomKey;
import eu.okaeri.validator.annotation.Min;
import lombok.Getter;
import lombok.Setter;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
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

@@ -0,0 +1,18 @@
package hdvtdev.blockandseek.objects;
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

@@ -0,0 +1,25 @@
package hdvtdev.blockandseek.objects;
import eu.okaeri.configs.OkaeriConfig;
import eu.okaeri.configs.annotation.Comment;
import org.bukkit.inventory.ItemStack;
public class PropBlock extends OkaeriConfig {
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

@@ -0,0 +1,20 @@
package hdvtdev.blockandseek.objects;
public enum Rarity {
COMMON(100), // Очень часто (как грязь)
UNCOMMON(50), // Часто (половина от обычного)
RARE(25), // Редко
EPIC(10), // Очень редко
MYTHIC(3), // Почти невозможно
LEGENDARY(1); // Чудо
private final int weight;
Rarity(int weight) {
this.weight = weight;
}
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

@@ -0,0 +1,145 @@
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.Sound;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack;
import org.bukkit.scheduler.BukkitRunnable;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public final class RouletteCreator implements InventoryHolder {
private static final int[] slots = {3, 5, 7, 12, 14, 16, 21, 23, 25, 30, 32, 34, 39, 41, 43};
private static final int[] midSlots = {21, 23, 25};
private static final Random random = new Random();
private final Inventory roulette;
private boolean isRunning = true;
public RouletteCreator(@NotNull Player player, @NotNull List<PropBlock> blocks) {
roulette = Bukkit.createInventory(this, 45, TranslationManager.get(player, TranslationKey.ROULETTE));
this.createSmoothRoulette(roulette, player, blocks);
}
@Override
public @NotNull Inventory getInventory() {
return roulette;
}
public ItemStack randomPropItem() {
return isRunning ? roulette.getItem(slots[random.nextInt(0, 15)]) : roulette.getItem(midSlots[random.nextInt(0, 3)]);
}
@ApiStatus.Experimental
private void createSmoothRoulette(Inventory gui, Player player, List<PropBlock> blocks) {
player.openInventory(gui);
new BukkitRunnable() {
final RouletteGenerator rouletteGenerator = new RouletteGenerator(blocks);
final List<RouletteList<ItemStack>> sources = List.of(
new RouletteList<>(rouletteGenerator.getRandomRow(15)),
new RouletteList<>(rouletteGenerator.getRandomRow(15)),
new RouletteList<>(rouletteGenerator.getRandomRow(15))
);
final List<ItemStack[]> columns = new ArrayList<>();
{
for (int i = 0; i < 3; i++) {
ItemStack[] col = new ItemStack[5];
for (int row = 0; row < 5; row++) {
col[row] = sources.get(i).next();
}
columns.add(col);
}
}
final int[] colStartIndices = {3, 5, 7};
int ticksAlive = 0; // Общее время работы анимации
int ticksUntilNextMove = 0; // Обратный отсчет до следующего кадра
@Override
public void run() {
// Безопасность
if (!player.isOnline() || !player.getOpenInventory().getTopInventory().equals(gui)) {
this.cancel();
return;
}
// Увеличиваем общий счетчик жизни таймера
ticksAlive++;
// Если время ожидания еще не вышло — ждем
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();
}
}
private void updateRoulette() {
player.playSound(player.getLocation(), Sound.UI_BUTTON_CLICK, 0.5f, 1f);
for (int colIndex = 0; colIndex < 3; colIndex++) {
ItemStack[] colItems = columns.get(colIndex);
for (int row = 0; row < 5; row++) {
int slot = colStartIndices[colIndex] + (row * 9);
gui.setItem(slot, colItems[row]);
}
System.arraycopy(colItems, 0, colItems, 1, 4);
colItems[0] = sources.get(colIndex).next();
}
}
}.runTaskTimer(BlockAndSeek.getInstance(), 0L, 1L);
}
}

View File

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

View File

@@ -1,4 +1,6 @@
package hdvtdev.blockAndSeek.roulette;
package hdvtdev.blockandseek.roulette;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.Objects;
@@ -7,7 +9,7 @@ public class RouletteList<T> {
private final List<T> items;
private int currentIndex;
public RouletteList(List<T> items) {
public RouletteList(@NotNull List<T> items) {
this.items = Objects.requireNonNull(items);
this.currentIndex = 0;
if (items.isEmpty()) {

View File

@@ -1,19 +0,0 @@
config:
==: hdvtdev.blockAndSeek.Config # <-- DO NOT CHANGE THIS FIELD!!!
# A place to teleport players to after the game is over
# Example:
# default-spawn:
# - world: "your world"
# location:
# - 0 #x
# - 60 #y
# - 0 #z
default-spawn: { }
# If force-control set to true plugin will control typical aspects such as
# disabling breaking blocks, disabling damage in lobbies, etc.
# WARN: DO NOT USE THIS WITH OTHER PLUGINS THAT PROVIDE SIMILAR FUNCTIONALITY
force-control: false
# planned
# auto-update: false

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,40 +0,0 @@
en-US:
#Maps
maps-available: "<gold>Available maps: <white>"
maps-available-element: "- {color-status}{map}<white>"
map-lobby-sidebar: "<bold><gradient:#FFFF00:#FFD700:#FFA500>{map}"
not-enough-permissions: "<red><bold>You do not have permission to run this command."
not-enough-arguments: "<red><bold>Too few arguments to run command {command}. Arguments example: {help}"
successful-reload: "<green>Successfully reloaded <yellow><u>{config}<green>."
failed-reload: "<red>Failed to reload <yellow><u>{config}<red>. Error: {e}"
seekers-won: "<bold><yellow>Seekers <red>won!"
hiders-won: "<bold><yellow>Hiders <green>won!"
hiders-solo-win: "<bold><yellow>{hider} <green>won this game!"
hider-was-found: "<yellow><bold>{hider}<reset><gold> was found by <red><bold>{seeker}"
hider-died: "<yellow><bold>{hider}<reset><gold> somehow died xd"
game-time-left: "<gold>Time left: <yellow>{time}<gold>s"
game-players-count: "<yellow>{players} <gold>of <yellow>{max-players}"
player-join: "<yellow><bold>{player} <reset><gold>joined. <yellow><bold>{players}"
player-leave: "<yellow><bold>{player} <reset><gold>leaved. <yellow><bold>{players}"
game-title: "<bold><gold>{title}"
wait-time-left: "<gold>Game starts in: <yellow><bold>{time}s"
#items
freeze-item: "<gradient:#084CFB:#ADF3FD><bold>Freeze"
seeker-armor: "<gradient:#8B0000:#B22222:#DC143C><bold>Seeker armor"
menu-item: "<gradient:#FFD700:#FFF200:#FFFF00><bold>BlockAndSeek Menu"
games-page-item: "<gradient:#006400:#228B22:#00FF7F><bold>Games"
create-game-item: "<green><bold>Create new game"
game-name: "<green>{name}"
game-player-count: "<yellow>{players}<gold> of <yellow>{max-players}<gold> players"
leave-item: "<red>Leave game"
roulette-title: "<bold><gold>Props Roulette"

View File

@@ -1 +0,0 @@
# DO NOT edit this file manually! Use plugin commands to edit maps instead.

View File

@@ -1,7 +1,11 @@
name: BlockAndSeek
version: '0.0.1-a'
main: hdvtdev.blockAndSeek.BlockAndSeek
main: hdvtdev.blockandseek.BlockAndSeek
api-version: '1.20'
load: POSTWORLD
depend:
- LibsDisguises
permissions:
blockandseek.manage:
@@ -12,4 +16,4 @@ commands:
blockandseek:
aliases:
- bs
usage: "Usage: /blockandseek [subcommand]"
usage: "Usage: /blockandseek [subcommand]"