Complete Stage 0: Project scaffold

- Fix build.gradle.kts: Java 21, Paper 1.20.6, ProtocolLib 5.3.0
- Add permissions to paper-plugin.yml
- Add ConfigManager with safe yaml loading (no overwrite)
- Add LocationsConfig for all game object coordinates
- Add GameConfig with all balance values
- Update mdbd.java with onEnable/onDisable + ConfigManager
- Mark Stage 0 complete in PLAN.md
This commit is contained in:
loki5512344 2026-05-31 20:37:36 +02:00
parent 32da611c63
commit 53f3f6e9da
7 changed files with 443 additions and 15 deletions

12
PLAN.md
View file

@ -167,12 +167,12 @@ src/main/java/com/dbd/plugin/
## TODO (по порядку) ## TODO (по порядку)
### Этап 0: Скелет проекта ### Этап 0: Скелет проекта
- [ ] Создать `build.gradle` (Paper 1.20.6, Java 17, ProtocolLib) - [x] Создать `build.gradle` (Paper 1.20.6, Java 21, ProtocolLib)
- [ ] `plugin.yml` с пермишнами - [x] `plugin.yml` с пермишнами
- [ ] `DBDPlugin.java` — onEnable/onDisable - [x] `DBDPlugin.java` — onEnable/onDisable
- [ ] `ConfigManager` — загрузка yaml без перезаписи - [x] `ConfigManager` — загрузка yaml без перезаписи
- [ ] `LocationsConfig` — структура для всех координат - [x] `LocationsConfig` — структура для всех координат
- [ ] `GameConfig` — балансные цифры - [x] `GameConfig` — балансные цифры
### Этап 1: Core Game Loop ### Этап 1: Core Game Loop
- [ ] `GameState` enum - [ ] `GameState` enum

View file

@ -6,22 +6,21 @@ plugins {
repositories { repositories {
mavenCentral() mavenCentral()
maven("https://repo.papermc.io/repository/maven-public/") maven("https://repo.papermc.io/repository/maven-public/")
maven("https://repo.dmulloy2.net/repository/public/")
} }
dependencies { dependencies {
compileOnly("io.papermc.paper:paper-api:26.1.2.build.+") compileOnly("io.papermc.paper:paper-api:1.20.6-R0.1-SNAPSHOT")
compileOnly("com.comphenix.protocol:ProtocolLib:5.3.0")
} }
java { java {
toolchain.languageVersion = JavaLanguageVersion.of(25) toolchain.languageVersion = JavaLanguageVersion.of(21)
} }
tasks { tasks {
runServer { runServer {
// Configure the Minecraft version for our task. minecraftVersion("1.20.6")
// This is the only required configuration besides applying the plugin.
// Your plugin's jar (or shadowJar if present) will be used automatically.
minecraftVersion("26.1.2")
jvmArgs("-Xms2G", "-Xmx2G") jvmArgs("-Xms2G", "-Xmx2G")
} }

View file

@ -0,0 +1,68 @@
package dev.loki.config;
import dev.loki.mdbd;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
public class ConfigManager {
private final mdbd plugin;
private final File dataFolder;
private LocationsConfig locationsConfig;
private GameConfig gameConfig;
public ConfigManager(mdbd plugin) {
this.plugin = plugin;
this.dataFolder = plugin.getDataFolder();
if (!dataFolder.exists()) {
dataFolder.mkdirs();
}
}
public void loadConfigs() {
this.locationsConfig = new LocationsConfig(loadConfig("locations.yml"));
this.gameConfig = new GameConfig(loadConfig("game.yml"));
plugin.getLogger().info("Configs loaded successfully");
}
private FileConfiguration loadConfig(String fileName) {
File file = new File(dataFolder, fileName);
if (!file.exists()) {
createDefaultConfig(fileName, file);
}
return YamlConfiguration.loadConfiguration(file);
}
private void createDefaultConfig(String resourceName, File targetFile) {
try (InputStream is = plugin.getResource(resourceName)) {
if (is != null) {
Files.copy(is, targetFile.toPath());
} else {
targetFile.createNewFile();
}
} catch (IOException e) {
plugin.getLogger().warning("Could not create config: " + resourceName);
}
}
public void saveConfig(String fileName, FileConfiguration config) {
try {
config.save(new File(dataFolder, fileName));
} catch (IOException e) {
plugin.getLogger().warning("Could not save config: " + fileName);
}
}
public LocationsConfig getLocations() {
return locationsConfig;
}
public GameConfig getGame() {
return gameConfig;
}
}

View file

@ -0,0 +1,176 @@
package dev.loki.config;
import org.bukkit.configuration.file.FileConfiguration;
public class GameConfig {
private final FileConfiguration config;
public GameConfig(FileConfiguration config) {
this.config = config;
setDefaults();
}
private void setDefaults() {
// Lobby settings
setDefault("lobby.min-players", 5);
setDefault("lobby.max-players", 5);
setDefault("lobby.countdown-seconds", 60);
setDefault("lobby.role-assign-delay", 5);
// Generator settings
setDefault("generators.total-count", 7);
setDefault("generators.required-count", 5);
setDefault("generators.repair-seconds", 80);
setDefault("generators.regression-rate", 0.25);
setDefault("generators.kick-regression-rate", 2.5);
setDefault("generators.kick-duration-seconds", 30);
// Skill check settings
setDefault("skillcheck.zone-size", 0.15);
setDefault("skillcheck.rotation-speed", 3.0);
setDefault("skillcheck.success-bonus", 5.0);
setDefault("skillcheck.fail-penalty", 10.0);
// Hook settings
setDefault("hooks.stage1-seconds", 60);
setDefault("hooks.stage2-seconds", 60);
setDefault("hooks.struggle-interval", 2);
setDefault("hooks.max-hooks", 3);
// Movement speeds (% of normal)
setDefault("movement.killer", 115);
setDefault("movement.killer-carry", 92);
setDefault("movement.survivor-walk", 100);
setDefault("movement.survivor-sprint", 150);
setDefault("movement.survivor-crawl", 30);
setDefault("movement.exhaustion-duration", 3);
setDefault("movement.exhaustion-cooldown", 5);
// Healing settings
setDefault("healing.self-heal-seconds", 30);
setDefault("healing.other-heal-seconds", 16);
setDefault("healing.medkit-bonus", 0.5);
// Killer settings
setDefault("killer.attack-cooldown", 2.5);
setDefault("killer.attack-range", 3.0);
setDefault("killer.pallet-stun-seconds", 2.5);
setDefault("killer.pallet-break-seconds", 3.0);
setDefault("killer.terror-radius", 32);
// Window vault
setDefault("window.vault-survivor-fast", 1.0);
setDefault("window.vault-survivor-slow", 1.5);
setDefault("window.vault-killer", 1.5);
// Endgame
setDefault("endgame.gate-open-seconds", 20);
setDefault("endgame.collapse-seconds", 120);
setDefault("endgame.chest-open-seconds", 3);
// Hatch
setDefault("hatch.min-survivors-for-open", 1);
}
private void setDefault(String path, Object value) {
if (!config.contains(path)) {
config.set(path, value);
}
}
public int getMinPlayers() {
return config.getInt("lobby.min-players", 5);
}
public int getMaxPlayers() {
return config.getInt("lobby.max-players", 5);
}
public int getLobbyCountdown() {
return config.getInt("lobby.countdown-seconds", 60);
}
public int getRoleAssignDelay() {
return config.getInt("lobby.role-assign-delay", 5);
}
public int getGeneratorTotal() {
return config.getInt("generators.total-count", 7);
}
public int getGeneratorRequired() {
return config.getInt("generators.required-count", 5);
}
public int getGeneratorRepairSeconds() {
return config.getInt("generators.repair-seconds", 80);
}
public double getRegressionRate() {
return config.getDouble("generators.regression-rate", 0.25);
}
public double getKickRegressionRate() {
return config.getDouble("generators.kick-regression-rate", 2.5);
}
public int getKickDuration() {
return config.getInt("generators.kick-duration-seconds", 30);
}
public int getStage1Seconds() {
return config.getInt("hooks.stage1-seconds", 60);
}
public int getStage2Seconds() {
return config.getInt("hooks.stage2-seconds", 60);
}
public int getMaxHooks() {
return config.getInt("hooks.max-hooks", 3);
}
public double getKillerSpeed() {
return config.getDouble("movement.killer", 115) / 100.0;
}
public double getKillerCarrySpeed() {
return config.getDouble("movement.killer-carry", 92) / 100.0;
}
public double getSurvivorWalkSpeed() {
return config.getDouble("movement.survivor-walk", 100) / 100.0;
}
public double getSurvivorSprintSpeed() {
return config.getDouble("movement.survivor-sprint", 150) / 100.0;
}
public double getSurvivorCrawlSpeed() {
return config.getDouble("movement.survivor-crawl", 30) / 100.0;
}
public int getAttackCooldown() {
return (int) (config.getDouble("killer.attack-cooldown", 2.5) * 20);
}
public double getAttackRange() {
return config.getDouble("killer.attack-range", 3.0);
}
public int getPalletStunTicks() {
return (int) (config.getDouble("killer.pallet-stun-seconds", 2.5) * 20);
}
public int getPalletBreakTicks() {
return (int) (config.getDouble("killer.pallet-break-seconds", 3.0) * 20);
}
public int getTerrorRadius() {
return config.getInt("killer.terror-radius", 32);
}
public FileConfiguration getConfig() {
return config;
}
}

View file

@ -0,0 +1,159 @@
package dev.loki.config;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.configuration.file.FileConfiguration;
import java.util.ArrayList;
import java.util.List;
public class LocationsConfig {
private final FileConfiguration config;
public LocationsConfig(FileConfiguration config) {
this.config = config;
setDefaults();
}
private void setDefaults() {
if (!config.contains("lobby")) {
config.set("lobby.world", "world");
config.set("lobby.x", 0.0);
config.set("lobby.y", 100.0);
config.set("lobby.z", 0.0);
config.set("lobby.yaw", 0.0);
config.set("lobby.pitch", 0.0);
}
if (!config.contains("generators")) {
config.set("generators", new ArrayList<>());
}
if (!config.contains("hooks")) {
config.set("hooks", new ArrayList<>());
}
if (!config.contains("pallets")) {
config.set("pallets", new ArrayList<>());
}
if (!config.contains("windows")) {
config.set("windows", new ArrayList<>());
}
if (!config.contains("chests")) {
config.set("chests", new ArrayList<>());
}
if (!config.contains("totems")) {
config.set("totems", new ArrayList<>());
}
if (!config.contains("exitgates")) {
config.set("exitgates", new ArrayList<>());
}
if (!config.contains("killer-spawn")) {
config.set("killer-spawn", new ArrayList<>());
}
if (!config.contains("survivor-spawns")) {
config.set("survivor-spawns", new ArrayList<>());
}
}
public Location getLobby() {
World world = Bukkit.getWorld(config.getString("lobby.world", "world"));
if (world == null) return null;
return new Location(
world,
config.getDouble("lobby.x", 0.0),
config.getDouble("lobby.y", 100.0),
config.getDouble("lobby.z", 0.0),
(float) config.getDouble("lobby.yaw", 0.0),
(float) config.getDouble("lobby.pitch", 0.0)
);
}
public void setLobby(Location loc) {
config.set("lobby.world", loc.getWorld().getName());
config.set("lobby.x", loc.getX());
config.set("lobby.y", loc.getY());
config.set("lobby.z", loc.getZ());
config.set("lobby.yaw", loc.getYaw());
config.set("lobby.pitch", loc.getPitch());
}
public List<Location> getGenerators() {
return loadLocationList("generators");
}
public List<Location> getHooks() {
return loadLocationList("hooks");
}
public List<Location> getPallets() {
return loadLocationList("pallets");
}
public List<Location> getWindows() {
return loadLocationList("windows");
}
public List<Location> getChests() {
return loadLocationList("chests");
}
public List<Location> getTotems() {
return loadLocationList("totems");
}
public List<Location> getExitGates() {
return loadLocationList("exitgates");
}
public Location getKillerSpawn() {
List<Location> spawns = loadLocationList("killer-spawn");
return spawns.isEmpty() ? null : spawns.get(0);
}
public List<Location> getSurvivorSpawns() {
return loadLocationList("survivor-spawns");
}
private List<Location> loadLocationList(String path) {
List<Location> locations = new ArrayList<>();
List<?> list = config.getList(path);
if (list == null) return locations;
for (Object obj : list) {
if (obj instanceof String) {
Location loc = parseLocation((String) obj);
if (loc != null) locations.add(loc);
}
}
return locations;
}
private Location parseLocation(String str) {
String[] parts = str.split(",");
if (parts.length < 4) return null;
World world = Bukkit.getWorld(parts[0].trim());
if (world == null) return null;
try {
double x = Double.parseDouble(parts[1].trim());
double y = Double.parseDouble(parts[2].trim());
double z = Double.parseDouble(parts[3].trim());
float yaw = parts.length > 4 ? Float.parseFloat(parts[4].trim()) : 0f;
float pitch = parts.length > 5 ? Float.parseFloat(parts[5].trim()) : 0f;
return new Location(world, x, y, z, yaw, pitch);
} catch (NumberFormatException e) {
return null;
}
}
public void addLocation(String path, Location loc) {
List<String> list = config.getStringList(path);
list.add(String.format("%s,%.2f,%.2f,%.2f,%.1f,%.1f",
loc.getWorld().getName(),
loc.getX(), loc.getY(), loc.getZ(),
loc.getYaw(), loc.getPitch()));
config.set(path, list);
}
public FileConfiguration getConfig() {
return config;
}
}

View file

@ -1,16 +1,34 @@
package dev.loki; package dev.loki;
import dev.loki.config.ConfigManager;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
public final class mdbd extends JavaPlugin { public final class mdbd extends JavaPlugin {
private static mdbd instance;
private ConfigManager configManager;
@Override @Override
public void onEnable() { public void onEnable() {
// Plugin startup logic instance = this;
getLogger().info("Mdbd Plugin enabling...");
this.configManager = new ConfigManager(this);
this.configManager.loadConfigs();
getLogger().info("Mdbd Plugin enabled successfully!");
} }
@Override @Override
public void onDisable() { public void onDisable() {
// Plugin shutdown logic getLogger().info("Mdbd Plugin disabling...");
instance = null;
}
public static mdbd getInstance() {
return instance;
}
public ConfigManager getConfigManager() {
return configManager;
} }
} }

View file

@ -4,7 +4,15 @@ version: '${version}'
main: dev.loki.mdbd main: dev.loki.mdbd
bootstrapper: dev.loki.mdbdBootstrap bootstrapper: dev.loki.mdbdBootstrap
loader: dev.loki.mdbdLoader loader: dev.loki.mdbdLoader
api-version: '26.1.2' api-version: '1.20.6'
load: POSTWORLD load: POSTWORLD
authors: [ loki ] authors: [ loki ]
permissions:
mdbd.admin:
description: "Полный доступ к админ-командам плагина"
default: op
mdbd.player:
description: "Базовые команды для игроков"
default: true