forked from SteamWar/SteamWar
Compare commits
102
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f63390915b | ||
|
|
db939e27d9 | ||
|
|
36e6329f2b | ||
|
|
b2300c128e | ||
|
|
9cb4e0b7db | ||
|
|
8e4730bd12 | ||
|
|
baa278a1f5 | ||
|
|
8fd6fdf81e | ||
|
|
7d0f5aceae | ||
|
|
ad9087c038 | ||
|
|
872da5f62a | ||
|
|
e830d087c9 | ||
|
|
b4661ea345 | ||
|
|
4e5945d2bc | ||
|
|
a5b8f00c58 | ||
|
|
8e23490b04 | ||
|
|
4c6b5e654a | ||
|
|
38a33b7298 | ||
|
|
32797bd2a8 | ||
|
|
28b62bf27b | ||
|
|
db915f7be7 | ||
|
|
aef26b5e9f | ||
|
|
8009098965 | ||
|
|
47f0d9858b | ||
|
|
c3eda01e20 | ||
|
|
5e81081620 | ||
|
|
ed70d65bc4 | ||
|
|
2ce8799a19 | ||
|
|
15036a7ba0 | ||
|
|
d77c014409 | ||
|
|
6b5441bb04 | ||
|
|
54d30f4a42 | ||
|
|
e41f76937e | ||
|
|
0c7ee052e8 | ||
|
|
b60cdfc88d | ||
|
|
98f04451a4 | ||
|
|
c95901bc42 | ||
|
|
211e2693cf | ||
|
|
1980375e73 | ||
|
|
ef041272bb | ||
|
|
bfbf98c1d6 | ||
|
|
c4b46e4431 | ||
|
|
c238b9bdf2 | ||
|
|
fec4007120 | ||
|
|
11798c3ec0 | ||
|
|
9935ab785d | ||
|
|
8e3e71dd96 | ||
|
|
3fe2e006b9 | ||
|
|
9d5fcc07e8 | ||
|
|
0c3e63d521 | ||
|
|
27d260a8d2 | ||
|
|
37f017b84c | ||
|
|
21d0a4359d | ||
|
|
8318d408b6 | ||
|
|
d14ddc4fab | ||
|
|
5cc5529533 | ||
|
|
380693a2b7 | ||
|
|
69aef9dfb6 | ||
|
|
097df865e5 | ||
|
|
230f8c521a | ||
|
|
253a975c73 | ||
|
|
12dd46176b | ||
|
|
2111d72387 | ||
|
|
3aa471c3ab | ||
|
|
47557d7bb8 | ||
|
|
62850b2e11 | ||
|
|
7f47d687d3 | ||
|
|
362e1cebff | ||
|
|
39cacbd701 | ||
|
|
503261ce6d | ||
|
|
8a86ebde83 | ||
|
|
be915770b1 | ||
|
|
70c4225827 | ||
|
|
904a850a3f | ||
|
|
4c75879915 | ||
|
|
65cc6119b0 | ||
|
|
975d42c17f | ||
|
|
317cb0adf8 | ||
|
|
4266a7f35b | ||
|
|
26d5ff7aad | ||
|
|
b21ff415a8 | ||
|
|
9fe05b59e4 | ||
|
|
2302f9aafb | ||
|
|
d727982ce0 | ||
|
|
2b42f0d13c | ||
|
|
3e3a209c80 | ||
|
|
1d43021853 | ||
|
|
5a224c1bcc | ||
|
|
6330dbc2b1 | ||
|
|
4306fe7bc9 | ||
|
|
58df6087f0 | ||
|
|
b9b7869124 | ||
|
|
1e85b14667 | ||
|
|
35c27923aa | ||
|
|
c3b3e3e60f | ||
|
|
3aa1bf7ffd | ||
|
|
5a02a58437 | ||
|
|
6209b86149 | ||
|
|
6f1403df9c | ||
|
|
0113788da2 | ||
|
|
8a7f65e000 | ||
|
|
8401f67b24 |
@@ -7,8 +7,14 @@
|
||||
!gradle/wrapper/gradle-wrapper.jar
|
||||
steamwar.properties
|
||||
|
||||
# Kotlin
|
||||
.kotlin
|
||||
|
||||
# IntelliJ IDEA
|
||||
*.iml
|
||||
.idea
|
||||
!.idea/copyright
|
||||
!.idea/runConfigurations
|
||||
|
||||
# VSCode
|
||||
bin/
|
||||
|
||||
@@ -47,7 +47,7 @@ dependencies {
|
||||
compileOnly(libs.netty)
|
||||
|
||||
implementation(libs.luaj)
|
||||
implementation(files("$projectDir/../libs/YAPION-SNAPSHOT.jar"))
|
||||
// implementation(files("$projectDir/../libs/YAPION-SNAPSHOT.jar"))
|
||||
}
|
||||
|
||||
widener {
|
||||
|
||||
@@ -20,8 +20,7 @@
|
||||
package de.steamwar.bausystem;
|
||||
|
||||
import de.steamwar.bausystem.config.BauServer;
|
||||
import de.steamwar.bausystem.configplayer.Config;
|
||||
import de.steamwar.bausystem.configplayer.ConfigConverter;
|
||||
import de.steamwar.bausystem.config.ConfigStore;
|
||||
import de.steamwar.bausystem.features.gui.BauGUI;
|
||||
import de.steamwar.bausystem.features.script.lua.SteamWarLuaPlugin;
|
||||
import de.steamwar.bausystem.features.script.lua.libs.LuaLib;
|
||||
@@ -36,7 +35,6 @@ import de.steamwar.bausystem.region.Region;
|
||||
import de.steamwar.bausystem.region.RegionSystem;
|
||||
import de.steamwar.bausystem.utils.ScoreboardElement;
|
||||
import de.steamwar.bausystem.utils.TickManager;
|
||||
import de.steamwar.bausystem.worlddata.WorldData;
|
||||
import de.steamwar.command.AbstractValidator;
|
||||
import de.steamwar.command.SWCommandUtils;
|
||||
import de.steamwar.core.CRIUSleepEvent;
|
||||
@@ -112,9 +110,6 @@ public class BauSystem extends JavaPlugin implements Listener {
|
||||
if (any instanceof PanzernAlgorithm) {
|
||||
Panzern.add((PanzernAlgorithm) any);
|
||||
}
|
||||
if (any instanceof ConfigConverter) {
|
||||
Config.addConfigConverter((ConfigConverter) any);
|
||||
}
|
||||
if (any instanceof BoundingBoxLoader) {
|
||||
((BoundingBoxLoader) any).load();
|
||||
}
|
||||
@@ -160,9 +155,8 @@ public class BauSystem extends JavaPlugin implements Listener {
|
||||
@Override
|
||||
public void onDisable() {
|
||||
linker.unlink();
|
||||
WorldData.write();
|
||||
RegionSystem.INSTANCE.save();
|
||||
Config.getInstance().saveAll();
|
||||
ConfigStore.saveAll();
|
||||
}
|
||||
|
||||
private <T extends CommandSender> AbstractValidator<T, ?> validator(Permission permission) {
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2025 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.bausystem.config;
|
||||
|
||||
/**
|
||||
* Converts a config of one version into the next version, e.g. ConfigV1 to
|
||||
* ConfigV2. The converter for the second newest version converts into the
|
||||
* current version. Converters are registered in the {@link ConfigStore}
|
||||
* of the config.
|
||||
*
|
||||
* @param <F> the config version to convert from
|
||||
* @param <T> the config version to convert to
|
||||
*/
|
||||
public interface ConfigConverter<F extends VersionedConfig, T extends VersionedConfig> {
|
||||
|
||||
/**
|
||||
* @return the version this converter converts from
|
||||
*/
|
||||
int version();
|
||||
|
||||
/**
|
||||
* Converts the config to the next version. If anything goes wrong do not
|
||||
* silently exit this method, throw an Exception. The {@link ConfigStore}
|
||||
* will deal with it.
|
||||
*
|
||||
* @param config the config to convert
|
||||
* @return the converted config
|
||||
*/
|
||||
T convert(F config);
|
||||
}
|
||||
@@ -0,0 +1,364 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2026 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.bausystem.config;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import de.steamwar.sql.UserConfig;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
* Loads, converts and saves a versioned config stored as JSON. Older versions
|
||||
* are converted with the registered {@link ConfigConverter}s, legacy formats
|
||||
* that are not JSON can be read with {@link #legacy(Predicate, Function)}.
|
||||
* <p>
|
||||
* A store can be bound to where the config is stored:
|
||||
* <ul>
|
||||
* <li>{@link #userConfig(String)}: one config per player in the {@link UserConfig}, use {@link #get(Player)}</li>
|
||||
* <li>{@link #file(File)}: one config in a file, use {@link #get()}</li>
|
||||
* <li>unbound: use {@link #load(File)} and {@link #save(File, VersionedConfig)}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param <T> the current config version
|
||||
*/
|
||||
public class ConfigStore<T extends VersionedConfig> {
|
||||
|
||||
private static final List<ConfigStore<?>> STORES = new ArrayList<>();
|
||||
|
||||
private final Class<T> type;
|
||||
private final int version;
|
||||
private final Supplier<T> defaultConfig;
|
||||
|
||||
private final Map<Integer, Class<? extends VersionedConfig>> jsonVersions = new HashMap<>();
|
||||
private final Map<Integer, ConfigConverter<?, ?>> converters = new HashMap<>();
|
||||
private final List<LegacyFormat> legacyFormats = new ArrayList<>();
|
||||
private Gson gson = new Gson();
|
||||
private Integer unversionedVersion = null;
|
||||
|
||||
private String userConfigKey = null;
|
||||
private Predicate<T> removeIf = config -> false;
|
||||
private final Map<UUID, T> playerConfigs = new ConcurrentHashMap<>();
|
||||
|
||||
private File file = null;
|
||||
private File legacyFile = null;
|
||||
private Function<String, ? extends VersionedConfig> legacyFileReader = null;
|
||||
private T fileConfig = null;
|
||||
|
||||
private record LegacyFormat(Predicate<String> matches, Function<String, ? extends VersionedConfig> reader) {
|
||||
}
|
||||
|
||||
public ConfigStore(Class<T> type, int version, Supplier<T> defaultConfig) {
|
||||
this.type = type;
|
||||
this.version = version;
|
||||
this.defaultConfig = defaultConfig;
|
||||
jsonVersions.put(version, type);
|
||||
STORES.add(this);
|
||||
}
|
||||
|
||||
public ConfigStore<T> gson(Gson gson) {
|
||||
this.gson = gson;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an older config version that is stored as JSON.
|
||||
*/
|
||||
public ConfigStore<T> jsonVersion(int version, Class<? extends VersionedConfig> type) {
|
||||
jsonVersions.put(version, type);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ConfigStore<T> converter(ConfigConverter<?, ?> converter) {
|
||||
if (converters.putIfAbsent(converter.version(), converter) != null) {
|
||||
throw new IllegalStateException("Duplicate converter for version " + converter.version() + " of " + type.getSimpleName());
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a legacy format that is not JSON, stored strings matching the predicate are read by the reader.
|
||||
*/
|
||||
public ConfigStore<T> legacy(Predicate<String> matches, Function<String, ? extends VersionedConfig> reader) {
|
||||
legacyFormats.add(new LegacyFormat(matches, reader));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The version of stored JSON without a version field.
|
||||
*/
|
||||
public ConfigStore<T> unversioned(int version) {
|
||||
this.unversionedVersion = version;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds this store to the {@link UserConfig} with the given key, one config per player.
|
||||
*/
|
||||
public ConfigStore<T> userConfig(String key) {
|
||||
this.userConfigKey = key;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the config from the {@link UserConfig} instead of saving it, if the predicate matches.
|
||||
*/
|
||||
public ConfigStore<T> removeIf(Predicate<T> removeIf) {
|
||||
this.removeIf = removeIf;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds this store to a single file.
|
||||
*/
|
||||
public ConfigStore<T> file(File file) {
|
||||
this.file = file;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Legacy file to read, if the file of this store does not exist yet.
|
||||
*/
|
||||
public ConfigStore<T> legacyFile(File legacyFile, Function<String, ? extends VersionedConfig> reader) {
|
||||
this.legacyFile = legacyFile;
|
||||
this.legacyFileReader = reader;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a stored config and converts it to the current version.
|
||||
*
|
||||
* @throws RuntimeException if the config could not be read or converted
|
||||
*/
|
||||
public T read(String stored) {
|
||||
for (LegacyFormat legacyFormat : legacyFormats) {
|
||||
if (legacyFormat.matches().test(stored)) {
|
||||
return update(legacyFormat.reader().apply(stored));
|
||||
}
|
||||
}
|
||||
|
||||
JsonObject json = JsonParser.parseString(stored).getAsJsonObject();
|
||||
int storedVersion;
|
||||
if (json.has("version")) {
|
||||
storedVersion = json.get("version").getAsInt();
|
||||
} else if (unversionedVersion != null) {
|
||||
storedVersion = unversionedVersion;
|
||||
json.addProperty("version", storedVersion);
|
||||
} else {
|
||||
throw new IllegalStateException("Missing version of " + type.getSimpleName());
|
||||
}
|
||||
Class<? extends VersionedConfig> storedType = jsonVersions.get(storedVersion);
|
||||
if (storedType == null) {
|
||||
throw new IllegalStateException("Unknown version " + storedVersion + " of " + type.getSimpleName());
|
||||
}
|
||||
return update(gson.fromJson(json, storedType));
|
||||
}
|
||||
|
||||
public String write(T config) {
|
||||
return gson.toJson(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a config to the current version.
|
||||
*/
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public T update(VersionedConfig config) {
|
||||
while (config.getVersion() < version) {
|
||||
ConfigConverter converter = converters.get(config.getVersion());
|
||||
if (converter == null) {
|
||||
throw new IllegalStateException("No converter found for version " + config.getVersion() + " of " + type.getSimpleName());
|
||||
}
|
||||
VersionedConfig converted = converter.convert(config);
|
||||
if (converted.getVersion() <= config.getVersion()) {
|
||||
throw new IllegalStateException("Version did not increase after conversion of version " + config.getVersion() + " of " + type.getSimpleName());
|
||||
}
|
||||
config = converted;
|
||||
}
|
||||
if (!type.isInstance(config)) {
|
||||
throw new IllegalStateException("Version " + config.getVersion() + " is not the current version of " + type.getSimpleName());
|
||||
}
|
||||
return type.cast(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the config stored in the file, or the default config if the file is missing or empty
|
||||
* @throws RuntimeException if the config could not be read or converted
|
||||
*/
|
||||
public T load(File file) throws IOException {
|
||||
if (file.length() == 0) {
|
||||
return defaultConfig.get();
|
||||
}
|
||||
return read(Files.readString(file.toPath(), StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
public void save(File file, T config) throws IOException {
|
||||
Files.writeString(file.toPath(), write(config), StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the config of a player, loads it from the {@link UserConfig} and converts it if necessary.
|
||||
*/
|
||||
public T get(Player player) {
|
||||
requireUserConfig();
|
||||
return playerConfigs.computeIfAbsent(player.getUniqueId(), this::loadPlayer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the config of a player, this does not unload the config.
|
||||
*/
|
||||
public void save(Player player) {
|
||||
requireUserConfig();
|
||||
T config = playerConfigs.get(player.getUniqueId());
|
||||
if (config != null) {
|
||||
savePlayer(player.getUniqueId(), config);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save and unload the config of a player.
|
||||
*/
|
||||
public void unload(Player player) {
|
||||
if (userConfigKey == null) return;
|
||||
T config = playerConfigs.remove(player.getUniqueId());
|
||||
if (config != null) {
|
||||
savePlayer(player.getUniqueId(), config);
|
||||
}
|
||||
}
|
||||
|
||||
private T loadPlayer(UUID uuid) {
|
||||
String stored = UserConfig.getConfig(uuid, userConfigKey);
|
||||
if (stored == null) {
|
||||
return defaultConfig.get();
|
||||
}
|
||||
try {
|
||||
return read(stored);
|
||||
} catch (Exception e) {
|
||||
Bukkit.getLogger().log(Level.SEVERE, "Could not load " + type.getSimpleName() + " of " + uuid, e);
|
||||
return defaultConfig.get();
|
||||
}
|
||||
}
|
||||
|
||||
private void savePlayer(UUID uuid, T config) {
|
||||
if (removeIf.test(config)) {
|
||||
UserConfig.removePlayerConfig(uuid, userConfigKey);
|
||||
} else {
|
||||
UserConfig.updatePlayerConfig(uuid, userConfigKey, write(config));
|
||||
}
|
||||
}
|
||||
|
||||
private void requireUserConfig() {
|
||||
if (userConfigKey == null) {
|
||||
throw new IllegalStateException(type.getSimpleName() + " is not stored in the UserConfig");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the config of the file of this store, loads it and converts it if necessary.
|
||||
*/
|
||||
public T get() {
|
||||
requireFile();
|
||||
if (fileConfig == null) {
|
||||
fileConfig = loadFile();
|
||||
save();
|
||||
}
|
||||
return fileConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the config to the file of this store, if it was loaded.
|
||||
*/
|
||||
public void save() {
|
||||
requireFile();
|
||||
if (fileConfig == null) return;
|
||||
try {
|
||||
save(file, fileConfig);
|
||||
} catch (IOException e) {
|
||||
Bukkit.getLogger().log(Level.SEVERE, "Could not write " + file, e);
|
||||
}
|
||||
}
|
||||
|
||||
private T loadFile() {
|
||||
try {
|
||||
if (file.length() != 0) {
|
||||
return load(file);
|
||||
}
|
||||
if (legacyFile != null && legacyFile.length() != 0) {
|
||||
return update(legacyFileReader.apply(Files.readString(legacyFile.toPath(), StandardCharsets.UTF_8)));
|
||||
}
|
||||
return defaultConfig.get();
|
||||
} catch (Exception e) {
|
||||
Bukkit.getLogger().log(Level.SEVERE, "Could not load " + file + ", using default " + type.getSimpleName(), e);
|
||||
backupBrokenFile();
|
||||
return defaultConfig.get();
|
||||
}
|
||||
}
|
||||
|
||||
private void backupBrokenFile() {
|
||||
if (file.length() == 0) return;
|
||||
try {
|
||||
Files.copy(file.toPath(), new File(file.getParentFile(), file.getName() + ".broken").toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||
} catch (IOException e) {
|
||||
Bukkit.getLogger().log(Level.SEVERE, "Could not backup broken " + file, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void requireFile() {
|
||||
if (file == null) {
|
||||
throw new IllegalStateException(type.getSimpleName() + " is not bound to a file");
|
||||
}
|
||||
}
|
||||
|
||||
static void unloadAll(Player player) {
|
||||
STORES.forEach(store -> store.unload(player));
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves every loaded config and unloads the configs of the players.
|
||||
*/
|
||||
public static void saveAll() {
|
||||
for (ConfigStore<?> store : STORES) {
|
||||
if (store.userConfigKey != null) {
|
||||
store.playerConfigs.forEach((uuid, config) -> saveUnchecked(store, uuid, config));
|
||||
store.playerConfigs.clear();
|
||||
}
|
||||
if (store.file != null) {
|
||||
store.save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <T extends VersionedConfig> void saveUnchecked(ConfigStore<T> store, UUID uuid, Object config) {
|
||||
store.savePlayer(uuid, (T) config);
|
||||
}
|
||||
}
|
||||
+17
-9
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2025 SteamWar.de-Serverteam
|
||||
* Copyright (C) 2026 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
@@ -17,21 +17,29 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.core.events;
|
||||
package de.steamwar.bausystem.config;
|
||||
|
||||
import de.steamwar.core.Core;
|
||||
import de.steamwar.core.CRIUSleepEvent;
|
||||
import de.steamwar.linkage.Linked;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.AsyncPlayerChatEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
|
||||
/**
|
||||
* Saves and unloads the configs of the {@link ConfigStore}s bound to the UserConfig
|
||||
* when a player quits, configs are loaded on first use.
|
||||
*/
|
||||
@Linked
|
||||
public class ChattingEvent implements Listener {
|
||||
public class ConfigStoreListener implements Listener {
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
private void onChat(AsyncPlayerChatEvent event) {
|
||||
event.setCancelled(true);
|
||||
Core.MESSAGE.broadcastPrefixless("LOCAL_CHAT", event.getPlayer().getDisplayName(), event.getMessage());
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void onPlayerQuit(PlayerQuitEvent event) {
|
||||
ConfigStore.unloadAll(event.getPlayer());
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onCRIUSleep(CRIUSleepEvent event) {
|
||||
ConfigStore.saveAll();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2026 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.bausystem.config;
|
||||
|
||||
/**
|
||||
* Common interface of every version of a config stored by a {@link ConfigStore},
|
||||
* the current version (e.g. Config) and every older version (ConfigV1, ConfigV2, ...).
|
||||
*/
|
||||
public interface VersionedConfig {
|
||||
|
||||
int getVersion();
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2026 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.bausystem.config.legacy;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
import org.bukkit.configuration.serialization.ConfigurationSerialization;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Deserializes {@link ItemStack}s that were serialized by YAPION, read with {@link YapionReader}.
|
||||
*/
|
||||
@UtilityClass
|
||||
@Deprecated
|
||||
public class YapionItemDeserializer {
|
||||
|
||||
private static final String TYPE_IDENTIFIER = "@type";
|
||||
|
||||
public ItemStack deserializeItem(Object yapion) {
|
||||
if (yapion == null) return null;
|
||||
Object result = deserialize(yapion);
|
||||
if (result instanceof ItemStack itemStack) return itemStack;
|
||||
throw new IllegalArgumentException("Not an ItemStack: " + result);
|
||||
}
|
||||
|
||||
private Object deserialize(Object yapion) {
|
||||
if (yapion instanceof List<?> list) {
|
||||
List<Object> result = new ArrayList<>();
|
||||
list.forEach(o -> result.add(deserialize(o)));
|
||||
return result;
|
||||
}
|
||||
if (yapion instanceof YapionReader.YapionMap map) {
|
||||
Map<Object, Object> result = new LinkedHashMap<>();
|
||||
map.entries().forEach((key, value) -> result.put(deserialize(key), deserialize(value)));
|
||||
return result;
|
||||
}
|
||||
if (yapion instanceof Map<?, ?> object) {
|
||||
return deserializeObject(object);
|
||||
}
|
||||
return yapion;
|
||||
}
|
||||
|
||||
private Object deserializeObject(Map<?, ?> object) {
|
||||
Map<String, Object> data = new LinkedHashMap<>();
|
||||
object.forEach((key, value) -> {
|
||||
if (!TYPE_IDENTIFIER.equals(key)) data.put((String) key, deserialize(value));
|
||||
});
|
||||
|
||||
if (!(object.get(TYPE_IDENTIFIER) instanceof String type)) {
|
||||
return data;
|
||||
}
|
||||
|
||||
Class<?> javaType = javaUtilClass(type);
|
||||
if (javaType != null && Collection.class.isAssignableFrom(javaType)) {
|
||||
return data.getOrDefault("values", new ArrayList<>());
|
||||
}
|
||||
if (javaType != null && Map.class.isAssignableFrom(javaType)) {
|
||||
return data.containsKey("values") ? data.get("values") : data;
|
||||
}
|
||||
|
||||
data.put(ConfigurationSerialization.SERIALIZED_TYPE_KEY, type.equals("org.bukkit.inventory.meta.ItemMeta") ? "ItemMeta" : type);
|
||||
return ConfigurationSerialization.deserializeObject(data);
|
||||
}
|
||||
|
||||
private Class<?> javaUtilClass(String type) {
|
||||
if (!type.startsWith("java.util.")) return null;
|
||||
try {
|
||||
return Class.forName(type);
|
||||
} catch (ClassNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2026 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.bausystem.config.legacy;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Minimal reader for the legacy YAPION format, only used to migrate old data.
|
||||
* <p>
|
||||
* The result is built from plain java objects:
|
||||
* <ul>
|
||||
* <li>YAPION objects {@code {key(value)}} become a {@link LinkedHashMap} of String to value</li>
|
||||
* <li>YAPION arrays {@code [a,b]} become a {@link List}</li>
|
||||
* <li>YAPION maps {@code <(a):(b)>} become a {@link YapionMap}</li>
|
||||
* <li>YAPION values {@code (value)} become null, Boolean, Number or String</li>
|
||||
* </ul>
|
||||
*/
|
||||
@Deprecated
|
||||
public class YapionReader {
|
||||
|
||||
private static final Pattern INTEGER = Pattern.compile("[-+]?\\d+");
|
||||
private static final Pattern DECIMAL = Pattern.compile("[-+]?(\\d+\\.\\d*|\\.\\d+|\\d+)([eE][-+]?\\d+)?");
|
||||
|
||||
public record YapionMap(Map<Object, Object> entries) {
|
||||
}
|
||||
|
||||
public static Map<String, Object> parse(String yapion) {
|
||||
YapionReader reader = new YapionReader(yapion);
|
||||
reader.skipWhitespace();
|
||||
Map<String, Object> result = reader.readObject();
|
||||
reader.skipWhitespace();
|
||||
if (reader.index != yapion.length()) {
|
||||
throw reader.error("Unexpected trailing content");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static double getDouble(Map<?, ?> object, String key, double defaultValue) {
|
||||
return object.get(key) instanceof Number value ? value.doubleValue() : defaultValue;
|
||||
}
|
||||
|
||||
public static int getInt(Map<?, ?> object, String key, int defaultValue) {
|
||||
return object.get(key) instanceof Number value ? value.intValue() : defaultValue;
|
||||
}
|
||||
|
||||
public static boolean getBoolean(Map<?, ?> object, String key, boolean defaultValue) {
|
||||
return object.get(key) instanceof Boolean value ? value : defaultValue;
|
||||
}
|
||||
|
||||
public static String getString(Map<?, ?> object, String key, String defaultValue) {
|
||||
return object.get(key) instanceof String value ? value : defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the entries of the array that are YAPION objects
|
||||
*/
|
||||
public static List<Map<?, ?>> getObjects(Map<?, ?> object, String key) {
|
||||
List<Map<?, ?>> objects = new ArrayList<>();
|
||||
if (object.get(key) instanceof List<?> array) {
|
||||
array.forEach(o -> {
|
||||
if (o instanceof Map<?, ?> map) objects.add(map);
|
||||
});
|
||||
}
|
||||
return objects;
|
||||
}
|
||||
|
||||
private final String input;
|
||||
private int index = 0;
|
||||
|
||||
private YapionReader(String input) {
|
||||
this.input = input;
|
||||
}
|
||||
|
||||
private Object readAny() {
|
||||
return switch (peek()) {
|
||||
case '{' -> readObject();
|
||||
case '[' -> readArray();
|
||||
case '<' -> readMap();
|
||||
case '(' -> readValue();
|
||||
default -> throw error("Expected '{', '[', '<' or '('");
|
||||
};
|
||||
}
|
||||
|
||||
private Map<String, Object> readObject() {
|
||||
expect('{');
|
||||
Map<String, Object> object = new LinkedHashMap<>();
|
||||
while (true) {
|
||||
skipWhitespace();
|
||||
if (peek() == '}') {
|
||||
index++;
|
||||
return object;
|
||||
}
|
||||
StringBuilder key = new StringBuilder();
|
||||
while (true) {
|
||||
char c = peek();
|
||||
if (c == '(' || c == '{' || c == '[' || c == '<') break;
|
||||
if (c == '}') throw error("Key without value");
|
||||
key.append(readChar());
|
||||
}
|
||||
object.put(key.toString(), readAny());
|
||||
}
|
||||
}
|
||||
|
||||
private List<Object> readArray() {
|
||||
expect('[');
|
||||
List<Object> array = new ArrayList<>();
|
||||
skipWhitespace();
|
||||
if (peek() == ']') {
|
||||
index++;
|
||||
return array;
|
||||
}
|
||||
while (true) {
|
||||
int start = index;
|
||||
skipWhitespace();
|
||||
char c = peek();
|
||||
if (c == '{' || c == '[' || c == '<' || c == '(') {
|
||||
array.add(readAny());
|
||||
skipWhitespace();
|
||||
} else {
|
||||
// Whitespace is part of plain values
|
||||
index = start;
|
||||
StringBuilder value = new StringBuilder();
|
||||
while (peek() != ',' && peek() != ']') {
|
||||
value.append(readChar());
|
||||
}
|
||||
array.add(typed(value.toString()));
|
||||
}
|
||||
char separator = input.charAt(index++);
|
||||
if (separator == ']') return array;
|
||||
if (separator != ',') throw error("Expected ',' or ']'");
|
||||
}
|
||||
}
|
||||
|
||||
private YapionMap readMap() {
|
||||
expect('<');
|
||||
Map<Object, Object> map = new LinkedHashMap<>();
|
||||
while (true) {
|
||||
skipWhitespace();
|
||||
if (peek() == '>') {
|
||||
index++;
|
||||
return new YapionMap(map);
|
||||
}
|
||||
Object key = readAny();
|
||||
skipWhitespace();
|
||||
expect(':');
|
||||
skipWhitespace();
|
||||
map.put(key, readAny());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* YAPION does not escape parentheses in values, balanced parentheses are part of the value.
|
||||
* A backslash only escapes a backslash, a unicode sequence or a control character, otherwise it is kept.
|
||||
*/
|
||||
private Object readValue() {
|
||||
expect('(');
|
||||
StringBuilder value = new StringBuilder();
|
||||
int depth = 0;
|
||||
while (true) {
|
||||
char c = peek();
|
||||
if (c == '\\') {
|
||||
value.append(readValueEscape());
|
||||
continue;
|
||||
}
|
||||
index++;
|
||||
if (c == '(') {
|
||||
depth++;
|
||||
} else if (c == ')') {
|
||||
if (depth == 0) break;
|
||||
depth--;
|
||||
}
|
||||
value.append(c);
|
||||
}
|
||||
return typed(value.toString());
|
||||
}
|
||||
|
||||
private String readValueEscape() {
|
||||
index++;
|
||||
if (index >= input.length()) return "\\";
|
||||
char escaped = input.charAt(index);
|
||||
switch (escaped) {
|
||||
case '\\', '(', ')':
|
||||
index++;
|
||||
return String.valueOf(escaped);
|
||||
case 'n':
|
||||
index++;
|
||||
return "\n";
|
||||
case 't':
|
||||
index++;
|
||||
return "\t";
|
||||
case 'r':
|
||||
index++;
|
||||
return "\r";
|
||||
case 'u':
|
||||
if (index + 5 <= input.length() && input.substring(index + 1, index + 5).matches("[0-9a-fA-F]{4}")) {
|
||||
char unicode = (char) Integer.parseInt(input.substring(index + 1, index + 5), 16);
|
||||
index += 5;
|
||||
return String.valueOf(unicode);
|
||||
}
|
||||
return "\\";
|
||||
default:
|
||||
return "\\";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads one character of a key or plain array value, resolving backslash escapes.
|
||||
* A backslash that does not escape anything is kept, as the old player config
|
||||
* collapsed escaped backslashes into a single one.
|
||||
*/
|
||||
private char readChar() {
|
||||
char c = input.charAt(index++);
|
||||
if (c != '\\') return c;
|
||||
if (index >= input.length()) throw error("Unterminated escape");
|
||||
char escaped = input.charAt(index);
|
||||
switch (escaped) {
|
||||
case 'n':
|
||||
index++;
|
||||
return '\n';
|
||||
case 't':
|
||||
index++;
|
||||
return '\t';
|
||||
case 'r':
|
||||
index++;
|
||||
return '\r';
|
||||
case 'u':
|
||||
if (index + 5 <= input.length() && input.substring(index + 1, index + 5).matches("[0-9a-fA-F]{4}")) {
|
||||
char unicode = (char) Integer.parseInt(input.substring(index + 1, index + 5), 16);
|
||||
index += 5;
|
||||
return unicode;
|
||||
}
|
||||
return '\\';
|
||||
case '\\', ',', ' ', '(', ')', '{', '}', '[', ']', '<', '>', '"', ':', '@':
|
||||
index++;
|
||||
return escaped;
|
||||
default:
|
||||
return '\\';
|
||||
}
|
||||
}
|
||||
|
||||
private static Object typed(String value) {
|
||||
if (value.equals("null")) return null;
|
||||
if (value.equals("true")) return true;
|
||||
if (value.equals("false")) return false;
|
||||
if (value.length() >= 2 && value.startsWith("\"") && value.endsWith("\"")) {
|
||||
return value.substring(1, value.length() - 1);
|
||||
}
|
||||
try {
|
||||
if (INTEGER.matcher(value).matches()) {
|
||||
long l = Long.parseLong(value);
|
||||
if (l >= Integer.MIN_VALUE && l <= Integer.MAX_VALUE) return (int) l;
|
||||
return l;
|
||||
}
|
||||
if (value.length() > 1) {
|
||||
String number = value.substring(0, value.length() - 1);
|
||||
switch (value.charAt(value.length() - 1)) {
|
||||
case 'L':
|
||||
if (INTEGER.matcher(number).matches()) return Long.parseLong(number);
|
||||
break;
|
||||
case 'S':
|
||||
if (INTEGER.matcher(number).matches()) return Short.parseShort(number);
|
||||
break;
|
||||
case 'B':
|
||||
if (INTEGER.matcher(number).matches()) return Byte.parseByte(number);
|
||||
break;
|
||||
case 'F':
|
||||
if (DECIMAL.matcher(number).matches()) return Float.parseFloat(number);
|
||||
break;
|
||||
case 'D':
|
||||
if (DECIMAL.matcher(number).matches()) return Double.parseDouble(number);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (DECIMAL.matcher(value).matches()) return Double.parseDouble(value);
|
||||
} catch (NumberFormatException e) {
|
||||
// Not a number, fall through to String
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private void skipWhitespace() {
|
||||
while (index < input.length() && Character.isWhitespace(input.charAt(index))) {
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
private char peek() {
|
||||
if (index >= input.length()) throw error("Unexpected end of input");
|
||||
return input.charAt(index);
|
||||
}
|
||||
|
||||
private void expect(char c) {
|
||||
if (peek() != c) throw error("Expected '" + c + "'");
|
||||
index++;
|
||||
}
|
||||
|
||||
private IllegalArgumentException error(String message) {
|
||||
return new IllegalArgumentException(message + " at index " + index);
|
||||
}
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2026 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.bausystem.config.serializer;
|
||||
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonToken;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Base64;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
* Stores an {@link ItemStack} as Base64 of {@link ItemStack#serializeAsBytes()}, which
|
||||
* contains the data version and gets upgraded on newer Minecraft versions.
|
||||
* Empty items are stored as null, items that can't be read are deserialized as null.
|
||||
*/
|
||||
public class ItemStackTypeAdapter extends TypeAdapter<ItemStack> {
|
||||
|
||||
@Override
|
||||
public void write(JsonWriter out, ItemStack itemStack) throws IOException {
|
||||
if (itemStack == null || itemStack.isEmpty()) {
|
||||
out.nullValue();
|
||||
return;
|
||||
}
|
||||
out.value(Base64.getEncoder().encodeToString(itemStack.serializeAsBytes()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack read(JsonReader in) throws IOException {
|
||||
if (in.peek() == JsonToken.NULL) {
|
||||
in.nextNull();
|
||||
return null;
|
||||
}
|
||||
String value = in.nextString();
|
||||
try {
|
||||
return ItemStack.deserializeBytes(Base64.getDecoder().decode(value));
|
||||
} catch (Exception e) {
|
||||
Bukkit.getLogger().log(Level.WARNING, "Could not deserialize ItemStack", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2025 SteamWar.de-Serverteam
|
||||
* Copyright (C) 2026 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
@@ -19,129 +19,77 @@
|
||||
|
||||
package de.steamwar.bausystem.configplayer;
|
||||
|
||||
import de.steamwar.bausystem.configplayer.serializer.ConfigurationSerializableSerializer;
|
||||
import de.steamwar.linkage.Linked;
|
||||
import de.steamwar.sql.UserConfig;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import de.steamwar.bausystem.config.ConfigConverter;
|
||||
import de.steamwar.bausystem.config.ConfigStore;
|
||||
import de.steamwar.bausystem.config.VersionedConfig;
|
||||
import de.steamwar.bausystem.config.serializer.ItemStackTypeAdapter;
|
||||
import de.steamwar.bausystem.configplayer.converter.ConfigV1ToV2Converter;
|
||||
import de.steamwar.bausystem.features.hotbar.DefaultHotbar;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
import yapion.hierarchy.output.StringOutput;
|
||||
import yapion.hierarchy.types.YAPIONObject;
|
||||
import yapion.parser.YAPIONParser;
|
||||
import yapion.serializing.SerializeManager;
|
||||
import lombok.Setter;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
|
||||
@Linked
|
||||
public class Config implements Listener {
|
||||
/**
|
||||
* The current player config. It is stored as JSON, fields missing in the
|
||||
* stored JSON keep the default value of their initializer, so adding a new
|
||||
* field does not need a new version.
|
||||
* <p>
|
||||
* When a field gets removed, renamed or its meaning changes, copy this class
|
||||
* to ConfigV{@link #VERSION} (without the {@link #STORE}), increase {@link #VERSION},
|
||||
* write a {@link ConfigConverter} from the copy to this class and register it in the {@link #STORE}.
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class Config implements VersionedConfig {
|
||||
|
||||
static {
|
||||
SerializeManager.add(new ConfigurationSerializableSerializer());
|
||||
}
|
||||
public static final int VERSION = 2;
|
||||
|
||||
@Getter
|
||||
private static Config instance;
|
||||
public static final ConfigStore<Config> STORE = new ConfigStore<>(Config.class, VERSION, Config::new)
|
||||
.gson(new GsonBuilder().registerTypeHierarchyAdapter(ItemStack.class, new ItemStackTypeAdapter()).create())
|
||||
.legacy(s -> s.startsWith("{@version(1)"), ConfigV1::parse)
|
||||
.converter(new ConfigV1ToV2Converter())
|
||||
.userConfig("bausystem");
|
||||
|
||||
{
|
||||
instance = this;
|
||||
}
|
||||
|
||||
private final Map<UUID, YAPIONObject> playerConfigurations = new HashMap<>();
|
||||
|
||||
private static final Map<Integer, ConfigConverter> CONFIG_CONVERTER_MAP = new HashMap<>();
|
||||
|
||||
public static void addConfigConverter(ConfigConverter configConverter) {
|
||||
CONFIG_CONVERTER_MAP.putIfAbsent(configConverter.version(), configConverter);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||
get(event.getPlayer());
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerQuit(PlayerQuitEvent event) {
|
||||
save(event.getPlayer());
|
||||
playerConfigurations.remove(event.getPlayer().getUniqueId());
|
||||
}
|
||||
@Setter(AccessLevel.NONE)
|
||||
protected int version = VERSION;
|
||||
|
||||
/**
|
||||
* Get a PlayerConfig, optionally loads it from the DataBase and migrates it if necessary.
|
||||
*
|
||||
* @param player the player from whom to get the config.
|
||||
* @return the config object
|
||||
* BauGui item id to inventory slot
|
||||
*/
|
||||
public YAPIONObject get(Player player) {
|
||||
UUID uuid = player.getUniqueId();
|
||||
if (!playerConfigurations.containsKey(uuid)) {
|
||||
String s = UserConfig.getConfig(uuid, "bausystem");
|
||||
YAPIONObject yapionObject;
|
||||
if (s == null) {
|
||||
yapionObject = ConfigCreator.createDefaultConfig();
|
||||
} else {
|
||||
yapionObject = YAPIONParser.parse(s);
|
||||
}
|
||||
yapionObject = update(yapionObject);
|
||||
playerConfigurations.put(uuid, yapionObject);
|
||||
return yapionObject;
|
||||
}
|
||||
return playerConfigurations.get(uuid);
|
||||
}
|
||||
|
||||
public void saveAll() {
|
||||
playerConfigurations.forEach((uuid, yapionObject) -> {
|
||||
String string = yapionObject.toYAPION(new StringOutput()).getResult().replaceAll("\\+", "\\");
|
||||
UserConfig.updatePlayerConfig(uuid, "bausystem", string);
|
||||
});
|
||||
playerConfigurations.clear();
|
||||
}
|
||||
protected Map<Integer, Integer> bauGui = defaultBauGui();
|
||||
protected int bauGuiSize = 45;
|
||||
|
||||
/**
|
||||
* Save a PlayerConfig, this does not remove the key value mapping from the map.
|
||||
*
|
||||
* @param player the player to save the config.
|
||||
* Slots 0-8 are the hotbar, slots 9-12 the armor contents
|
||||
*/
|
||||
public void save(Player player) {
|
||||
UUID uuid = player.getUniqueId();
|
||||
if (playerConfigurations.containsKey(uuid)) {
|
||||
YAPIONObject yapionObject = playerConfigurations.get(uuid);
|
||||
String string = yapionObject.toYAPION(new StringOutput()).getResult().replaceAll("\\\\+", "\\\\");
|
||||
UserConfig.updatePlayerConfig(uuid, "bausystem", string);
|
||||
}
|
||||
}
|
||||
protected ItemStack[] hotbar = DefaultHotbar.defaultHotbar();
|
||||
|
||||
private YAPIONObject update(YAPIONObject yapionObject) {
|
||||
int version = yapionObject.getPlainValue("@version");
|
||||
while (version < ConfigCreator.currentVersion) {
|
||||
ConfigConverter configConverter = CONFIG_CONVERTER_MAP.getOrDefault(version, null);
|
||||
if (configConverter == null) {
|
||||
Bukkit.getLogger().log(Level.SEVERE, "No updater found for version " + version);
|
||||
return ConfigCreator.createDefaultConfig();
|
||||
}
|
||||
try {
|
||||
configConverter.update(yapionObject);
|
||||
} catch (Exception e) {
|
||||
Bukkit.getLogger().log(Level.SEVERE, e.getMessage(), e);
|
||||
return ConfigCreator.createDefaultConfig();
|
||||
}
|
||||
int newVersion = yapionObject.getPlainValue("@version");
|
||||
if (version == newVersion) {
|
||||
Bukkit.getLogger().log(Level.SEVERE, "Version Tag was the same after conversion");
|
||||
return ConfigCreator.createDefaultConfig();
|
||||
}
|
||||
if (newVersion < version) {
|
||||
Bukkit.getLogger().log(Level.SEVERE, "Version Tag was earlier after conversion");
|
||||
return ConfigCreator.createDefaultConfig();
|
||||
}
|
||||
version = newVersion;
|
||||
}
|
||||
return yapionObject;
|
||||
protected boolean nightVision = false;
|
||||
protected boolean tntClickDetails = true;
|
||||
protected boolean detonatorAutostart = false;
|
||||
protected boolean inventoryFill = false;
|
||||
protected boolean rayVisualizer = false;
|
||||
protected boolean smartPlace = false;
|
||||
protected boolean blockCounter = false;
|
||||
protected boolean depthMessage = true;
|
||||
|
||||
public static Map<Integer, Integer> defaultBauGui() {
|
||||
// 0: ? | 1: ? | 2: 10 | 3: 3 | 4: 7 | 5: 17 | 6: 15 | 7: ? | 8: ?
|
||||
// 9: 5 | 10: ? | 11: ? | 12: ? | 13: ? | 14: ? | 15: ? | 16: ? | 17: 16
|
||||
// 18: 4 | 19: ? | 20: 19 | 21: 21 | 22: 9 | 23: 0 | 24: 1 | 25: ? | 26: 11
|
||||
// 27: 6 | 28: ? | 29: ? | 30: ? | 31: ? | 32: ? | 33: ? | 34: ? | 35: 18
|
||||
// 36: ? | 37: 23 | 38: 20 | 39: 8 | 40: 22 | 41: 26 | 42: 12 | 43: 14 | 44: ?
|
||||
Map<Integer, Integer> bauGui = new HashMap<>();
|
||||
bauGui.put(10, 2); bauGui.put(3, 3); bauGui.put(7, 4); bauGui.put(17, 5); bauGui.put(15, 6);
|
||||
bauGui.put(5, 9); bauGui.put(4, 18); bauGui.put(6, 27);
|
||||
bauGui.put(16, 17); bauGui.put(11, 26); bauGui.put(18, 35);
|
||||
bauGui.put(19, 20); bauGui.put(21, 21); bauGui.put(9, 22); bauGui.put(0, 23); bauGui.put(1, 24);
|
||||
bauGui.put(23, 37); bauGui.put(20, 38); bauGui.put(8, 39); bauGui.put(22, 40); bauGui.put(26, 41); bauGui.put(12, 42); bauGui.put(14, 43);
|
||||
return bauGui;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2025 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.bausystem.configplayer;
|
||||
|
||||
import yapion.hierarchy.types.YAPIONObject;
|
||||
|
||||
/**
|
||||
* A new {@link ConfigConverter} should be written when you remove anything
|
||||
* from the Config or modify any mayor part. When you move anything from
|
||||
* any key to any other key you should write a new {@link ConfigConverter}.
|
||||
* For adding any new key you should be able to get the default without
|
||||
* having it in the Config. Anything you need to change you should also
|
||||
* change the {@link ConfigCreator} accordingly, to produce the new Config.
|
||||
*/
|
||||
public interface ConfigConverter {
|
||||
|
||||
/**
|
||||
* This describes the version this Converter can convert from. The version
|
||||
* it should convert to is the version 1 above this number. But this is not
|
||||
* a necessity. In the config Object as parameter given in {@link #update(YAPIONObject)}
|
||||
* you should update the <b>@version</b> variable in the root object to the
|
||||
* new version this converter produced.
|
||||
*
|
||||
* @return the version number
|
||||
*/
|
||||
int version();
|
||||
|
||||
/**
|
||||
* This method should update everything needed to go from a lower config
|
||||
* version to a higher. It should update the <b>@version</b> variable
|
||||
* accordingly. Anything else is up the implementation. If anything goes wrong
|
||||
* do not silently exit this method, throw an Exception. The updater Code will
|
||||
* deal with it. Never leave the inputted object in a corrupted state.
|
||||
*
|
||||
* @param config the config object to update
|
||||
*/
|
||||
void update(YAPIONObject config);
|
||||
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2025 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.bausystem.configplayer;
|
||||
|
||||
import de.steamwar.bausystem.features.hotbar.DefaultHotbar;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import yapion.hierarchy.types.YAPIONObject;
|
||||
|
||||
@UtilityClass
|
||||
public class ConfigCreator {
|
||||
|
||||
public static final int currentVersion = 1;
|
||||
|
||||
public YAPIONObject createDefaultConfig() {
|
||||
YAPIONObject yapionObject = new YAPIONObject();
|
||||
// This call should never be touched
|
||||
yapionObject.add("@version", currentVersion);
|
||||
|
||||
// Any initialising goes into here
|
||||
yapionObject.add("baugui", defaultBauGui());
|
||||
|
||||
// Default Hotbar Gui
|
||||
yapionObject.add("hotbar", DefaultHotbar.defaultHotbar());
|
||||
return yapionObject;
|
||||
}
|
||||
|
||||
private YAPIONObject defaultBauGui() {
|
||||
YAPIONObject yapionObject = new YAPIONObject();
|
||||
|
||||
// 0: ? | 1: ? | 2: 10 | 3: 3 | 4: 7 | 5: 17 | 6: 15 | 7: ? | 8: ?
|
||||
// 9: 5 | 10: ? | 11: ? | 12: ? | 13: ? | 14: ? | 15: ? | 16: ? | 17: 16
|
||||
// 18: 4 | 19: ? | 20: 19 | 21: 21 | 22: 9 | 23: 0 | 24: 1 | 25: ? | 26: 11
|
||||
// 27: 6 | 28: ? | 29: ? | 30: ? | 31: ? | 32: ? | 33: ? | 34: ? | 35: 18
|
||||
// 36: ? | 37: 23 | 38: 20 | 39: 8 | 40: 22 | 41: 26 | 42: 12 | 43: 14 | 44: ?
|
||||
|
||||
yapionObject.add("10", 2).add("3", 3).add("7", 4).add("17", 5).add("15", 6);
|
||||
yapionObject.add("5", 9).add("4", 18).add("6", 27);
|
||||
yapionObject.add("16", 17).add("11", 26).add("18", 35);
|
||||
yapionObject.add("19", 20).add("21", 21).add("9", 22).add("0", 23).add("1", 24);
|
||||
yapionObject.add("23", 37).add("20", 38).add("8", 39).add("22", 40).add("26", 41).add("12", 42).add("14", 43);
|
||||
yapionObject.add("size", 45);
|
||||
return yapionObject;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2026 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.bausystem.configplayer;
|
||||
|
||||
import de.steamwar.bausystem.config.VersionedConfig;
|
||||
import de.steamwar.bausystem.config.legacy.YapionReader;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* The legacy config that was stored in the YAPION format.
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class ConfigV1 implements VersionedConfig {
|
||||
|
||||
public static final int VERSION = 1;
|
||||
|
||||
@Setter(AccessLevel.NONE)
|
||||
protected int version = VERSION;
|
||||
|
||||
/**
|
||||
* BauGui item id to inventory slot
|
||||
*/
|
||||
protected Map<Integer, Integer> bauGui = new HashMap<>();
|
||||
protected int bauGuiSize = 45;
|
||||
|
||||
/**
|
||||
* Minecraft version to the hotbar serialized by YAPION, read with {@link YapionReader}.
|
||||
* Use {@link de.steamwar.bausystem.config.legacy.YapionItemDeserializer} to get the items.
|
||||
*/
|
||||
protected Map<Integer, List<?>> hotbars = new HashMap<>();
|
||||
|
||||
protected boolean nightVision = false;
|
||||
protected boolean tntClickDetails = true;
|
||||
protected boolean detonatorAutostart = false;
|
||||
protected boolean inventoryFill = false;
|
||||
protected boolean rayVisualizer = false;
|
||||
protected boolean smartPlace = false;
|
||||
protected boolean blockCounter = false;
|
||||
protected boolean depthMessage = true;
|
||||
|
||||
public static ConfigV1 parse(String yapion) {
|
||||
Map<String, Object> object = YapionReader.parse(yapion);
|
||||
ConfigV1 config = new ConfigV1();
|
||||
config.version = (Integer) object.get("@version");
|
||||
|
||||
if (object.get("baugui") instanceof Map<?, ?> bauGui) {
|
||||
bauGui.forEach((key, value) -> {
|
||||
if (key.equals("size")) {
|
||||
config.bauGuiSize = (Integer) value;
|
||||
} else {
|
||||
config.bauGui.put(Integer.parseInt((String) key), (Integer) value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
object.forEach((key, value) -> {
|
||||
if (!(value instanceof List<?> hotbar)) return;
|
||||
if (key.equals("hotbar")) {
|
||||
// Hotbars were stored without version prior to 1.19
|
||||
config.hotbars.putIfAbsent(19, hotbar);
|
||||
} else if (key.startsWith("hotbar-")) {
|
||||
config.hotbars.put(Integer.parseInt(key.substring("hotbar-".length())), hotbar);
|
||||
}
|
||||
});
|
||||
|
||||
config.nightVision = getBoolean(object, "nightvision", config.nightVision);
|
||||
config.tntClickDetails = getBoolean(object, "tnt_click_details", config.tntClickDetails);
|
||||
config.detonatorAutostart = getBoolean(object, "detonator-autostart", config.detonatorAutostart);
|
||||
config.inventoryFill = getBoolean(object, "inventoryfill", config.inventoryFill);
|
||||
config.rayVisualizer = getBoolean(object, "rayvisualizer", config.rayVisualizer);
|
||||
config.smartPlace = getBoolean(object, "smartPlace", config.smartPlace);
|
||||
config.blockCounter = getBoolean(object, "blockCounter", config.blockCounter);
|
||||
config.depthMessage = getBoolean(object, "depth_message", config.depthMessage);
|
||||
return config;
|
||||
}
|
||||
|
||||
private static boolean getBoolean(Map<String, Object> object, String key, boolean defaultValue) {
|
||||
return object.get(key) instanceof Boolean value ? value : defaultValue;
|
||||
}
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2026 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.bausystem.configplayer.converter;
|
||||
|
||||
import de.steamwar.bausystem.config.ConfigConverter;
|
||||
import de.steamwar.bausystem.config.legacy.YapionItemDeserializer;
|
||||
import de.steamwar.bausystem.configplayer.Config;
|
||||
import de.steamwar.bausystem.configplayer.ConfigV1;
|
||||
import de.steamwar.core.Core;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
|
||||
@Deprecated
|
||||
public class ConfigV1ToV2Converter implements ConfigConverter<ConfigV1, Config> {
|
||||
|
||||
@Override
|
||||
public int version() {
|
||||
return ConfigV1.VERSION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Config convert(ConfigV1 old) {
|
||||
Config config = new Config();
|
||||
config.setBauGui(new HashMap<>(old.getBauGui()));
|
||||
config.setBauGuiSize(old.getBauGuiSize());
|
||||
|
||||
List<?> hotbar = old.getHotbars().get(Core.getVersion());
|
||||
if (hotbar != null) {
|
||||
config.setHotbar(convertHotbar(hotbar));
|
||||
}
|
||||
|
||||
config.setNightVision(old.isNightVision());
|
||||
config.setTntClickDetails(old.isTntClickDetails());
|
||||
config.setDetonatorAutostart(old.isDetonatorAutostart());
|
||||
config.setInventoryFill(old.isInventoryFill());
|
||||
config.setRayVisualizer(old.isRayVisualizer());
|
||||
config.setSmartPlace(old.isSmartPlace());
|
||||
config.setBlockCounter(old.isBlockCounter());
|
||||
config.setDepthMessage(old.isDepthMessage());
|
||||
return config;
|
||||
}
|
||||
|
||||
private ItemStack[] convertHotbar(List<?> hotbar) {
|
||||
ItemStack[] items = new ItemStack[13];
|
||||
for (int i = 0; i < Math.min(hotbar.size(), items.length); i++) {
|
||||
try {
|
||||
items[i] = YapionItemDeserializer.deserializeItem(hotbar.get(i));
|
||||
} catch (Exception e) {
|
||||
Bukkit.getLogger().log(Level.WARNING, "Could not convert hotbar item " + i, e);
|
||||
}
|
||||
}
|
||||
return items;
|
||||
}
|
||||
}
|
||||
-90
@@ -1,90 +0,0 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2025 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.bausystem.configplayer.serializer;
|
||||
|
||||
import org.bukkit.configuration.serialization.ConfigurationSerializable;
|
||||
import org.bukkit.configuration.serialization.ConfigurationSerialization;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import yapion.hierarchy.api.groups.YAPIONAnyType;
|
||||
import yapion.hierarchy.types.YAPIONObject;
|
||||
import yapion.serializing.api.SerializerObject;
|
||||
import yapion.serializing.data.DeserializeData;
|
||||
import yapion.serializing.data.SerializeData;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static yapion.utils.IdentifierUtils.TYPE_IDENTIFIER;
|
||||
|
||||
public class ConfigurationSerializableSerializer extends SerializerObject<ConfigurationSerializable> {
|
||||
|
||||
@Override
|
||||
public Class<ConfigurationSerializable> type() {
|
||||
return ConfigurationSerializable.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInterface() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public YAPIONObject serialize(SerializeData<ConfigurationSerializable> serializeData) {
|
||||
YAPIONObject yapionObject = new YAPIONObject();
|
||||
yapionObject.add(TYPE_IDENTIFIER, serializeData.object.getClass().getTypeName());
|
||||
if (serializeData.object instanceof ItemStack) {
|
||||
yapionObject.add(TYPE_IDENTIFIER, ItemStack.class.getTypeName());
|
||||
}
|
||||
if (serializeData.object instanceof ItemMeta) {
|
||||
yapionObject.add(TYPE_IDENTIFIER, ItemMeta.class.getTypeName());
|
||||
}
|
||||
Map<String, Object> serializeDataMap = serializeData.object.serialize();
|
||||
serializeDataMap.forEach((s, o) -> {
|
||||
YAPIONAnyType yapionAnyType = serializeData.serialize(o);
|
||||
if (yapionAnyType instanceof YAPIONObject) {
|
||||
YAPIONObject object = (YAPIONObject) yapionAnyType;
|
||||
if (object.containsKey(TYPE_IDENTIFIER) && object.getPlainValue(TYPE_IDENTIFIER).equals("com.google.common.collect.RegularImmutableList")) {
|
||||
object.put(TYPE_IDENTIFIER, ArrayList.class.getTypeName());
|
||||
}
|
||||
}
|
||||
yapionObject.add(s, yapionAnyType);
|
||||
});
|
||||
return yapionObject;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigurationSerializable deserialize(DeserializeData<YAPIONObject> deserializeData) {
|
||||
Map<String, Object> deserializeDataMap = new HashMap<>();
|
||||
deserializeData.object.forEach((s, yapionAnyType) -> {
|
||||
if (s.equals(TYPE_IDENTIFIER)) {
|
||||
if (yapionAnyType.toString().equals("(org.bukkit.inventory.meta.ItemMeta)")) {
|
||||
deserializeDataMap.put("==", "ItemMeta");
|
||||
} else {
|
||||
deserializeDataMap.put("==", deserializeData.deserialize(yapionAnyType));
|
||||
}
|
||||
return;
|
||||
}
|
||||
deserializeDataMap.put(s, deserializeData.deserialize(yapionAnyType));
|
||||
});
|
||||
return ConfigurationSerialization.deserializeObject(deserializeDataMap);
|
||||
}
|
||||
}
|
||||
@@ -64,7 +64,7 @@ public class Depth {
|
||||
dimensions.setZ(Math.abs(dimensions.getZ()));
|
||||
|
||||
RegionUtils.message(region, player -> {
|
||||
if (Config.getInstance().get(player).getPlainValueOrDefault("depth_message", true)) {
|
||||
if (Config.STORE.get(player).isDepthMessage()) {
|
||||
player.spigot().sendMessage(getMessage(player, dimensions.getBlockX() + 1, dimensions.getBlockY() + 1, dimensions.getBlockZ() + 1, tntCount));
|
||||
}
|
||||
});
|
||||
|
||||
+3
-3
@@ -34,11 +34,11 @@ public class DepthCommand extends SWCommand {
|
||||
|
||||
@Register
|
||||
public void toggle(Player player) {
|
||||
if (Config.getInstance().get(player).getPlainValueOrDefault("depth_message", true)) {
|
||||
Config.getInstance().get(player).put("depth_message", false);
|
||||
if (Config.STORE.get(player).isDepthMessage()) {
|
||||
Config.STORE.get(player).setDepthMessage(false);
|
||||
BauSystem.MESSAGE.send("DEPTH_COUNTER_DISABLE", player);
|
||||
} else {
|
||||
Config.getInstance().get(player).put("depth_message", true);
|
||||
Config.STORE.get(player).setDepthMessage(true);
|
||||
BauSystem.MESSAGE.send("DEPTH_COUNTER_ENABLE", player);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -95,7 +95,7 @@ public class Detonator {
|
||||
|
||||
public static void activateDetonator(DetonatorStorage detonator) {
|
||||
Player p = detonator.getPlayer();
|
||||
if (Config.getInstance().get(p).getPlainValueOrDefault("detonator-autostart", false)) {
|
||||
if (Config.STORE.get(p).isDetonatorAutostart()) {
|
||||
AutostartListener.instance.activate(p);
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -76,8 +76,8 @@ public class DetonatorCommand extends SWCommand {
|
||||
|
||||
@Register(value = "autostart", description = "DETONATOR_HELP_AUTOSTART")
|
||||
public void toggleAutostartTimer(Player p) {
|
||||
boolean current = Config.getInstance().get(p).getPlainValueOrDefault("detonator-autostart", false);
|
||||
Config.getInstance().get(p).put("detonator-autostart", !current);
|
||||
boolean current = Config.STORE.get(p).isDetonatorAutostart();
|
||||
Config.STORE.get(p).setDetonatorAutostart(!current);
|
||||
if (!current) {
|
||||
BauSystem.MESSAGE.send("DETONATOR_AUTOSTART_ENABLE", p);
|
||||
} else {
|
||||
|
||||
+15
-16
@@ -28,7 +28,6 @@ import lombok.Setter;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
import yapion.hierarchy.types.YAPIONObject;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@@ -52,15 +51,15 @@ public class BauGuiMapping {
|
||||
}
|
||||
|
||||
@Getter
|
||||
private final YAPIONObject object;
|
||||
private final Config config;
|
||||
@Getter
|
||||
private final Player owner;
|
||||
@Getter
|
||||
@Setter
|
||||
private boolean saved;
|
||||
|
||||
protected BauGuiMapping(YAPIONObject object, Player p) {
|
||||
this.object = object;
|
||||
protected BauGuiMapping(Config config, Player p) {
|
||||
this.config = config;
|
||||
this.owner = p;
|
||||
fromUUID.put(p.getUniqueId(), this);
|
||||
mappings.add(this);
|
||||
@@ -70,19 +69,18 @@ public class BauGuiMapping {
|
||||
BauGuiMapping mapping = fromUUID.get(p.getUniqueId());
|
||||
|
||||
if (mapping == null) {
|
||||
YAPIONObject yapionObject = Config.getInstance().get(p);
|
||||
mapping = new BauGuiMapping(yapionObject.getObject("baugui"), p);
|
||||
mapping = new BauGuiMapping(Config.STORE.get(p), p);
|
||||
}
|
||||
|
||||
return mapping;
|
||||
}
|
||||
|
||||
public boolean isShown(int id) {
|
||||
return object.getPlainValueOrDefault(Integer.toString(id), -1) >= 0;
|
||||
return config.getBauGui().getOrDefault(id, -1) >= 0;
|
||||
}
|
||||
|
||||
public int getSlot(int id) {
|
||||
return object.getPlainValue(Integer.toString(id));
|
||||
return config.getBauGui().get(id);
|
||||
}
|
||||
|
||||
public Map<Integer, Integer> getMapping() {
|
||||
@@ -94,7 +92,7 @@ public class BauGuiMapping {
|
||||
}
|
||||
|
||||
public int getSize() {
|
||||
return object.getPlainValueOrDefault("size", 45);
|
||||
return config.getBauGuiSize();
|
||||
}
|
||||
|
||||
public void setSize(int size) {
|
||||
@@ -102,7 +100,7 @@ public class BauGuiMapping {
|
||||
return;
|
||||
}
|
||||
|
||||
object.add("size", size);
|
||||
config.setBauGuiSize(size);
|
||||
}
|
||||
|
||||
private void tick() {
|
||||
@@ -111,16 +109,17 @@ public class BauGuiMapping {
|
||||
|
||||
public void save() {
|
||||
if (saved) return;
|
||||
YAPIONObject config = Config.getInstance().get(owner);
|
||||
config.add("baugui", object);
|
||||
Config.getInstance().save(owner);
|
||||
Config current = Config.STORE.get(owner);
|
||||
current.setBauGui(config.getBauGui());
|
||||
current.setBauGuiSize(config.getBauGuiSize());
|
||||
Config.STORE.save(owner);
|
||||
saved = true;
|
||||
}
|
||||
|
||||
private Map<Integer, Integer> internalReadMap() {
|
||||
Map<Integer, Integer> map = new HashMap<>();
|
||||
for (Map.Entry<Integer, BauGuiItem> e : BauGUI.getITEMS().entrySet()) {
|
||||
Integer value = object.getPlainValueOrDefault(e.getKey().toString(), -1);
|
||||
Integer value = config.getBauGui().getOrDefault(e.getKey(), -1);
|
||||
if (value == -1) continue;
|
||||
map.put(e.getKey(), value);
|
||||
}
|
||||
@@ -129,10 +128,10 @@ public class BauGuiMapping {
|
||||
|
||||
private void internalWriteMap(Map<Integer, Integer> map) {
|
||||
for (Map.Entry<Integer, BauGuiItem> e : BauGUI.getITEMS().entrySet()) {
|
||||
object.remove(e.getKey().toString());
|
||||
config.getBauGui().remove(e.getKey());
|
||||
Integer value = map.getOrDefault(e.getKey(), -1);
|
||||
if (value == -1) continue;
|
||||
object.add(e.getKey().toString(), value);
|
||||
config.getBauGui().put(e.getKey(), value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+30
-40
@@ -20,39 +20,23 @@
|
||||
package de.steamwar.bausystem.features.hotbar;
|
||||
|
||||
import de.steamwar.bausystem.configplayer.Config;
|
||||
import de.steamwar.core.Core;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import yapion.hierarchy.types.YAPIONArray;
|
||||
import yapion.hierarchy.types.YAPIONObject;
|
||||
import yapion.hierarchy.types.YAPIONValue;
|
||||
import yapion.parser.YAPIONParser;
|
||||
import yapion.serializing.YAPIONDeserializer;
|
||||
import yapion.serializing.YAPIONSerializer;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.List;
|
||||
|
||||
@UtilityClass
|
||||
public class DefaultHotbar {
|
||||
|
||||
private final YAPIONArray DEFAULT_HOTBAR = YAPIONParser.parse("{[{@type(org.bukkit.inventory.ItemStack)v(2230)type(WOODEN_AXE)meta{@type(org.bukkit.inventory.meta.ItemMeta)meta-type(UNSPECIFIC)display-name(WorldEdit Wand)lore{@type(java.util.ArrayList)values[Left click: select pos #1,Right click: select pos #2]}}},{@type(org.bukkit.inventory.ItemStack)v(2230)type(COMPASS)meta{@type(org.bukkit.inventory.meta.ItemMeta)meta-type(UNSPECIFIC)display-name(Navigation Wand)lore{@type(java.util.ArrayList)values[Left click: jump to location,Right click: pass through walls]}}},null,null,null,null,null,null,{@type(org.bukkit.inventory.ItemStack)v(2230)type(NETHER_STAR)meta{@type(org.bukkit.inventory.meta.ItemMeta)meta-type(UNSPECIFIC)display-name(§eBau GUI)}},null,null,{@type(org.bukkit.inventory.ItemStack)v(3117)type(ELYTRA)},null]}").getArray("");
|
||||
|
||||
public void updateHotbar(Player p) {
|
||||
ItemStack[] hotbar = new ItemStack[13];
|
||||
System.arraycopy(p.getInventory().getContents(), 0, hotbar, 0, 9);
|
||||
System.arraycopy(p.getInventory().getArmorContents(), 0, hotbar, 9, 4);
|
||||
YAPIONArray yapionArray = new YAPIONArray();
|
||||
for (ItemStack itemStack : hotbar) {
|
||||
if (itemStack != null) {
|
||||
yapionArray.add(YAPIONSerializer.serialize(itemStack));
|
||||
} else {
|
||||
yapionArray.add(new YAPIONValue<>(null));
|
||||
}
|
||||
}
|
||||
Config.getInstance().get(p).add("hotbar-" + Core.getVersion(), yapionArray);
|
||||
Config.getInstance().save(p);
|
||||
Config.STORE.get(p).setHotbar(copy(hotbar));
|
||||
Config.STORE.save(p);
|
||||
}
|
||||
|
||||
public void setHotbar(Player p) {
|
||||
@@ -66,28 +50,34 @@ public class DefaultHotbar {
|
||||
}
|
||||
|
||||
public ItemStack[] getItems(Player p) {
|
||||
Config.getInstance().get(p).remap("hotbar", "hotbar-19");
|
||||
YAPIONArray yapionArray = Config.getInstance().get(p).getYAPIONArrayOrSetDefault("hotbar-" + Core.getVersion(), defaultHotbar());
|
||||
return copy(Config.STORE.get(p).getHotbar());
|
||||
}
|
||||
|
||||
public ItemStack[] defaultHotbar() {
|
||||
ItemStack[] hotbar = new ItemStack[13];
|
||||
Set<Integer> invalid = new HashSet<>();
|
||||
yapionArray.forEach((integer, yapionAnyType) -> {
|
||||
if (yapionAnyType instanceof YAPIONValue) {
|
||||
hotbar[integer] = null;
|
||||
} else {
|
||||
try {
|
||||
hotbar[integer] = YAPIONDeserializer.deserialize((YAPIONObject) yapionAnyType);
|
||||
} catch (Exception e) {
|
||||
invalid.add(integer);
|
||||
hotbar[integer] = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
invalid.forEach(i -> yapionArray.set(i, new YAPIONValue<>(null)));
|
||||
if (!invalid.isEmpty()) Config.getInstance().save(p);
|
||||
hotbar[0] = item(Material.WOODEN_AXE, "WorldEdit Wand", "Left click: select pos #1", "Right click: select pos #2");
|
||||
hotbar[1] = item(Material.COMPASS, "Navigation Wand", "Left click: jump to location", "Right click: pass through walls");
|
||||
hotbar[8] = item(Material.NETHER_STAR, "§eBau GUI");
|
||||
hotbar[11] = new ItemStack(Material.ELYTRA);
|
||||
return hotbar;
|
||||
}
|
||||
|
||||
public YAPIONArray defaultHotbar() {
|
||||
return DEFAULT_HOTBAR;
|
||||
private ItemStack item(Material material, String name, String... lore) {
|
||||
ItemStack itemStack = new ItemStack(material);
|
||||
ItemMeta meta = itemStack.getItemMeta();
|
||||
meta.setDisplayName(name);
|
||||
if (lore.length > 0) {
|
||||
meta.setLore(List.of(lore));
|
||||
}
|
||||
itemStack.setItemMeta(meta);
|
||||
return itemStack;
|
||||
}
|
||||
|
||||
private ItemStack[] copy(ItemStack[] hotbar) {
|
||||
ItemStack[] copy = new ItemStack[13];
|
||||
for (int i = 0; i < Math.min(hotbar.length, copy.length); i++) {
|
||||
copy[i] = hotbar[i] == null ? null : hotbar[i].clone();
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -49,7 +49,7 @@ public class InventoryFillBauGuiItem extends BauGuiItem {
|
||||
|
||||
@Override
|
||||
public ItemStack getItem(Player player) {
|
||||
String loreKey = Config.getInstance().get(player).getPlainValueOrDefault("inventoryfill", false) ? "OTHER_ITEMS_INVENTORY_FILL_LORE_ACTIVE" : "OTHER_ITEMS_INVENTORY_FILL_LORE_INACTIVE";
|
||||
String loreKey = Config.STORE.get(player).isInventoryFill() ? "OTHER_ITEMS_INVENTORY_FILL_LORE_ACTIVE" : "OTHER_ITEMS_INVENTORY_FILL_LORE_INACTIVE";
|
||||
return new SWItem(Material.HOPPER, BauSystem.MESSAGE.parse("OTHER_ITEMS_INVENTORY_FILL_NAME", player), Collections.singletonList(BauSystem.MESSAGE.parse(loreKey, player)), false, clickType -> {
|
||||
}).getItemStack();
|
||||
}
|
||||
|
||||
+2
-2
@@ -40,7 +40,7 @@ public class InventoryFiller implements Listener {
|
||||
@EventHandler
|
||||
public void onPlayerDropItem(PlayerDropItemEvent event) {
|
||||
if (!Permission.BUILD.hasPermission(event.getPlayer())) return;
|
||||
if (!Config.getInstance().get(event.getPlayer()).getPlainValueOrDefault("inventoryfill", false)) return;
|
||||
if (!Config.STORE.get(event.getPlayer()).isInventoryFill()) return;
|
||||
if (!event.getPlayer().isSneaking()) return;
|
||||
Block block = event.getPlayer().getTargetBlockExact(5);
|
||||
if (block == null) return;
|
||||
@@ -62,7 +62,7 @@ public class InventoryFiller implements Listener {
|
||||
@EventHandler
|
||||
public void onPlayerItemHeld(PlayerItemHeldEvent event) {
|
||||
if (!Permission.BUILD.hasPermission(event.getPlayer())) return;
|
||||
if (!Config.getInstance().get(event.getPlayer()).getPlainValueOrDefault("inventoryfill", false)) return;
|
||||
if (!Config.STORE.get(event.getPlayer()).isInventoryFill()) return;
|
||||
if (!event.getPlayer().isSneaking()) return;
|
||||
ItemStack itemStack = event.getPlayer().getInventory().getItemInMainHand();
|
||||
if (itemStack.getType() == Material.AIR) return;
|
||||
|
||||
+2
-2
@@ -68,8 +68,8 @@ public class InventoryFillerCommand extends SWCommand {
|
||||
|
||||
@Register(description = {"INVENTORY_FILL_HELP", "INVENTORY_FILL_INFO"})
|
||||
public void toggle(Player player) {
|
||||
boolean inventoryFill = Config.getInstance().get(player).getPlainValueOrDefault("inventoryfill", false);
|
||||
Config.getInstance().get(player).put("inventoryfill", !inventoryFill);
|
||||
boolean inventoryFill = Config.STORE.get(player).isInventoryFill();
|
||||
Config.STORE.get(player).setInventoryFill(!inventoryFill);
|
||||
if (!inventoryFill) {
|
||||
SWUtils.sendToActionbar(player, BauSystem.MESSAGE.parse("INVENTORY_FILL_ENABLE", player));
|
||||
BauSystem.MESSAGE.send("INVENTORY_FILL_INFO", player);
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@ public class ObserverTracerListener implements Listener {
|
||||
public ObserverTracerListener() {
|
||||
Bukkit.getScheduler().runTaskTimer(BauSystem.getInstance(), () -> {
|
||||
SWPlayer.allWithSingleComponent(ObserverTracer.class).forEach(pair -> {
|
||||
if (pair.getPlayer().getGameMode() != GameMode.SPECTATOR) return;
|
||||
if (pair.getSwPlayer().getPlayer().getGameMode() != GameMode.SPECTATOR) return;
|
||||
pair.getComponent().show();
|
||||
});
|
||||
}, 15L, 15L);
|
||||
|
||||
+4
-4
@@ -167,7 +167,7 @@ public class RayVisualizerCommand extends SWCommand implements Listener {
|
||||
@EventHandler
|
||||
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||
if (!Permission.BUILD.hasPermission(event.getPlayer())) return;
|
||||
boolean rayvisualizer = Config.getInstance().get(event.getPlayer()).getPlainValueOrDefault("rayvisualizer", false);
|
||||
boolean rayvisualizer = Config.STORE.get(event.getPlayer()).isRayVisualizer();
|
||||
if (rayvisualizer) server.addPlayer(event.getPlayer());
|
||||
}
|
||||
|
||||
@@ -181,7 +181,7 @@ public class RayVisualizerCommand extends SWCommand implements Listener {
|
||||
Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> {
|
||||
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||
if (Permission.BUILD.hasPermission(player)) {
|
||||
boolean rayvisualizer = Config.getInstance().get(player).getPlainValueOrDefault("rayvisualizer", false);
|
||||
boolean rayvisualizer = Config.STORE.get(player).isRayVisualizer();
|
||||
if (rayvisualizer) server.addPlayer(player);
|
||||
} else {
|
||||
server.removePlayer(player);
|
||||
@@ -192,8 +192,8 @@ public class RayVisualizerCommand extends SWCommand implements Listener {
|
||||
|
||||
@Register
|
||||
public void toggle(@Validator Player player) {
|
||||
boolean rayvisualizer = Config.getInstance().get(player).getPlainValueOrDefault("rayvisualizer", false);
|
||||
Config.getInstance().get(player).put("rayvisualizer", !rayvisualizer);
|
||||
boolean rayvisualizer = Config.STORE.get(player).isRayVisualizer();
|
||||
Config.STORE.get(player).setRayVisualizer(!rayvisualizer);
|
||||
if (!rayvisualizer) {
|
||||
SWUtils.sendToActionbar(player, BauSystem.MESSAGE.parse("RAY_VISUALIZER_ENABLE", player));
|
||||
server.addPlayer(player);
|
||||
|
||||
+3
-3
@@ -21,6 +21,7 @@ package de.steamwar.bausystem.features.simulator.data;
|
||||
|
||||
import de.steamwar.bausystem.features.simulator.execute.SimulatorAction;
|
||||
import de.steamwar.bausystem.features.simulator.gui.base.SimulatorBaseGui;
|
||||
import de.steamwar.bausystem.features.simulator.storage.SimulatorConfig;
|
||||
import de.steamwar.inventory.InvCallback;
|
||||
import de.steamwar.inventory.SWItem;
|
||||
import lombok.Getter;
|
||||
@@ -28,7 +29,6 @@ import lombok.Setter;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.util.Vector;
|
||||
import yapion.hierarchy.types.YAPIONObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
@@ -121,9 +121,9 @@ public abstract class SimulatorElement<T extends SimulatorPhase> {
|
||||
|
||||
public abstract String getType();
|
||||
|
||||
public void saveExtra(YAPIONObject elementObject) {
|
||||
public void saveExtra(SimulatorConfig.Element element) {
|
||||
}
|
||||
|
||||
public void loadExtra(YAPIONObject elementObject) {
|
||||
public void loadExtra(SimulatorConfig.Element element) {
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -20,10 +20,10 @@
|
||||
package de.steamwar.bausystem.features.simulator.data;
|
||||
|
||||
import de.steamwar.bausystem.features.simulator.execute.SimulatorAction;
|
||||
import de.steamwar.bausystem.features.simulator.storage.SimulatorConfig;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.bukkit.util.Vector;
|
||||
import yapion.hierarchy.types.YAPIONObject;
|
||||
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
@@ -38,9 +38,9 @@ public abstract class SimulatorPhase {
|
||||
|
||||
public abstract void toSimulatorActions(Vector position, BiConsumer<Integer, SimulatorAction> tickStart, BiConsumer<Integer, SimulatorAction> tickEnd);
|
||||
|
||||
public void saveExtra(YAPIONObject phaseObject) {
|
||||
public void saveExtra(SimulatorConfig.Phase phase) {
|
||||
}
|
||||
|
||||
public void loadExtra(YAPIONObject phaseObject) {
|
||||
public void loadExtra(SimulatorConfig.Phase phase) {
|
||||
}
|
||||
}
|
||||
|
||||
+5
-5
@@ -21,6 +21,7 @@ package de.steamwar.bausystem.features.simulator.data.observer;
|
||||
|
||||
import de.steamwar.bausystem.features.simulator.data.SimulatorPhase;
|
||||
import de.steamwar.bausystem.features.simulator.execute.SimulatorAction;
|
||||
import de.steamwar.bausystem.features.simulator.storage.SimulatorConfig;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
@@ -31,7 +32,6 @@ import org.bukkit.block.BlockFace;
|
||||
import org.bukkit.block.BlockState;
|
||||
import org.bukkit.block.data.type.Observer;
|
||||
import org.bukkit.util.Vector;
|
||||
import yapion.hierarchy.types.YAPIONObject;
|
||||
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
@@ -70,12 +70,12 @@ public final class ObserverPhase extends SimulatorPhase {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveExtra(YAPIONObject phaseObject) {
|
||||
phaseObject.add("orientation", orientation.name());
|
||||
public void saveExtra(SimulatorConfig.Phase phase) {
|
||||
phase.setOrientation(orientation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadExtra(YAPIONObject phaseObject) {
|
||||
orientation = BlockFace.valueOf(phaseObject.getPlainValue("orientation"));
|
||||
public void loadExtra(SimulatorConfig.Phase phase) {
|
||||
if (phase.getOrientation() != null) orientation = phase.getOrientation();
|
||||
}
|
||||
}
|
||||
|
||||
+7
-7
@@ -24,13 +24,13 @@ import de.steamwar.bausystem.features.simulator.data.SimulatorElement;
|
||||
import de.steamwar.bausystem.features.simulator.data.SimulatorGroup;
|
||||
import de.steamwar.bausystem.features.simulator.gui.SimulatorTNTGui;
|
||||
import de.steamwar.bausystem.features.simulator.gui.base.SimulatorBaseGui;
|
||||
import de.steamwar.bausystem.features.simulator.storage.SimulatorConfig;
|
||||
import de.steamwar.inventory.InvCallback;
|
||||
import de.steamwar.inventory.SWItem;
|
||||
import lombok.Getter;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.util.Vector;
|
||||
import yapion.hierarchy.types.YAPIONObject;
|
||||
|
||||
public final class TNTElement extends SimulatorElement<TNTPhase> {
|
||||
|
||||
@@ -100,14 +100,14 @@ public final class TNTElement extends SimulatorElement<TNTPhase> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveExtra(YAPIONObject elementObject) {
|
||||
elementObject.add("alignmentX", alignment.getX());
|
||||
elementObject.add("alignmentZ", alignment.getZ());
|
||||
public void saveExtra(SimulatorConfig.Element element) {
|
||||
element.setAlignmentX(alignment.getX());
|
||||
element.setAlignmentZ(alignment.getZ());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadExtra(YAPIONObject elementObject) {
|
||||
alignment.setX(elementObject.getDoubleOrSetDefault("alignmentX", 0));
|
||||
alignment.setZ(elementObject.getDoubleOrSetDefault("alignmentZ", 0));
|
||||
public void loadExtra(SimulatorConfig.Element element) {
|
||||
alignment.setX(element.getAlignmentX() != null ? element.getAlignmentX() : 0);
|
||||
alignment.setZ(element.getAlignmentZ() != null ? element.getAlignmentZ() : 0);
|
||||
}
|
||||
}
|
||||
|
||||
+11
-11
@@ -21,6 +21,7 @@ package de.steamwar.bausystem.features.simulator.data.tnt;
|
||||
|
||||
import de.steamwar.bausystem.features.simulator.data.SimulatorPhase;
|
||||
import de.steamwar.bausystem.features.simulator.execute.SimulatorAction;
|
||||
import de.steamwar.bausystem.features.simulator.storage.SimulatorConfig;
|
||||
import de.steamwar.bausystem.region.Region;
|
||||
import de.steamwar.bausystem.region.flags.Flag;
|
||||
import de.steamwar.bausystem.region.flags.FreezeMode;
|
||||
@@ -31,7 +32,6 @@ import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.TNTPrimed;
|
||||
import org.bukkit.util.Vector;
|
||||
import yapion.hierarchy.types.YAPIONObject;
|
||||
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
@@ -83,18 +83,18 @@ public final class TNTPhase extends SimulatorPhase {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveExtra(YAPIONObject phaseObject) {
|
||||
phaseObject.add("count", count);
|
||||
phaseObject.add("xJump", xJump);
|
||||
phaseObject.add("yJump", yJump);
|
||||
phaseObject.add("zJump", zJump);
|
||||
public void saveExtra(SimulatorConfig.Phase phase) {
|
||||
phase.setCount(count);
|
||||
phase.setXJump(xJump);
|
||||
phase.setYJump(yJump);
|
||||
phase.setZJump(zJump);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadExtra(YAPIONObject phaseObject) {
|
||||
count = phaseObject.getPlainValue("count");
|
||||
xJump = phaseObject.getPlainValue("xJump");
|
||||
yJump = phaseObject.getPlainValue("yJump");
|
||||
zJump = phaseObject.getPlainValue("zJump");
|
||||
public void loadExtra(SimulatorConfig.Phase phase) {
|
||||
if (phase.getCount() != null) count = phase.getCount();
|
||||
if (phase.getXJump() != null) xJump = phase.getXJump();
|
||||
if (phase.getYJump() != null) yJump = phase.getYJump();
|
||||
if (phase.getZJump() != null) zJump = phase.getZJump();
|
||||
}
|
||||
}
|
||||
|
||||
+29
-37
@@ -19,6 +19,7 @@
|
||||
|
||||
package de.steamwar.bausystem.features.simulator.storage;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import de.steamwar.bausystem.features.simulator.data.Simulator;
|
||||
import de.steamwar.bausystem.features.simulator.data.SimulatorElement;
|
||||
import de.steamwar.bausystem.features.simulator.data.SimulatorGroup;
|
||||
@@ -29,12 +30,7 @@ import de.steamwar.bausystem.features.simulator.data.redstone.RedstoneElement;
|
||||
import de.steamwar.bausystem.features.simulator.data.redstone.RedstonePhase;
|
||||
import de.steamwar.bausystem.features.simulator.data.tnt.TNTElement;
|
||||
import de.steamwar.bausystem.features.simulator.data.tnt.TNTPhase;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.util.Vector;
|
||||
import yapion.exceptions.YAPIONException;
|
||||
import yapion.hierarchy.types.YAPIONArray;
|
||||
import yapion.hierarchy.types.YAPIONObject;
|
||||
import yapion.parser.YAPIONParser;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -51,44 +47,41 @@ public class SimFormatSimulatorLoader implements SimulatorLoader {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
YAPIONObject yapionObject;
|
||||
SimulatorConfig config;
|
||||
try {
|
||||
yapionObject = YAPIONParser.parse(file);
|
||||
} catch (YAPIONException | IOException e) {
|
||||
config = SimulatorConfig.STORE.load(file);
|
||||
} catch (JsonParseException | IllegalStateException | IOException e) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
String name = file.getName().substring(0, file.getName().length() - ".sim".length());
|
||||
|
||||
Simulator simulator = new Simulator(name);
|
||||
loadSimulator(yapionObject, simulator);
|
||||
loadSimulator(config, simulator);
|
||||
return Optional.of(Collections.singletonList(simulator));
|
||||
}
|
||||
|
||||
private void loadSimulator(YAPIONObject simulatorObject, Simulator simulator) {
|
||||
simulator.setMaterial(Material.valueOf(simulatorObject.getPlainValue("material")));
|
||||
simulator.setAutoTrace(simulatorObject.getPlainValue("autoTrace"));
|
||||
simulator.setAutoTestblock(simulatorObject.getPlainValueOrDefault("autoTestblock", false));
|
||||
private void loadSimulator(SimulatorConfig config, Simulator simulator) {
|
||||
if (config.getMaterial() != null) simulator.setMaterial(config.getMaterial());
|
||||
simulator.setAutoTrace(config.isAutoTrace());
|
||||
simulator.setAutoTestblock(config.isAutoTestblock());
|
||||
|
||||
YAPIONArray groups = simulatorObject.getArray("groups");
|
||||
groups.streamObject().forEach(groupObject -> {
|
||||
config.getGroups().forEach(groupConfig -> {
|
||||
SimulatorGroup simulatorGroup = new SimulatorGroup();
|
||||
loadGroup(groupObject, simulatorGroup);
|
||||
loadGroup(groupConfig, simulatorGroup);
|
||||
simulator.add(simulatorGroup);
|
||||
});
|
||||
}
|
||||
|
||||
private void loadGroup(YAPIONObject groupObject, SimulatorGroup group) {
|
||||
group.setMaterial(Material.valueOf(groupObject.getPlainValue("material")));
|
||||
group.setDisabled(groupObject.getPlainValue("disabled"));
|
||||
private void loadGroup(SimulatorConfig.Group groupConfig, SimulatorGroup group) {
|
||||
if (groupConfig.getMaterial() != null) group.setMaterial(groupConfig.getMaterial());
|
||||
group.setDisabled(groupConfig.isDisabled());
|
||||
|
||||
YAPIONArray elements = groupObject.getArray("elements");
|
||||
elements.streamObject().forEach(elementObject -> {
|
||||
groupConfig.getElements().forEach(elementConfig -> {
|
||||
SimulatorElement<?> element;
|
||||
Supplier<SimulatorPhase> phaseConstructor;
|
||||
Vector position = new Vector(elementObject.getDouble("x"), elementObject.getDouble("y"), elementObject.getDouble("z"));
|
||||
String type = elementObject.getPlainValue("type");
|
||||
switch (type) {
|
||||
Vector position = new Vector(elementConfig.getX(), elementConfig.getY(), elementConfig.getZ());
|
||||
switch (String.valueOf(elementConfig.getType())) {
|
||||
case "TNT":
|
||||
element = new TNTElement(position);
|
||||
phaseConstructor = TNTPhase::new;
|
||||
@@ -109,28 +102,27 @@ public class SimFormatSimulatorLoader implements SimulatorLoader {
|
||||
if (element == null) {
|
||||
return;
|
||||
}
|
||||
loadElement(elementObject, element, phaseConstructor);
|
||||
loadElement(elementConfig, element, phaseConstructor);
|
||||
group.add(element);
|
||||
});
|
||||
}
|
||||
|
||||
private void loadElement(YAPIONObject elementObject, SimulatorElement<?> element, Supplier<SimulatorPhase> phaseConstructor) {
|
||||
element.setMaterial(Material.valueOf(elementObject.getPlainValue("material")));
|
||||
element.setDisabled(elementObject.getPlainValue("disabled"));
|
||||
element.loadExtra(elementObject);
|
||||
private void loadElement(SimulatorConfig.Element elementConfig, SimulatorElement<?> element, Supplier<SimulatorPhase> phaseConstructor) {
|
||||
if (elementConfig.getMaterial() != null) element.setMaterial(elementConfig.getMaterial());
|
||||
element.setDisabled(elementConfig.isDisabled());
|
||||
element.loadExtra(elementConfig);
|
||||
|
||||
YAPIONArray phases = elementObject.getArray("phases");
|
||||
phases.streamObject().forEach(phaseObject -> {
|
||||
elementConfig.getPhases().forEach(phaseConfig -> {
|
||||
SimulatorPhase phase = phaseConstructor.get();
|
||||
loadPhase(phaseObject, phase);
|
||||
loadPhase(phaseConfig, phase);
|
||||
((SimulatorElement) element).add(phase);
|
||||
});
|
||||
}
|
||||
|
||||
private void loadPhase(YAPIONObject phaseObject, SimulatorPhase phase) {
|
||||
phase.setTickOffset(phaseObject.getPlainValue("tickOffset"));
|
||||
phase.setLifetime(phaseObject.getPlainValue("lifetime"));
|
||||
phase.setOrder(phaseObject.getPlainValue("order"));
|
||||
phase.loadExtra(phaseObject);
|
||||
private void loadPhase(SimulatorConfig.Phase phaseConfig, SimulatorPhase phase) {
|
||||
phase.setTickOffset(phaseConfig.getTickOffset());
|
||||
phase.setLifetime(phaseConfig.getLifetime());
|
||||
phase.setOrder(phaseConfig.getOrder());
|
||||
phase.loadExtra(phaseConfig);
|
||||
}
|
||||
}
|
||||
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2026 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.bausystem.features.simulator.storage;
|
||||
|
||||
import de.steamwar.bausystem.config.ConfigStore;
|
||||
import de.steamwar.bausystem.config.VersionedConfig;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.BlockFace;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The current stored form of a simulator, saved as JSON in a .sim file. Files
|
||||
* saved before the version was stored have no version and are version 1.
|
||||
* Fields missing in the stored JSON keep the default value of their initializer,
|
||||
* so adding a new field does not need a new version.
|
||||
* <p>
|
||||
* When a field gets removed, renamed or its meaning changes, copy this class
|
||||
* to SimulatorConfigV{@link #VERSION} (without the {@link #STORE}), increase {@link #VERSION}, write a
|
||||
* {@link de.steamwar.bausystem.config.ConfigConverter} from the copy to
|
||||
* this class and register it in the {@link #STORE}.
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class SimulatorConfig implements VersionedConfig {
|
||||
|
||||
public static final int VERSION = 1;
|
||||
|
||||
/**
|
||||
* Files saved before the version was stored are version 1
|
||||
*/
|
||||
public static final ConfigStore<SimulatorConfig> STORE = new ConfigStore<>(SimulatorConfig.class, VERSION, SimulatorConfig::new)
|
||||
.unversioned(1);
|
||||
|
||||
@Setter(AccessLevel.NONE)
|
||||
protected int version = VERSION;
|
||||
|
||||
protected Material material = Material.BARREL;
|
||||
protected boolean autoTrace = false;
|
||||
protected boolean autoTestblock = false;
|
||||
protected List<Group> groups = new ArrayList<>();
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public static class Group {
|
||||
|
||||
protected Material material = Material.CHEST;
|
||||
protected boolean disabled = false;
|
||||
protected List<Element> elements = new ArrayList<>();
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public static class Element {
|
||||
|
||||
/**
|
||||
* One of "TNT", "Redstone" or "Observer"
|
||||
*/
|
||||
protected String type;
|
||||
protected Material material;
|
||||
protected boolean disabled = false;
|
||||
protected double x;
|
||||
protected double y;
|
||||
protected double z;
|
||||
|
||||
/**
|
||||
* Only used by TNT
|
||||
*/
|
||||
protected Double alignmentX;
|
||||
protected Double alignmentZ;
|
||||
|
||||
protected List<Phase> phases = new ArrayList<>();
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public static class Phase {
|
||||
|
||||
protected int tickOffset;
|
||||
protected int lifetime;
|
||||
protected int order;
|
||||
|
||||
/**
|
||||
* Only used by TNT
|
||||
*/
|
||||
protected Integer count;
|
||||
protected Boolean xJump;
|
||||
protected Boolean yJump;
|
||||
protected Boolean zJump;
|
||||
|
||||
/**
|
||||
* Only used by Observer
|
||||
*/
|
||||
protected BlockFace orientation;
|
||||
}
|
||||
}
|
||||
+43
-35
@@ -19,24 +19,30 @@
|
||||
|
||||
package de.steamwar.bausystem.features.simulator.storage;
|
||||
|
||||
import de.steamwar.bausystem.config.legacy.YapionReader;
|
||||
import de.steamwar.bausystem.features.simulator.data.Simulator;
|
||||
import de.steamwar.bausystem.features.simulator.data.SimulatorGroup;
|
||||
import de.steamwar.bausystem.features.simulator.data.tnt.TNTElement;
|
||||
import de.steamwar.bausystem.features.simulator.data.tnt.TNTPhase;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.util.Vector;
|
||||
import yapion.exceptions.YAPIONException;
|
||||
import yapion.hierarchy.types.YAPIONArray;
|
||||
import yapion.hierarchy.types.YAPIONObject;
|
||||
import yapion.parser.YAPIONParser;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static de.steamwar.bausystem.config.legacy.YapionReader.*;
|
||||
|
||||
/**
|
||||
* Loads the legacy .simulator files stored as YAPION, they are deleted after loading
|
||||
* and saved in the current format by the {@link SimulatorSaver}.
|
||||
*/
|
||||
@Deprecated
|
||||
public class SimulatorFormatSimulatorLoader implements SimulatorLoader {
|
||||
|
||||
@Override
|
||||
@@ -45,20 +51,19 @@ public class SimulatorFormatSimulatorLoader implements SimulatorLoader {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
YAPIONObject yapionObject;
|
||||
Map<String, Object> yapionObject;
|
||||
try {
|
||||
yapionObject = YAPIONParser.parse(file);
|
||||
} catch (YAPIONException | IOException e) {
|
||||
yapionObject = YapionReader.parse(Files.readString(file.toPath(), StandardCharsets.UTF_8));
|
||||
} catch (IllegalArgumentException | IOException e) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
String name = file.getName().substring(0, file.getName().length() - ".simulator".length());
|
||||
|
||||
Simulator simulator = new Simulator(name);
|
||||
simulator.setMaterial(Material.valueOf(yapionObject.getStringOrDefault("material", "BARREL")));
|
||||
simulator.setMaterial(Material.valueOf(getString(yapionObject, "material", "BARREL")));
|
||||
|
||||
YAPIONArray tntElements = yapionObject.getArrayOrDefault("tntElements", new YAPIONArray());
|
||||
for (YAPIONObject element : tntElements.streamObject().collect(Collectors.toList())) {
|
||||
for (Map<?, ?> element : getObjects(yapionObject, "tntElements")) {
|
||||
if (element.containsKey("elements")) {
|
||||
simulator.add(loadGroup(element));
|
||||
} else {
|
||||
@@ -70,27 +75,26 @@ public class SimulatorFormatSimulatorLoader implements SimulatorLoader {
|
||||
return Optional.of(Collections.singletonList(simulator));
|
||||
}
|
||||
|
||||
private SimulatorGroup loadGroup(YAPIONObject element) {
|
||||
double x = element.getDoubleOrDefault("x", 0);
|
||||
double y = element.getDoubleOrDefault("y", 0);
|
||||
double z = element.getDoubleOrDefault("z", 0);
|
||||
int tickOffset = element.getIntOrDefault("tickOffset", 0);
|
||||
private SimulatorGroup loadGroup(Map<?, ?> element) {
|
||||
double x = getDouble(element, "x", 0);
|
||||
double y = getDouble(element, "y", 0);
|
||||
double z = getDouble(element, "z", 0);
|
||||
int tickOffset = getInt(element, "tickOffset", 0);
|
||||
|
||||
SimulatorGroup simulatorGroup = new SimulatorGroup();
|
||||
simulatorGroup.setDisabled(element.getBooleanOrDefault("disabled", false));
|
||||
simulatorGroup.setDisabled(getBoolean(element, "disabled", false));
|
||||
|
||||
YAPIONArray elements = element.getArrayOrDefault("elements", new YAPIONArray());
|
||||
for (YAPIONObject e : elements.streamObject().collect(Collectors.toList())) {
|
||||
for (Map<?, ?> e : getObjects(element, "elements")) {
|
||||
simulatorGroup.add(loadElement(e, x, y, z, tickOffset));
|
||||
}
|
||||
|
||||
return simulatorGroup;
|
||||
}
|
||||
|
||||
private TNTElement loadElement(YAPIONObject element, double dx, double dy, double dz, int dTickOffset) {
|
||||
TNTElement tntElement = new TNTElement(new Vector(element.getDouble("x") + dx, element.getDouble("y") + dy, element.getDouble("z") + dz));
|
||||
tntElement.setDisabled(element.getBooleanOrDefault("disabled", false));
|
||||
tntElement.setMaterial(Material.valueOf(element.getStringOrDefault("material", "TNT")));
|
||||
private TNTElement loadElement(Map<?, ?> element, double dx, double dy, double dz, int dTickOffset) {
|
||||
TNTElement tntElement = new TNTElement(new Vector(requireDouble(element, "x") + dx, requireDouble(element, "y") + dy, requireDouble(element, "z") + dz));
|
||||
tntElement.setDisabled(getBoolean(element, "disabled", false));
|
||||
tntElement.setMaterial(Material.valueOf(getString(element, "material", "TNT")));
|
||||
|
||||
TNTPhase tntPhase = loadPhase(element);
|
||||
tntPhase.setTickOffset(tntPhase.getTickOffset() + dTickOffset);
|
||||
@@ -99,12 +103,12 @@ public class SimulatorFormatSimulatorLoader implements SimulatorLoader {
|
||||
return tntElement;
|
||||
}
|
||||
|
||||
private SimulatorGroup loadElement(YAPIONObject element) {
|
||||
private SimulatorGroup loadElement(Map<?, ?> element) {
|
||||
SimulatorGroup simulatorGroup = new SimulatorGroup();
|
||||
|
||||
TNTElement tntElement = new TNTElement(new Vector(element.getDouble("x"), element.getDouble("y"), element.getDouble("z")));
|
||||
tntElement.setDisabled(element.getBooleanOrDefault("disabled", false));
|
||||
tntElement.setMaterial(Material.valueOf(element.getStringOrDefault("material", "TNT")));
|
||||
TNTElement tntElement = new TNTElement(new Vector(requireDouble(element, "x"), requireDouble(element, "y"), requireDouble(element, "z")));
|
||||
tntElement.setDisabled(getBoolean(element, "disabled", false));
|
||||
tntElement.setMaterial(Material.valueOf(getString(element, "material", "TNT")));
|
||||
simulatorGroup.add(tntElement);
|
||||
|
||||
tntElement.add(loadPhase(element));
|
||||
@@ -112,15 +116,15 @@ public class SimulatorFormatSimulatorLoader implements SimulatorLoader {
|
||||
return simulatorGroup;
|
||||
}
|
||||
|
||||
private TNTPhase loadPhase(YAPIONObject element) {
|
||||
private TNTPhase loadPhase(Map<?, ?> element) {
|
||||
TNTPhase tntPhase = new TNTPhase();
|
||||
tntPhase.setLifetime(element.getIntOrDefault("fuseTicks", 80));
|
||||
tntPhase.setCount(element.getIntOrDefault("count", 1));
|
||||
tntPhase.setTickOffset(element.getIntOrDefault("tickOffset", 0));
|
||||
tntPhase.setXJump(element.getBooleanOrDefault("xVelocity", false));
|
||||
tntPhase.setYJump(element.getBooleanOrDefault("yVelocity", false));
|
||||
tntPhase.setZJump(element.getBooleanOrDefault("zVelocity", false));
|
||||
switch (element.getStringOrDefault("order", "REPEATER")) {
|
||||
tntPhase.setLifetime(getInt(element, "fuseTicks", 80));
|
||||
tntPhase.setCount(getInt(element, "count", 1));
|
||||
tntPhase.setTickOffset(getInt(element, "tickOffset", 0));
|
||||
tntPhase.setXJump(getBoolean(element, "xVelocity", false));
|
||||
tntPhase.setYJump(getBoolean(element, "yVelocity", false));
|
||||
tntPhase.setZJump(getBoolean(element, "zVelocity", false));
|
||||
switch (getString(element, "order", "REPEATER")) {
|
||||
case "REPEATER":
|
||||
tntPhase.setOrder(0);
|
||||
break;
|
||||
@@ -133,4 +137,8 @@ public class SimulatorFormatSimulatorLoader implements SimulatorLoader {
|
||||
}
|
||||
return tntPhase;
|
||||
}
|
||||
|
||||
private double requireDouble(Map<?, ?> element, String key) {
|
||||
return ((Number) element.get(key)).doubleValue();
|
||||
}
|
||||
}
|
||||
|
||||
+24
-33
@@ -21,9 +21,6 @@ package de.steamwar.bausystem.features.simulator.storage;
|
||||
|
||||
import de.steamwar.bausystem.features.simulator.data.Simulator;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import yapion.hierarchy.output.FileOutput;
|
||||
import yapion.hierarchy.types.YAPIONArray;
|
||||
import yapion.hierarchy.types.YAPIONObject;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -36,48 +33,42 @@ public class SimulatorSaver {
|
||||
directory.mkdirs();
|
||||
}
|
||||
|
||||
YAPIONObject simulatorObject = new YAPIONObject();
|
||||
simulatorObject.add("material", simulator.getMaterial().name());
|
||||
simulatorObject.add("autoTrace", simulator.isAutoTrace());
|
||||
simulatorObject.add("autoTestblock", simulator.isAutoTestblock());
|
||||
SimulatorConfig config = new SimulatorConfig();
|
||||
config.setMaterial(simulator.getMaterial());
|
||||
config.setAutoTrace(simulator.isAutoTrace());
|
||||
config.setAutoTestblock(simulator.isAutoTestblock());
|
||||
|
||||
YAPIONArray groups = new YAPIONArray();
|
||||
simulator.getGroups().forEach(group -> {
|
||||
YAPIONObject groupObject = new YAPIONObject();
|
||||
groupObject.add("material", group.getMaterial().name());
|
||||
groupObject.add("disabled", group.isDisabled());
|
||||
SimulatorConfig.Group groupConfig = new SimulatorConfig.Group();
|
||||
groupConfig.setMaterial(group.getMaterial());
|
||||
groupConfig.setDisabled(group.isDisabled());
|
||||
|
||||
YAPIONArray elements = new YAPIONArray();
|
||||
group.getElements().forEach(element -> {
|
||||
YAPIONObject elementObject = new YAPIONObject();
|
||||
elementObject.add("type", element.getType());
|
||||
elementObject.add("material", element.getMaterial().name());
|
||||
elementObject.add("disabled", element.isDisabled());
|
||||
elementObject.add("x", element.getPosition().getX());
|
||||
elementObject.add("y", element.getPosition().getY());
|
||||
elementObject.add("z", element.getPosition().getZ());
|
||||
element.saveExtra(elementObject);
|
||||
SimulatorConfig.Element elementConfig = new SimulatorConfig.Element();
|
||||
elementConfig.setType(element.getType());
|
||||
elementConfig.setMaterial(element.getMaterial());
|
||||
elementConfig.setDisabled(element.isDisabled());
|
||||
elementConfig.setX(element.getPosition().getX());
|
||||
elementConfig.setY(element.getPosition().getY());
|
||||
elementConfig.setZ(element.getPosition().getZ());
|
||||
element.saveExtra(elementConfig);
|
||||
|
||||
YAPIONArray phases = new YAPIONArray();
|
||||
element.getPhases().forEach(phase -> {
|
||||
YAPIONObject phaseObject = new YAPIONObject();
|
||||
phaseObject.add("tickOffset", phase.getTickOffset());
|
||||
phaseObject.add("lifetime", phase.getLifetime());
|
||||
phaseObject.add("order", phase.getOrder());
|
||||
phase.saveExtra(phaseObject);
|
||||
phases.add(phaseObject);
|
||||
SimulatorConfig.Phase phaseConfig = new SimulatorConfig.Phase();
|
||||
phaseConfig.setTickOffset(phase.getTickOffset());
|
||||
phaseConfig.setLifetime(phase.getLifetime());
|
||||
phaseConfig.setOrder(phase.getOrder());
|
||||
phase.saveExtra(phaseConfig);
|
||||
elementConfig.getPhases().add(phaseConfig);
|
||||
});
|
||||
elementObject.add("phases", phases);
|
||||
elements.add(elementObject);
|
||||
groupConfig.getElements().add(elementConfig);
|
||||
});
|
||||
groupObject.add("elements", elements);
|
||||
groups.add(groupObject);
|
||||
config.getGroups().add(groupConfig);
|
||||
});
|
||||
simulatorObject.add("groups", groups);
|
||||
|
||||
File file = new File(directory, simulator.getName() + ".sim");
|
||||
try {
|
||||
simulatorObject.toJSONLossy(new FileOutput(file)).close();
|
||||
SimulatorConfig.STORE.save(file, config);
|
||||
} catch (IOException e) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
+31
-21
@@ -19,6 +19,7 @@
|
||||
|
||||
package de.steamwar.bausystem.features.simulator.storage;
|
||||
|
||||
import de.steamwar.bausystem.config.legacy.YapionReader;
|
||||
import de.steamwar.bausystem.features.simulator.data.Simulator;
|
||||
import de.steamwar.bausystem.features.simulator.data.SimulatorGroup;
|
||||
import de.steamwar.bausystem.features.simulator.data.tnt.TNTElement;
|
||||
@@ -26,18 +27,23 @@ import de.steamwar.bausystem.features.simulator.data.tnt.TNTPhase;
|
||||
import de.steamwar.sql.SteamwarUser;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.util.Vector;
|
||||
import yapion.exceptions.YAPIONException;
|
||||
import yapion.hierarchy.types.YAPIONArray;
|
||||
import yapion.hierarchy.types.YAPIONObject;
|
||||
import yapion.parser.YAPIONParser;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static de.steamwar.bausystem.config.legacy.YapionReader.*;
|
||||
|
||||
/**
|
||||
* Loads the legacy per player .yapion files containing multiple simulators, they are
|
||||
* deleted after loading and saved in the current format by the {@link SimulatorSaver}.
|
||||
*/
|
||||
@Deprecated
|
||||
public class YAPIONFormatSimulatorLoader implements SimulatorLoader {
|
||||
|
||||
@Override
|
||||
@@ -46,10 +52,10 @@ public class YAPIONFormatSimulatorLoader implements SimulatorLoader {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
YAPIONObject yapionObject;
|
||||
Map<String, Object> yapionObject;
|
||||
try {
|
||||
yapionObject = YAPIONParser.parse(file);
|
||||
} catch (YAPIONException | IOException e) {
|
||||
yapionObject = YapionReader.parse(Files.readString(file.toPath(), StandardCharsets.UTF_8));
|
||||
} catch (IllegalArgumentException | IOException e) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@@ -57,28 +63,28 @@ public class YAPIONFormatSimulatorLoader implements SimulatorLoader {
|
||||
SteamwarUser steamwarUser = SteamwarUser.byId(Integer.parseInt(name));
|
||||
|
||||
List<Simulator> simulators = new ArrayList<>();
|
||||
for (String s : yapionObject.getKeys()) {
|
||||
for (String s : yapionObject.keySet()) {
|
||||
String newName = steamwarUser.getUserName() + (s.isEmpty() ? "" : "_" + s);
|
||||
YAPIONArray content = yapionObject.getArray(s);
|
||||
List<Map<?, ?>> content = getObjects(yapionObject, s);
|
||||
if (content.isEmpty()) continue;
|
||||
Simulator simulator = new Simulator(newName);
|
||||
for (YAPIONObject element : content.streamObject().collect(Collectors.toList())) {
|
||||
for (Map<?, ?> element : content) {
|
||||
SimulatorGroup simulatorGroup = new SimulatorGroup();
|
||||
simulator.add(simulatorGroup);
|
||||
|
||||
TNTElement tntElement = new TNTElement(new Vector(element.getDouble("positionX"), element.getDouble("positionY"), element.getDouble("positionZ")));
|
||||
tntElement.setDisabled(element.getBooleanOrDefault("disabled", false)); // IDK if this existed back then?
|
||||
tntElement.setMaterial(Material.valueOf(element.getStringOrDefault("material", "TNT")));
|
||||
TNTElement tntElement = new TNTElement(new Vector(requireDouble(element, "positionX"), requireDouble(element, "positionY"), requireDouble(element, "positionZ")));
|
||||
tntElement.setDisabled(getBoolean(element, "disabled", false)); // IDK if this existed back then?
|
||||
tntElement.setMaterial(Material.valueOf(getString(element, "material", "TNT")));
|
||||
simulatorGroup.add(tntElement);
|
||||
|
||||
TNTPhase tntPhase = new TNTPhase();
|
||||
tntPhase.setLifetime(element.getIntOrDefault("fuseTicks", 80));
|
||||
tntPhase.setCount(element.getIntOrDefault("count", 1));
|
||||
tntPhase.setTickOffset(element.getIntOrDefault("tickOffset", 0));
|
||||
tntPhase.setXJump(element.getBooleanOrDefault("xVelocity", false));
|
||||
tntPhase.setYJump(element.getBooleanOrDefault("yVelocity", false));
|
||||
tntPhase.setZJump(element.getBooleanOrDefault("zVelocity", false));
|
||||
tntPhase.setOrder(element.getBooleanOrDefault("comparator", false) ? 2 : 0);
|
||||
tntPhase.setLifetime(getInt(element, "fuseTicks", 80));
|
||||
tntPhase.setCount(getInt(element, "count", 1));
|
||||
tntPhase.setTickOffset(getInt(element, "tickOffset", 0));
|
||||
tntPhase.setXJump(getBoolean(element, "xVelocity", false));
|
||||
tntPhase.setYJump(getBoolean(element, "yVelocity", false));
|
||||
tntPhase.setZJump(getBoolean(element, "zVelocity", false));
|
||||
tntPhase.setOrder(getBoolean(element, "comparator", false) ? 2 : 0);
|
||||
tntElement.add(tntPhase);
|
||||
}
|
||||
simulators.add(simulator);
|
||||
@@ -87,4 +93,8 @@ public class YAPIONFormatSimulatorLoader implements SimulatorLoader {
|
||||
file.delete();
|
||||
return Optional.of(simulators);
|
||||
}
|
||||
|
||||
private double requireDouble(Map<?, ?> element, String key) {
|
||||
return ((Number) element.get(key)).doubleValue();
|
||||
}
|
||||
}
|
||||
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2026 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.bausystem.features.slaves.laufbau;
|
||||
|
||||
import de.steamwar.bausystem.config.ConfigStore;
|
||||
import de.steamwar.bausystem.config.VersionedConfig;
|
||||
import de.steamwar.bausystem.features.slaves.laufbau.converter.LaufbauConfigV1ToV2Converter;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* The current laufbau config of a player. It is stored as JSON, fields missing
|
||||
* in the stored JSON keep the default value of their initializer, so adding a
|
||||
* new field does not need a new version.
|
||||
* <p>
|
||||
* When a field gets removed, renamed or its meaning changes, copy this class
|
||||
* to LaufbauConfigV{@link #VERSION} (without the {@link #STORE}), increase {@link #VERSION}, write a
|
||||
* {@link de.steamwar.bausystem.config.ConfigConverter} from the copy to
|
||||
* this class and register it in the {@link #STORE}.
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class LaufbauConfig implements VersionedConfig {
|
||||
|
||||
public static final int VERSION = 2;
|
||||
|
||||
/**
|
||||
* LaufbauConfigV1 is stored as Base64 of gzipped YAPION, the current version as JSON
|
||||
*/
|
||||
public static final ConfigStore<LaufbauConfig> STORE = new ConfigStore<>(LaufbauConfig.class, VERSION, LaufbauConfig::new)
|
||||
.legacy(s -> !s.startsWith("{"), LaufbauConfigV1::parse)
|
||||
.converter(new LaufbauConfigV1ToV2Converter())
|
||||
.userConfig("bausystem-laufbau")
|
||||
.removeIf(config -> config.getDeactivated().isEmpty());
|
||||
|
||||
@Setter(AccessLevel.NONE)
|
||||
protected int version = VERSION;
|
||||
|
||||
/**
|
||||
* Identifiers of the deactivated {@link BlockBoundingBox}es, see {@link LaufbauUtils#identifier(BlockBoundingBox)}
|
||||
*/
|
||||
protected Set<String> deactivated = new HashSet<>();
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2026 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.bausystem.features.slaves.laufbau;
|
||||
|
||||
import de.steamwar.bausystem.config.VersionedConfig;
|
||||
import de.steamwar.bausystem.config.legacy.YapionReader;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
/**
|
||||
* The legacy laufbau config that was stored as Base64 of the gzipped YAPION,
|
||||
* an object with the deactivated identifiers as keys.
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@Deprecated
|
||||
public class LaufbauConfigV1 implements VersionedConfig {
|
||||
|
||||
public static final int VERSION = 1;
|
||||
|
||||
@Setter(AccessLevel.NONE)
|
||||
protected int version = VERSION;
|
||||
|
||||
protected Set<String> deactivated = new HashSet<>();
|
||||
|
||||
public static LaufbauConfigV1 parse(String base64) {
|
||||
String yapion;
|
||||
try (InputStream inputStream = new GZIPInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(base64)))) {
|
||||
yapion = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
LaufbauConfigV1 config = new LaufbauConfigV1();
|
||||
config.deactivated.addAll(YapionReader.parse(yapion).keySet());
|
||||
return config;
|
||||
}
|
||||
}
|
||||
+5
-48
@@ -20,63 +20,22 @@
|
||||
package de.steamwar.bausystem.features.slaves.laufbau;
|
||||
|
||||
import de.steamwar.bausystem.BauSystem;
|
||||
import de.steamwar.core.SWPlayer;
|
||||
import de.steamwar.inventory.SWItem;
|
||||
import de.steamwar.sql.UserConfig;
|
||||
import lombok.Cleanup;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import yapion.hierarchy.output.StreamOutput;
|
||||
import yapion.hierarchy.types.YAPIONObject;
|
||||
import yapion.parser.YAPIONParser;
|
||||
import yapion.parser.options.StreamOptions;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
@UtilityClass
|
||||
public class LaufbauUtils {
|
||||
|
||||
public static class LaufbauComponent implements SWPlayer.Component {
|
||||
|
||||
private YAPIONObject yapionObject = new YAPIONObject();
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void onMount(SWPlayer player) {
|
||||
String config = UserConfig.getConfig(player.getPlayer().getUniqueId(), "bausystem-laufbau");
|
||||
if (config == null) {
|
||||
return;
|
||||
}
|
||||
byte[] bytes = Base64.getDecoder().decode(config);
|
||||
@Cleanup GZIPInputStream gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(bytes));
|
||||
yapionObject = new YAPIONParser(gzipInputStream, new StreamOptions().stopOnStreamEnd(true)).parse().result();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void onUnmount(SWPlayer player) {
|
||||
if (yapionObject.isEmpty()) {
|
||||
UserConfig.removePlayerConfig(player.getPlayer().getUniqueId(), "bausystem-laufbau");
|
||||
return;
|
||||
}
|
||||
@Cleanup ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
@Cleanup GZIPOutputStream gzipOutputStream = new GZIPOutputStream(byteArrayOutputStream);
|
||||
yapionObject.toYAPION(new StreamOutput(gzipOutputStream)).close();
|
||||
UserConfig.updatePlayerConfig(player.getPlayer().getUniqueId(), "bausystem-laufbau", Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray()));
|
||||
}
|
||||
}
|
||||
|
||||
public static Cuboid pixelCuboid(double pixelX, double pixelY, double pixelZ, double pixelDX, double pixelDY, double pixelDZ) {
|
||||
return new Cuboid(pixelToBlock(pixelX), pixelToBlock(pixelY), pixelToBlock(pixelZ), pixelToBlock(pixelDX), pixelToBlock(pixelDY), pixelToBlock(pixelDZ));
|
||||
}
|
||||
@@ -123,7 +82,7 @@ public class LaufbauUtils {
|
||||
return false;
|
||||
}
|
||||
String identifier = identifier(bb);
|
||||
return SWPlayer.of(p).getComponentOrDefault(LaufbauComponent.class, LaufbauComponent::new).yapionObject.containsKey(identifier);
|
||||
return LaufbauConfig.STORE.get(p).getDeactivated().contains(identifier);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@@ -132,11 +91,9 @@ public class LaufbauUtils {
|
||||
return;
|
||||
}
|
||||
String identifier = identifier(bb);
|
||||
YAPIONObject yapionObject = SWPlayer.of(p).getComponentOrDefault(LaufbauComponent.class, LaufbauComponent::new).yapionObject;
|
||||
if (yapionObject.containsKey(identifier)) {
|
||||
yapionObject.remove(identifier);
|
||||
} else {
|
||||
yapionObject.put(identifier, new YAPIONObject());
|
||||
Set<String> deactivated = LaufbauConfig.STORE.get(p).getDeactivated();
|
||||
if (!deactivated.remove(identifier)) {
|
||||
deactivated.add(identifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2026 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.bausystem.features.slaves.laufbau.converter;
|
||||
|
||||
import de.steamwar.bausystem.config.ConfigConverter;
|
||||
import de.steamwar.bausystem.features.slaves.laufbau.LaufbauConfig;
|
||||
import de.steamwar.bausystem.features.slaves.laufbau.LaufbauConfigV1;
|
||||
|
||||
@Deprecated
|
||||
public class LaufbauConfigV1ToV2Converter implements ConfigConverter<LaufbauConfigV1, LaufbauConfig> {
|
||||
|
||||
@Override
|
||||
public int version() {
|
||||
return LaufbauConfigV1.VERSION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LaufbauConfig convert(LaufbauConfigV1 old) {
|
||||
LaufbauConfig config = new LaufbauConfig();
|
||||
config.getDeactivated().addAll(old.getDeactivated());
|
||||
return config;
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -35,8 +35,8 @@ public class SmartPlaceCommand extends SWCommand {
|
||||
|
||||
@Register(description = {"SMART_PLACE_HELP", "SMART_PLACE_INFO"})
|
||||
public void genericToggle(Player p) {
|
||||
boolean smartPlace = Config.getInstance().get(p).getPlainValueOrDefault("smartPlace", false);
|
||||
Config.getInstance().get(p).put("smartPlace", !smartPlace);
|
||||
boolean smartPlace = Config.STORE.get(p).isSmartPlace();
|
||||
Config.STORE.get(p).setSmartPlace(!smartPlace);
|
||||
if (!smartPlace) {
|
||||
SWUtils.sendToActionbar(p, BauSystem.MESSAGE.parse("SMART_PLACE_ENABLE", p));
|
||||
BauSystem.MESSAGE.send("SMART_PLACE_INFO", p);
|
||||
|
||||
+6
-3
@@ -25,6 +25,7 @@ import de.steamwar.bausystem.Permission;
|
||||
import de.steamwar.bausystem.configplayer.Config;
|
||||
import de.steamwar.linkage.Linked;
|
||||
import net.minecraft.network.protocol.game.ServerboundUseItemOnPacket;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockFace;
|
||||
@@ -87,7 +88,9 @@ public class SmartPlaceListener implements Listener {
|
||||
public SmartPlaceListener() {
|
||||
TinyProtocol.instance.addFilter(ServerboundUseItemOnPacket.class, (player, packet) -> {
|
||||
if (!Permission.BUILD.hasPermission(player)) return packet;
|
||||
if (!Config.getInstance().get(player).getPlainValueOrDefault("smartPlace", false)) return packet;
|
||||
if (!Config.STORE.get(player).isSmartPlace()) return packet;
|
||||
ItemStack usedItem = packet.getHand() == InteractionHand.MAIN_HAND ? player.getInventory().getItemInMainHand() : player.getInventory().getItemInOffHand();
|
||||
if (usedItem.getType() == Material.DEBUG_STICK) return packet;
|
||||
RayTraceResult rayTraceResult = player.rayTraceBlocks(6);
|
||||
Block block = rayTraceResult != null ? rayTraceResult.getHitBlock() : null;
|
||||
BlockFace blockFace = rayTraceResult != null ? rayTraceResult.getHitBlockFace() : null;
|
||||
@@ -127,7 +130,7 @@ public class SmartPlaceListener implements Listener {
|
||||
@EventHandler
|
||||
public void onPlayerInteract(PlayerInteractEvent event) {
|
||||
if (!Permission.BUILD.hasPermission(event.getPlayer())) return;
|
||||
if (!Config.getInstance().get(event.getPlayer()).getPlainValueOrDefault("smartPlace", false)) return;
|
||||
if (!Config.STORE.get(event.getPlayer()).isSmartPlace()) return;
|
||||
WAS_EXECUTED.add(event.getPlayer());
|
||||
if (event.getAction() != Action.RIGHT_CLICK_BLOCK) return;
|
||||
if (event.getPlayer().getGameMode() == GameMode.SPECTATOR) return;
|
||||
@@ -146,7 +149,7 @@ public class SmartPlaceListener implements Listener {
|
||||
@EventHandler
|
||||
public void onBlockPlace(BlockPlaceEvent event) {
|
||||
if (!Permission.BUILD.hasPermission(event.getPlayer())) return;
|
||||
if (!Config.getInstance().get(event.getPlayer()).getPlainValueOrDefault("smartPlace", false)) return;
|
||||
if (!Config.STORE.get(event.getPlayer()).isSmartPlace()) return;
|
||||
if (!SMART_PLACING.contains(event.getPlayer())) {
|
||||
if (CONTAINERS.contains(event.getBlockAgainst().getType())) {
|
||||
SoundGroup soundGroup = event.getBlockPlaced().getBlockData().getSoundGroup();
|
||||
|
||||
+3
-6
@@ -35,18 +35,15 @@ public class BlockCounter {
|
||||
public final Map<Region, BlockCount> blockCountMap = new HashMap<>();
|
||||
|
||||
public boolean isActive(final Player p) {
|
||||
if (!Config.getInstance().get(p).containsKey("blockCounter")) {
|
||||
return false;
|
||||
}
|
||||
return Config.getInstance().get(p).getPlainValue("blockCounter");
|
||||
return Config.STORE.get(p).isBlockCounter();
|
||||
}
|
||||
|
||||
public void enable(final Player p) {
|
||||
Config.getInstance().get(p).put("blockCounter", true);
|
||||
Config.STORE.get(p).setBlockCounter(true);
|
||||
}
|
||||
|
||||
public void disable(final Player p) {
|
||||
Config.getInstance().get(p).put("blockCounter", false);
|
||||
Config.STORE.get(p).setBlockCounter(false);
|
||||
}
|
||||
|
||||
public String getMessage(Player player, int count, int tntCount, long tick, long lastTick) {
|
||||
|
||||
+1
-4
@@ -36,13 +36,10 @@ import org.bukkit.entity.Player;
|
||||
import org.bukkit.util.Transformation;
|
||||
import org.joml.Quaternionf;
|
||||
import org.joml.Vector3f;
|
||||
import yapion.hierarchy.types.YAPIONValue;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static de.steamwar.bausystem.features.util.TNTClickListener.TNT_CLICK_DETAILS;
|
||||
|
||||
/**
|
||||
* Wrapper for the rendering of a record bundle
|
||||
*/
|
||||
@@ -102,7 +99,7 @@ public class TraceEntity extends RBlockDisplay {
|
||||
* @param player the player the message should be printed for
|
||||
*/
|
||||
public void printIntoChat(Player player) {
|
||||
if (!Config.getInstance().get(player).getOrSetDefault(TNT_CLICK_DETAILS, new YAPIONValue<>(true)).asBoolean().orElse(true)) {
|
||||
if (!Config.STORE.get(player).isTntClickDetails()) {
|
||||
return;
|
||||
}
|
||||
TNTPoint representative = records.get(0);
|
||||
|
||||
+4
-5
@@ -30,7 +30,6 @@ import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.potion.PotionEffect;
|
||||
import org.bukkit.potion.PotionEffectType;
|
||||
import yapion.hierarchy.types.YAPIONObject;
|
||||
|
||||
@Linked
|
||||
public class NightVisionCommand extends SWCommand implements Listener {
|
||||
@@ -41,15 +40,15 @@ public class NightVisionCommand extends SWCommand implements Listener {
|
||||
|
||||
@Register(description = "NIGHT_VISION_HELP")
|
||||
public void genericCommand(Player p) {
|
||||
YAPIONObject yapionObject = Config.getInstance().get(p);
|
||||
boolean value = !yapionObject.getBooleanOrDefault("nightvision", false);
|
||||
yapionObject.put("nightvision", value);
|
||||
Config config = Config.STORE.get(p);
|
||||
boolean value = !config.isNightVision();
|
||||
config.setNightVision(value);
|
||||
setNightVision(p, value);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||
setNightVision(event.getPlayer(), Config.getInstance().get(event.getPlayer()).getBooleanOrDefault("nightvision", false));
|
||||
setNightVision(event.getPlayer(), Config.STORE.get(event.getPlayer()).isNightVision());
|
||||
}
|
||||
|
||||
private void setNightVision(Player p, boolean value) {
|
||||
|
||||
+31
-89
@@ -22,122 +22,71 @@ package de.steamwar.bausystem.features.util;
|
||||
import com.comphenix.tinyprotocol.TinyProtocol;
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import de.steamwar.bausystem.BauSystem;
|
||||
import de.steamwar.bausystem.features.tpslimit.TPSUtils;
|
||||
import de.steamwar.bausystem.utils.BauMemberUpdateEvent;
|
||||
import de.steamwar.command.SWCommand;
|
||||
import de.steamwar.core.ProtocolWrapper;
|
||||
import de.steamwar.core.SWPlayer;
|
||||
import de.steamwar.linkage.Linked;
|
||||
import net.minecraft.network.protocol.game.*;
|
||||
import net.minecraft.world.entity.player.Abilities;
|
||||
import net.minecraft.world.level.GameType;
|
||||
import org.bukkit.Bukkit;
|
||||
import io.papermc.paper.event.player.PlayerFailMoveEvent;
|
||||
import net.minecraft.network.protocol.game.ClientboundGameEventPacket;
|
||||
import org.bukkit.GameMode;
|
||||
import org.bukkit.craftbukkit.entity.CraftPlayer;
|
||||
import org.bukkit.craftbukkit.inventory.CraftItemStack;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.BlockCanBuildEvent;
|
||||
import org.bukkit.event.player.PlayerGameModeChangeEvent;
|
||||
import org.bukkit.event.player.PlayerTeleportEvent;
|
||||
import org.bukkit.event.player.PlayerToggleFlightEvent;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
@Linked
|
||||
public class NoClipCommand extends SWCommand implements Listener {
|
||||
|
||||
public static class NoClipData implements SWPlayer.Component {
|
||||
private long lastTick = -1;
|
||||
|
||||
@Override
|
||||
public void onUnmount(SWPlayer player) {
|
||||
player.setGameMode(GameMode.CREATIVE);
|
||||
public void onUnmount(SWPlayer swPlayer) {
|
||||
Player player = swPlayer.getPlayer();
|
||||
showClientGameMode(player, GameMode.CREATIVE, GameMode.CREATIVE);
|
||||
}
|
||||
}
|
||||
|
||||
public NoClipCommand() {
|
||||
super("noclip", "nc");
|
||||
|
||||
BiFunction<Player, ServerboundMovePlayerPacket, Object> first = (player, o) -> {
|
||||
NoClipData noClipData = SWPlayer.of(player).getComponent(NoClipData.class).orElse(null);
|
||||
if (noClipData == null) return o;
|
||||
if (noClipData.lastTick == TPSUtils.currentTick.get()) return o;
|
||||
setInternalGameMode(player, GameMode.SPECTATOR);
|
||||
noClipData.lastTick = TPSUtils.currentTick.get();
|
||||
return o;
|
||||
};
|
||||
TinyProtocol.instance.addFilter(ServerboundMovePlayerPacket.Pos.class, first);
|
||||
TinyProtocol.instance.addFilter(ServerboundMovePlayerPacket.PosRot.class, first);
|
||||
|
||||
BiFunction<Player, Object, Object> second = (player, o) -> {
|
||||
NoClipData noClipData = SWPlayer.of(player).getComponent(NoClipData.class).orElse(null);
|
||||
if (noClipData == null) return o;
|
||||
setInternalGameMode(player, GameMode.CREATIVE);
|
||||
noClipData.lastTick = TPSUtils.currentTick.get();
|
||||
return o;
|
||||
};
|
||||
TinyProtocol.instance.addFilter(ServerboundUseItemOnPacket.class, second);
|
||||
TinyProtocol.instance.addFilter(ServerboundPlayerActionPacket.class, second);
|
||||
TinyProtocol.instance.addFilter(ServerboundContainerClickPacket.class, second);
|
||||
|
||||
BiFunction<Player, ServerboundSetCreativeModeSlotPacket, Object> third = (player, o) -> {
|
||||
if (SWPlayer.of(player).hasComponent(NoClipData.class)) {
|
||||
int index = o.slotNum();
|
||||
if (index >= 36 && index <= 44) {
|
||||
index -= 36;
|
||||
} else if (index > 44) {
|
||||
index -= 5;
|
||||
} else if (index <= 8) {
|
||||
index = index - 8 + 36;
|
||||
}
|
||||
player.getInventory().setItem(index, CraftItemStack.asBukkitCopy(o.itemStack()));
|
||||
if (index < 9) player.getInventory().setHeldItemSlot(index);
|
||||
player.updateInventory();
|
||||
}
|
||||
return o;
|
||||
};
|
||||
TinyProtocol.instance.addFilter(ServerboundSetCreativeModeSlotPacket.class, third);
|
||||
}
|
||||
|
||||
private void setInternalGameMode(Player player, GameMode gameMode) {
|
||||
((CraftPlayer) player).getHandle().gameMode.gameModeForPlayer = GameType.byId(gameMode.getValue());
|
||||
}
|
||||
|
||||
@Register(help = true)
|
||||
@Register
|
||||
public void genericCommand(@Validator Player player) {
|
||||
SWPlayer swPlayer = SWPlayer.of(player);
|
||||
if (swPlayer.hasComponent(NoClipData.class)) {
|
||||
swPlayer.removeComponent(NoClipData.class);
|
||||
} else {
|
||||
player.setGameMode(GameMode.SPECTATOR);
|
||||
Abilities abilities = (((CraftPlayer) player).getHandle()).getAbilities();
|
||||
abilities.mayBuild = true;
|
||||
abilities.mayfly = true;
|
||||
|
||||
player.setGameMode(GameMode.CREATIVE);
|
||||
player.setAllowFlight(true);
|
||||
player.setFlying(true);
|
||||
swPlayer.setComponent(new NoClipData());
|
||||
BauSystem.MESSAGE.send("OTHER_NOCLIP_SLOT_INFO", player);
|
||||
TinyProtocol.instance.sendPacket(player, new ClientboundGameEventPacket(ClientboundGameEventPacket.CHANGE_GAME_MODE, 1F));
|
||||
pseudoGameMode(player, GameMode.SPECTATOR);
|
||||
showClientGameMode(player, GameMode.CREATIVE, GameMode.SPECTATOR);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onBauMemberUpdate(BauMemberUpdateEvent event) {
|
||||
event.getNewSpectator().forEach(player -> {
|
||||
SWPlayer.of(player).removeComponent(NoClipData.class);
|
||||
});
|
||||
public void onPlayerFailMove(PlayerFailMoveEvent event) {
|
||||
if (event.getFailReason() != PlayerFailMoveEvent.FailReason.CLIPPED_INTO_BLOCK) return;
|
||||
if (!SWPlayer.of(event.getPlayer()).hasComponent(NoClipData.class)) return;
|
||||
event.setAllowed(true);
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
@EventHandler
|
||||
public void onBauMemberUpdate(BauMemberUpdateEvent event) {
|
||||
event.getNewSpectator().forEach(player -> SWPlayer.of(player).removeComponent(NoClipData.class));
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerGameModeChange(PlayerGameModeChangeEvent event) {
|
||||
if (SWPlayer.of(event.getPlayer()).hasComponent(NoClipData.class)) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
@EventHandler
|
||||
public void onBlock(BlockCanBuildEvent event) {
|
||||
if (event.getPlayer() == null) return;
|
||||
if (SWPlayer.of(event.getPlayer()).hasComponent(NoClipData.class)) {
|
||||
@@ -145,7 +94,7 @@ public class NoClipCommand extends SWCommand implements Listener {
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
@EventHandler
|
||||
public void onPlayerToggleFlight(PlayerToggleFlightEvent event) {
|
||||
if (SWPlayer.of(event.getPlayer()).hasComponent(NoClipData.class)) {
|
||||
event.setCancelled(true);
|
||||
@@ -153,20 +102,13 @@ public class NoClipCommand extends SWCommand implements Listener {
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onPlayerTeleport(PlayerTeleportEvent event) {
|
||||
if (event.getCause() != PlayerTeleportEvent.TeleportCause.SPECTATE) {
|
||||
return;
|
||||
}
|
||||
if (SWPlayer.of(event.getPlayer()).hasComponent(NoClipData.class)) {
|
||||
event.setCancelled(true);
|
||||
Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> {
|
||||
event.getPlayer().setSpectatorTarget(null);
|
||||
}, 1L);
|
||||
}
|
||||
/**
|
||||
* @param player player
|
||||
* @param localMode GameMode for breaking and placing
|
||||
* @param listedMode GameMode for noPhysics
|
||||
*/
|
||||
private static void showClientGameMode(Player player, GameMode localMode, GameMode listedMode) {
|
||||
TinyProtocol.instance.sendPacket(player, new ClientboundGameEventPacket(ClientboundGameEventPacket.CHANGE_GAME_MODE, localMode.getValue()));
|
||||
TinyProtocol.instance.sendPacket(player, ProtocolWrapper.playerInfoPacketConstructor(ProtocolWrapper.PlayerInfoAction.GAMEMODE, new GameProfile(player.getUniqueId(), player.getName()), listedMode));
|
||||
}
|
||||
|
||||
private static void pseudoGameMode(Player player, GameMode gameMode) {
|
||||
TinyProtocol.instance.sendPacket(player, ProtocolWrapper.playerInfoPacketConstructor(ProtocolWrapper.PlayerInfoAction.GAMEMODE, new GameProfile(player.getUniqueId(), player.getName()), gameMode));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+57
-141
@@ -23,15 +23,17 @@ import de.steamwar.bausystem.BauSystem;
|
||||
import de.steamwar.bausystem.Permission;
|
||||
import de.steamwar.entity.*;
|
||||
import de.steamwar.linkage.Linked;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.level.block.piston.PistonStructureResolver;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockFace;
|
||||
import org.bukkit.block.PistonMoveReaction;
|
||||
import org.bukkit.block.TileState;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.block.data.type.Piston;
|
||||
import org.bukkit.craftbukkit.CraftWorld;
|
||||
import org.bukkit.entity.Display;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.entity.TextDisplay;
|
||||
@@ -92,41 +94,48 @@ public class PistonCalculator implements Listener {
|
||||
@EventHandler
|
||||
public void onBlockPistonExtend(BlockPistonExtendEvent event) {
|
||||
if (pistOrders.isEmpty()) return;
|
||||
Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> {
|
||||
movePistOrders(event.getDirection(), event.getBlocks());
|
||||
pistOrders.values().forEach(PistOrder::calculate);
|
||||
}, 3);
|
||||
removeMovedPistOrders(event.getBlocks());
|
||||
scheduleRecalculate();
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onBlockPistonRetract(BlockPistonRetractEvent event) {
|
||||
if (pistOrders.isEmpty()) return;
|
||||
Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> {
|
||||
movePistOrders(event.getDirection(), event.getBlocks());
|
||||
pistOrders.values().forEach(PistOrder::calculate);
|
||||
}, 3);
|
||||
removeMovedPistOrders(event.getBlocks());
|
||||
scheduleRecalculate();
|
||||
}
|
||||
|
||||
private void movePistOrders(BlockFace direction, List<Block> blocks) {
|
||||
Set<PistOrder> orders = new HashSet<>();
|
||||
private void removeMovedPistOrders(List<Block> blocks) {
|
||||
blocks.forEach(block -> {
|
||||
PistOrder pistOrder = pistOrders.get(block.getLocation());
|
||||
if (pistOrder == null) return;
|
||||
pistOrders.remove(block.getLocation());
|
||||
pistOrder.piston = pistOrder.piston.getRelative(direction);
|
||||
orders.add(pistOrder);
|
||||
});
|
||||
orders.forEach(pistOrder -> {
|
||||
pistOrders.put(pistOrder.piston.getLocation(), pistOrder);
|
||||
PistOrder pistOrder = pistOrders.remove(block.getLocation());
|
||||
if (pistOrder != null) pistOrder.server.close();
|
||||
});
|
||||
}
|
||||
|
||||
private static final long RECALCULATE_INTERVAL_MS = 200;
|
||||
private boolean recalculationScheduled = false;
|
||||
private long lastRecalculation = 0;
|
||||
|
||||
private void scheduleRecalculate() {
|
||||
if (recalculationScheduled) return;
|
||||
recalculationScheduled = true;
|
||||
Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), this::recalculateIfDue, 1);
|
||||
}
|
||||
|
||||
private void recalculateIfDue() {
|
||||
if (System.currentTimeMillis() - lastRecalculation < RECALCULATE_INTERVAL_MS) {
|
||||
Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), this::recalculateIfDue, 1);
|
||||
return;
|
||||
}
|
||||
recalculationScheduled = false;
|
||||
lastRecalculation = System.currentTimeMillis();
|
||||
pistOrders.values().forEach(PistOrder::calculate);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onBlockPlace(BlockPlaceEvent event) {
|
||||
if (pistOrders.isEmpty()) return;
|
||||
Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> {
|
||||
pistOrders.values().forEach(PistOrder::calculate);
|
||||
}, 1);
|
||||
scheduleRecalculate();
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
@@ -136,9 +145,7 @@ public class PistonCalculator implements Listener {
|
||||
PistOrder pistOrder = pistOrders.remove(event.getBlock().getLocation());
|
||||
pistOrder.server.close();
|
||||
}
|
||||
Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> {
|
||||
pistOrders.values().forEach(PistOrder::calculate);
|
||||
}, 1);
|
||||
scheduleRecalculate();
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
@@ -164,13 +171,13 @@ public class PistonCalculator implements Listener {
|
||||
private final Map<Location, PistOrder> pistOrders = new HashMap<>();
|
||||
|
||||
private final class PistOrder {
|
||||
private Block piston;
|
||||
private final Block piston;
|
||||
private REntityServer server = new REntityServer();
|
||||
|
||||
private boolean pulling = false;
|
||||
private List<Location> movedBlocks = new ArrayList<>();
|
||||
private List<Location> brokenBlocks = new ArrayList<>();
|
||||
private Set<Location> immovableBlocks = new HashSet<>();
|
||||
private boolean blocked = false;
|
||||
|
||||
public PistOrder(Block piston) {
|
||||
this.piston = piston;
|
||||
@@ -179,7 +186,7 @@ public class PistonCalculator implements Listener {
|
||||
public void calculate() {
|
||||
movedBlocks.clear();
|
||||
brokenBlocks.clear();
|
||||
immovableBlocks.clear();
|
||||
blocked = false;
|
||||
|
||||
if (piston.isEmpty()) {
|
||||
Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> {
|
||||
@@ -199,7 +206,7 @@ public class PistonCalculator implements Listener {
|
||||
|
||||
pulling = piston.getType() == Material.STICKY_PISTON && (piston.getRelative(pistonData.getFacing()).getType() == Material.AIR || pistonData.isExtended());
|
||||
|
||||
calculate(piston, pistonData.getFacing(), (pulling ? pistonData.getFacing().getOppositeFace() : pistonData.getFacing()));
|
||||
resolve(pistonData.getFacing());
|
||||
|
||||
Collections.reverse(movedBlocks);
|
||||
Collections.reverse(brokenBlocks);
|
||||
@@ -207,86 +214,23 @@ public class PistonCalculator implements Listener {
|
||||
visualize();
|
||||
}
|
||||
|
||||
private void calculate(Block origin, BlockFace facing, BlockFace direction) {
|
||||
Block calcOrigin = origin;
|
||||
if (facing != direction) calcOrigin = origin.getRelative(facing, 3);
|
||||
|
||||
List<Block> toCalc = new LinkedList<>();
|
||||
calcDirection(origin, null, calcOrigin, facing != direction ? origin.getRelative(facing) : null, facing, direction, toCalc);
|
||||
|
||||
while (!toCalc.isEmpty()) {
|
||||
Block current = toCalc.remove(0);
|
||||
if (!movedBlocks.contains(current.getLocation())) {
|
||||
movedBlocks.add(current.getLocation());
|
||||
}
|
||||
|
||||
Material type = current.getType();
|
||||
if (type != Material.SLIME_BLOCK && type != Material.HONEY_BLOCK) continue;
|
||||
Material oppositeType = type == Material.SLIME_BLOCK ? Material.HONEY_BLOCK : Material.SLIME_BLOCK;
|
||||
|
||||
for (BlockFace face : FACES) {
|
||||
Block block = current.getRelative(face);
|
||||
if (block.getType().isAir()) continue;
|
||||
if (isImmovable(block) || block.getPistonMoveReaction() == PistonMoveReaction.BREAK) continue;
|
||||
if (face != direction || face == direction.getOppositeFace()) {
|
||||
switch (block.getType()) {
|
||||
case BLACK_GLAZED_TERRACOTTA:
|
||||
case GRAY_GLAZED_TERRACOTTA:
|
||||
case BLUE_GLAZED_TERRACOTTA:
|
||||
case BROWN_GLAZED_TERRACOTTA:
|
||||
case CYAN_GLAZED_TERRACOTTA:
|
||||
case GREEN_GLAZED_TERRACOTTA:
|
||||
case LIGHT_BLUE_GLAZED_TERRACOTTA:
|
||||
case LIGHT_GRAY_GLAZED_TERRACOTTA:
|
||||
case LIME_GLAZED_TERRACOTTA:
|
||||
case MAGENTA_GLAZED_TERRACOTTA:
|
||||
case ORANGE_GLAZED_TERRACOTTA:
|
||||
case PINK_GLAZED_TERRACOTTA:
|
||||
case PURPLE_GLAZED_TERRACOTTA:
|
||||
case RED_GLAZED_TERRACOTTA:
|
||||
case WHITE_GLAZED_TERRACOTTA:
|
||||
case YELLOW_GLAZED_TERRACOTTA:
|
||||
continue;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (block.getType() != oppositeType) {
|
||||
if (!movedBlocks.contains(block.getLocation())) toCalc.add(block);
|
||||
calcDirection(null, origin, block, null, facing, direction, toCalc);
|
||||
}
|
||||
}
|
||||
private void resolve(BlockFace facing) {
|
||||
PistonStructureResolver resolver = new PistonStructureResolver(
|
||||
((CraftWorld) piston.getWorld()).getHandle(),
|
||||
new BlockPos(piston.getX(), piston.getY(), piston.getZ()),
|
||||
Direction.valueOf(facing.name()),
|
||||
!pulling
|
||||
);
|
||||
if (!resolver.resolve()) {
|
||||
blocked = true;
|
||||
return;
|
||||
}
|
||||
|
||||
movedBlocks.remove(origin.getLocation());
|
||||
if (facing != direction) movedBlocks.remove(origin.getRelative(facing, 1).getLocation());
|
||||
if (pulling) immovableBlocks.remove(origin.getRelative(facing).getLocation());
|
||||
resolver.getToPush().forEach(pos -> movedBlocks.add(toLocation(pos)));
|
||||
resolver.getToDestroy().forEach(pos -> brokenBlocks.add(toLocation(pos)));
|
||||
}
|
||||
|
||||
private void calcDirection(Block origin, Block blockOrigin, Block calcOrigin, Block ignore, BlockFace facing, BlockFace direction, List<Block> toCalc) {
|
||||
for (int i = 1; i < 24; i++) {
|
||||
Block block = calcOrigin.getRelative(direction, i);
|
||||
if (block.equals(ignore)) return;
|
||||
if (block.getPistonMoveReaction() == PistonMoveReaction.BREAK) {
|
||||
if (!brokenBlocks.contains(block.getLocation())) {
|
||||
brokenBlocks.add(block.getLocation());
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (isImmovable(block)) {
|
||||
immovableBlocks.add(block.getLocation());
|
||||
return;
|
||||
}
|
||||
if (block.getType().isAir()) return;
|
||||
if (facing == direction && block.equals(blockOrigin)) {
|
||||
immovableBlocks.add(block.getLocation());
|
||||
return;
|
||||
}
|
||||
if (facing != direction && (block.equals(origin) || block.getRelative(facing.getOppositeFace()).equals(origin))) {
|
||||
return;
|
||||
}
|
||||
if (!movedBlocks.contains(block.getLocation())) toCalc.add(block);
|
||||
}
|
||||
private Location toLocation(BlockPos pos) {
|
||||
return new Location(piston.getWorld(), pos.getX(), pos.getY(), pos.getZ());
|
||||
}
|
||||
|
||||
private void visualize() {
|
||||
@@ -321,21 +265,11 @@ public class PistonCalculator implements Listener {
|
||||
textDisplay.setBrightness(new Display.Brightness(15, 15));
|
||||
}
|
||||
|
||||
for (Location location : immovableBlocks) {
|
||||
CWireframe wireframe = new CWireframe(server);
|
||||
wireframe.setPos1(location);
|
||||
wireframe.setPos2(location);
|
||||
wireframe.setWidth(1 / 32f);
|
||||
wireframe.setBlock(Material.RED_CONCRETE.createBlockData());
|
||||
}
|
||||
|
||||
CWireframe wireframe = new CWireframe(server);
|
||||
wireframe.setPos1(piston.getLocation());
|
||||
wireframe.setPos2(piston.getLocation());
|
||||
if (!immovableBlocks.isEmpty()) {
|
||||
if (blocked) {
|
||||
wireframe.setBlock(Material.RED_CONCRETE.createBlockData());
|
||||
} else if (movedBlocks.size() > 12) {
|
||||
wireframe.setBlock(Material.YELLOW_CONCRETE.createBlockData());
|
||||
} else {
|
||||
wireframe.setBlock(Material.LIME_CONCRETE.createBlockData());
|
||||
}
|
||||
@@ -348,7 +282,11 @@ public class PistonCalculator implements Listener {
|
||||
} else {
|
||||
text.append("§ePush\n");
|
||||
}
|
||||
text.append("§f").append(movedBlocks.size()).append(" §eBlocks");
|
||||
if (blocked) {
|
||||
text.append("§cBlocked");
|
||||
} else {
|
||||
text.append("§f").append(movedBlocks.size()).append(" §eBlocks");
|
||||
}
|
||||
textDisplay.setText(text.toString());
|
||||
textDisplay.setBillboard(Display.Billboard.CENTER);
|
||||
textDisplay.setAlignment(TextDisplay.TextAlignment.CENTER);
|
||||
@@ -358,26 +296,4 @@ public class PistonCalculator implements Listener {
|
||||
textDisplay.setBrightness(new Display.Brightness(15, 15));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isImmovable(Block block) {
|
||||
if (block.isEmpty() || block.isLiquid()) {
|
||||
return false;
|
||||
}
|
||||
BlockData blockData = block.getBlockData();
|
||||
if (blockData instanceof Piston) {
|
||||
return ((Piston) blockData).isExtended();
|
||||
}
|
||||
if (block.getState() instanceof TileState) {
|
||||
return true;
|
||||
}
|
||||
PistonMoveReaction reaction = block.getPistonMoveReaction();
|
||||
if (reaction == PistonMoveReaction.IGNORE) return true;
|
||||
if (reaction == PistonMoveReaction.BLOCK) return true;
|
||||
switch (block.getType()) {
|
||||
case OBSIDIAN:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
-9
@@ -32,26 +32,22 @@ import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerInteractEntityEvent;
|
||||
import org.bukkit.inventory.EquipmentSlot;
|
||||
import yapion.hierarchy.types.YAPIONObject;
|
||||
import yapion.hierarchy.types.YAPIONValue;
|
||||
|
||||
@Linked
|
||||
public class TNTClickListener extends SWCommand implements Listener {
|
||||
|
||||
public static final String TNT_CLICK_DETAILS = "tnt_click_details";
|
||||
|
||||
public TNTClickListener() {
|
||||
super("tntdetails");
|
||||
}
|
||||
|
||||
@Register(description = "TNT_DETAILS_COMMAND")
|
||||
public void toggle(Player player) {
|
||||
YAPIONObject yapionObject = Config.getInstance().get(player);
|
||||
if (yapionObject.getOrSetDefault(TNT_CLICK_DETAILS, new YAPIONValue<>(true)).asBoolean().orElse(true)) {
|
||||
yapionObject.put(TNT_CLICK_DETAILS, false);
|
||||
Config config = Config.STORE.get(player);
|
||||
if (config.isTntClickDetails()) {
|
||||
config.setTntClickDetails(false);
|
||||
BauSystem.MESSAGE.send("TNT_DETAILS_OFF", player);
|
||||
} else {
|
||||
yapionObject.put(TNT_CLICK_DETAILS, true);
|
||||
config.setTntClickDetails(true);
|
||||
BauSystem.MESSAGE.send("TNT_DETAILS_ON", player);
|
||||
}
|
||||
}
|
||||
@@ -60,7 +56,7 @@ public class TNTClickListener extends SWCommand implements Listener {
|
||||
public void onPlayerInteractEntity(PlayerInteractEntityEvent event) {
|
||||
if (!Permission.BUILD.hasPermission(event.getPlayer())) return;
|
||||
if (event.getHand() != EquipmentSlot.HAND) return;
|
||||
if (!Config.getInstance().get(event.getPlayer()).getOrSetDefault(TNT_CLICK_DETAILS, new YAPIONValue<>(true)).asBoolean().orElse(true)) {
|
||||
if (!Config.STORE.get(event.getPlayer()).isTntClickDetails()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@ import lombok.Getter;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.player.PlayerTeleportEvent;
|
||||
import yapion.hierarchy.types.YAPIONObject;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@@ -56,27 +55,22 @@ public class Warp {
|
||||
warpMap.put(name, this);
|
||||
}
|
||||
|
||||
public Warp(String name, YAPIONObject object) {
|
||||
public Warp(String name, WorldData.WarpEntry warp) {
|
||||
this.name = name;
|
||||
double x = object.getPlainValue("x");
|
||||
double y = object.getPlainValue("y");
|
||||
double z = object.getPlainValue("z");
|
||||
float yaw = object.getPlainValue("yaw");
|
||||
float pitch = object.getPlainValue("pitch");
|
||||
location = new Location(Bukkit.getWorlds().get(0), x, y, z, yaw, pitch);
|
||||
mat = Material.getMaterial(object.getPlainValue("material"));
|
||||
location = new Location(Bukkit.getWorlds().get(0), warp.getX(), warp.getY(), warp.getZ(), warp.getYaw(), warp.getPitch());
|
||||
mat = Material.getMaterial(warp.getMaterial());
|
||||
warpMap.put(name, this);
|
||||
}
|
||||
|
||||
public YAPIONObject output() {
|
||||
YAPIONObject yapionObject = new YAPIONObject();
|
||||
yapionObject.add("x", location.getX());
|
||||
yapionObject.add("y", location.getY());
|
||||
yapionObject.add("z", location.getZ());
|
||||
yapionObject.add("yaw", location.getYaw());
|
||||
yapionObject.add("pitch", location.getPitch());
|
||||
yapionObject.add("material", mat.toString());
|
||||
return yapionObject;
|
||||
public WorldData.WarpEntry output() {
|
||||
WorldData.WarpEntry warp = new WorldData.WarpEntry();
|
||||
warp.setX(location.getX());
|
||||
warp.setY(location.getY());
|
||||
warp.setZ(location.getZ());
|
||||
warp.setYaw(location.getYaw());
|
||||
warp.setPitch(location.getPitch());
|
||||
warp.setMaterial(mat.toString());
|
||||
return warp;
|
||||
}
|
||||
|
||||
public static Set<String> getWarpNames() {
|
||||
@@ -101,7 +95,7 @@ public class Warp {
|
||||
|
||||
public void delete() {
|
||||
warpMap.remove(name);
|
||||
WorldData.getWarpData().remove(name);
|
||||
WorldData.STORE.get().getWarps().remove(name);
|
||||
}
|
||||
|
||||
public void teleport(Player player) {
|
||||
|
||||
@@ -34,9 +34,9 @@ import net.md_5.bungee.api.chat.ComponentBuilder;
|
||||
import net.md_5.bungee.api.chat.HoverEvent;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import org.bukkit.entity.Player;
|
||||
import yapion.hierarchy.types.YAPIONObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
|
||||
@Linked
|
||||
public class WarpCommand extends SWCommand implements Disable, Enable {
|
||||
@@ -135,17 +135,15 @@ public class WarpCommand extends SWCommand implements Disable, Enable {
|
||||
|
||||
@Override
|
||||
public void enable() {
|
||||
WorldData.getWarpData().forEach((name, yapionAnyType) -> {
|
||||
new Warp(name, (YAPIONObject) yapionAnyType);
|
||||
});
|
||||
WorldData.STORE.get().getWarps().forEach(Warp::new);
|
||||
Warp.enable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disable() {
|
||||
YAPIONObject yapionObject = WorldData.getWarpData();
|
||||
Map<String, WorldData.WarpEntry> warps = WorldData.STORE.get().getWarps();
|
||||
for (Warp warp : Warp.getWarps()) {
|
||||
yapionObject.add(warp.getName(), warp.output());
|
||||
warps.put(warp.getName(), warp.output());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import de.steamwar.bausystem.utils.bossbar.BossBarService;
|
||||
import de.steamwar.linkage.Linked;
|
||||
import de.steamwar.network.packets.PacketHandler;
|
||||
import de.steamwar.network.packets.server.BaumemberUpdatePacket;
|
||||
import de.steamwar.sql.BauweltMember;
|
||||
import io.papermc.paper.event.player.PlayerPickBlockEvent;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.boss.BarColor;
|
||||
@@ -57,6 +58,8 @@ public class BauMemberUpdate extends PacketHandler implements Listener {
|
||||
}
|
||||
|
||||
public static void baumemberUpdate() {
|
||||
// Packet handlers run in no guaranteed order, so don't rely on CoreNetworkHandler having cleared the cache yet.
|
||||
BauweltMember.clear();
|
||||
Set<Player> newSpectator = new HashSet<>();
|
||||
Set<Player> newBuilder = new HashSet<>();
|
||||
Bukkit.getOnlinePlayers().forEach(player -> {
|
||||
|
||||
@@ -131,6 +131,7 @@ public class BauScoreboard implements Listener {
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerQuit(PlayerQuitEvent event) {
|
||||
SWScoreboard.impl.removeScoreboard(event.getPlayer());
|
||||
ADDITIONAL_SCOREBOARD_LINES.remove(event.getPlayer());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,78 +20,39 @@
|
||||
package de.steamwar.bausystem.region;
|
||||
|
||||
import de.steamwar.bausystem.region.flags.Flag;
|
||||
import de.steamwar.bausystem.worlddata.WorldData;
|
||||
import de.steamwar.sql.SchematicNode;
|
||||
import lombok.NonNull;
|
||||
import yapion.hierarchy.types.YAPIONObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
public abstract class RegionData {
|
||||
|
||||
private final List<Property<?, ?>> properties = new ArrayList<>();
|
||||
|
||||
protected final YAPIONObject data;
|
||||
protected final YAPIONObject flagData;
|
||||
protected final WorldData.RegionEntry data;
|
||||
protected final Map<String, String> flagData;
|
||||
protected final Runnable onChange;
|
||||
protected final Map<Flag<?>, Flag.Value<?>> flagMap = new HashMap<>();
|
||||
|
||||
private final class Property<T, K> {
|
||||
private final String field;
|
||||
private final Function<K, T> loader;
|
||||
private final Function<T, K> writer;
|
||||
private SchematicNode testblockSchematic;
|
||||
|
||||
private T value;
|
||||
|
||||
public Property(String field, Function<K, T> loader, Function<T, K> writer) {
|
||||
this.field = field;
|
||||
this.loader = loader;
|
||||
this.writer = writer;
|
||||
properties.add(this);
|
||||
}
|
||||
|
||||
public void load() {
|
||||
if (flagData.containsKey(field)) {
|
||||
value = loader.apply(flagData.getPlainValue(field));
|
||||
} else {
|
||||
value = null;
|
||||
}
|
||||
}
|
||||
|
||||
public T get() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void set(T value) {
|
||||
this.value = value;
|
||||
if (value == null) {
|
||||
flagData.remove(field);
|
||||
} else {
|
||||
flagData.put(field, writer.apply(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Property<SchematicNode, Integer> testblockSchematic = new Property<>("testblockSchematic", SchematicNode::byId, SchematicNode::getId);
|
||||
|
||||
protected RegionData(YAPIONObject data, Runnable onChange) {
|
||||
protected RegionData(WorldData.RegionEntry data, Runnable onChange) {
|
||||
this.data = data;
|
||||
this.flagData = data.getObjectOrSetDefault("flagStorage", new YAPIONObject());
|
||||
this.flagData = data.getFlags();
|
||||
this.onChange = onChange;
|
||||
initialize();
|
||||
for (final Flag flag : Flag.getFlags()) {
|
||||
if (!has(flag).isWritable()) continue;
|
||||
try {
|
||||
String s = flagData.getPlainValue(flag.name());
|
||||
String s = flagData.get(flag.name());
|
||||
flagMap.put(flag, flag.valueOfValue(s));
|
||||
} catch (Exception e) {
|
||||
flagMap.put(flag, (Flag.Value<?>) flag.getDefaultValue());
|
||||
}
|
||||
}
|
||||
properties.forEach(Property::load);
|
||||
if (data.getTestblockSchematic() != null) {
|
||||
testblockSchematic = SchematicNode.byId(data.getTestblockSchematic());
|
||||
}
|
||||
}
|
||||
|
||||
protected void initialize() {
|
||||
@@ -126,7 +87,8 @@ public abstract class RegionData {
|
||||
flagData.remove(flag.name());
|
||||
}
|
||||
}
|
||||
properties.forEach(property -> property.set(null));
|
||||
testblockSchematic = null;
|
||||
data.setTestblockSchematic(null);
|
||||
onChange.run();
|
||||
}
|
||||
|
||||
@@ -135,11 +97,12 @@ public abstract class RegionData {
|
||||
}
|
||||
|
||||
public SchematicNode getTestblockSchematic() {
|
||||
return testblockSchematic.get();
|
||||
return testblockSchematic;
|
||||
}
|
||||
|
||||
public void setTestblockSchematic(SchematicNode schematic) {
|
||||
testblockSchematic.set(schematic);
|
||||
testblockSchematic = schematic;
|
||||
data.setTestblockSchematic(schematic == null ? null : schematic.getId());
|
||||
onChange.run();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2025 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.bausystem.worlddata;
|
||||
|
||||
import de.steamwar.sql.SteamwarUser;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import yapion.hierarchy.output.FileOutput;
|
||||
import yapion.hierarchy.types.YAPIONArray;
|
||||
import yapion.hierarchy.types.YAPIONObject;
|
||||
import yapion.parser.InputStreamCharsets;
|
||||
import yapion.parser.YAPIONParser;
|
||||
import yapion.parser.options.FileOptions;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
@UtilityClass
|
||||
public class SimulatorData {
|
||||
|
||||
private final File simulatorsDir = new File(Bukkit.getWorlds().get(0).getWorldFolder(), "simulators");
|
||||
|
||||
static {
|
||||
simulatorsDir.mkdirs();
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public YAPIONObject getSimulator(Player player) {
|
||||
File file = getFile(player);
|
||||
if (!file.exists()) {
|
||||
return new YAPIONObject();
|
||||
}
|
||||
return YAPIONParser.parse(file, new FileOptions().charset(InputStreamCharsets.UTF_8));
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private void internalSaveSimulator(Player player, YAPIONObject yapionObject) {
|
||||
File file = getFile(player);
|
||||
yapionObject.toYAPION(new FileOutput(file)).close();
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
void saveSimulator(SteamwarUser steamwarUser, YAPIONObject yapionObject) {
|
||||
File file = new File(simulatorsDir, steamwarUser.getId() + ".yapion");
|
||||
yapionObject.toYAPION(new FileOutput(file)).close();
|
||||
}
|
||||
|
||||
public List<String> listSimulator(Player player) {
|
||||
List<String> strings = getSimulator(player).getKeys();
|
||||
strings.remove("");
|
||||
return strings;
|
||||
}
|
||||
|
||||
public void saveSimulator(Player player, String name) {
|
||||
YAPIONObject yapionObject = getSimulator(player);
|
||||
yapionObject.put(name, yapionObject.getArray("").copy());
|
||||
internalSaveSimulator(player, yapionObject);
|
||||
}
|
||||
|
||||
public void saveSimulator(Player player, YAPIONArray simulator) {
|
||||
YAPIONObject yapionObject = getSimulator(player);
|
||||
yapionObject.put("", simulator);
|
||||
internalSaveSimulator(player, yapionObject);
|
||||
}
|
||||
|
||||
public void loadSimulator(Player player, String name) {
|
||||
YAPIONObject yapionObject = getSimulator(player);
|
||||
yapionObject.put("", yapionObject.getArray(name).copy());
|
||||
internalSaveSimulator(player, yapionObject);
|
||||
}
|
||||
|
||||
public void removeSimulator(Player player, String name) {
|
||||
YAPIONObject yapionObject = getSimulator(player);
|
||||
yapionObject.remove(name);
|
||||
internalSaveSimulator(player, yapionObject);
|
||||
}
|
||||
|
||||
private File getFile(Player player) {
|
||||
SteamwarUser steamwarUser = SteamwarUser.get(player.getUniqueId());
|
||||
return new File(simulatorsDir, steamwarUser.getId() + ".yapion");
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2025 SteamWar.de-Serverteam
|
||||
* Copyright (C) 2026 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
@@ -19,58 +19,83 @@
|
||||
|
||||
package de.steamwar.bausystem.worlddata;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import de.steamwar.bausystem.config.ConfigStore;
|
||||
import de.steamwar.bausystem.config.VersionedConfig;
|
||||
import de.steamwar.bausystem.worlddata.converter.WorldDataV1ToV2Converter;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.bukkit.Bukkit;
|
||||
import yapion.hierarchy.output.FileOutput;
|
||||
import yapion.hierarchy.types.YAPIONObject;
|
||||
import yapion.parser.InputStreamCharsets;
|
||||
import yapion.parser.YAPIONParser;
|
||||
import yapion.parser.options.StreamOptions;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@UtilityClass
|
||||
public class WorldData {
|
||||
/**
|
||||
* The current world data. It is stored as JSON, fields missing in the
|
||||
* stored JSON keep the default value of their initializer, so adding a new
|
||||
* field does not need a new version.
|
||||
* <p>
|
||||
* When a field gets removed, renamed or its meaning changes, copy this class
|
||||
* to WorldDataV{@link #VERSION} (without the {@link #STORE}), increase {@link #VERSION}, write a
|
||||
* {@link de.steamwar.bausystem.config.ConfigConverter} from the copy to this class and register it in the {@link #STORE}.
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class WorldData implements VersionedConfig {
|
||||
|
||||
private final File optionsFile = new File(Bukkit.getWorlds().get(0).getWorldFolder(), "options.yapion");
|
||||
private YAPIONObject worldData;
|
||||
public static final int VERSION = 2;
|
||||
|
||||
public YAPIONObject getWorldData() {
|
||||
if (worldData == null) {
|
||||
read();
|
||||
}
|
||||
return worldData;
|
||||
public static final ConfigStore<WorldData> STORE = new ConfigStore<>(WorldData.class, VERSION, WorldData::new)
|
||||
.gson(new GsonBuilder().setPrettyPrinting().create())
|
||||
.converter(new WorldDataV1ToV2Converter())
|
||||
.file(new File(Bukkit.getWorlds().get(0).getWorldFolder(), "options.json"))
|
||||
.legacyFile(new File(Bukkit.getWorlds().get(0).getWorldFolder(), "options.yapion"), WorldDataV1::parse);
|
||||
|
||||
@Setter(AccessLevel.NONE)
|
||||
protected int version = VERSION;
|
||||
|
||||
/**
|
||||
* Region name to the data of the region
|
||||
*/
|
||||
protected Map<String, RegionEntry> regions = new HashMap<>();
|
||||
protected RegionEntry globalRegion = new RegionEntry();
|
||||
|
||||
/**
|
||||
* Warp name to warp
|
||||
*/
|
||||
protected Map<String, WarpEntry> warps = new HashMap<>();
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public static class RegionEntry {
|
||||
|
||||
/**
|
||||
* Overrides the prototype of the region config, null if not overridden
|
||||
*/
|
||||
protected String prototype = null;
|
||||
|
||||
/**
|
||||
* Flag name to flag value name
|
||||
*/
|
||||
protected Map<String, String> flags = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Id of the testblock SchematicNode, null if the default testblock is used
|
||||
*/
|
||||
protected Integer testblockSchematic = null;
|
||||
}
|
||||
|
||||
public YAPIONObject getRegionsData() {
|
||||
return getWorldData().getYAPIONObjectOrSetDefault("regions", new YAPIONObject());
|
||||
}
|
||||
@Getter
|
||||
@Setter
|
||||
public static class WarpEntry {
|
||||
|
||||
public YAPIONObject getWarpData() {
|
||||
return getWorldData().getYAPIONObjectOrSetDefault("warps", new YAPIONObject());
|
||||
}
|
||||
|
||||
private void read() {
|
||||
worldData = new YAPIONObject();
|
||||
if (optionsFile.length() != 0) {
|
||||
try (BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(optionsFile))) {
|
||||
worldData = YAPIONParser.parse(bufferedInputStream, new StreamOptions().charset(InputStreamCharsets.UTF_8));
|
||||
} catch (IOException e) {
|
||||
// Ignored
|
||||
}
|
||||
} else {
|
||||
write();
|
||||
}
|
||||
}
|
||||
|
||||
public void write() {
|
||||
try {
|
||||
worldData.toYAPION(new FileOutput(optionsFile)).close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
protected double x;
|
||||
protected double y;
|
||||
protected double z;
|
||||
protected float yaw;
|
||||
protected float pitch;
|
||||
protected String material;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2026 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.bausystem.worlddata;
|
||||
|
||||
import de.steamwar.bausystem.config.VersionedConfig;
|
||||
import de.steamwar.bausystem.config.legacy.YapionReader;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* The legacy world data that was stored in the YAPION format in options.yapion.
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@Deprecated
|
||||
public class WorldDataV1 implements VersionedConfig {
|
||||
|
||||
public static final int VERSION = 1;
|
||||
|
||||
@Setter(AccessLevel.NONE)
|
||||
protected int version = VERSION;
|
||||
|
||||
/**
|
||||
* Region name to the data of the region, the global region is stored with the name "global"
|
||||
*/
|
||||
protected Map<String, RegionEntry> regions = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Warp name to warp
|
||||
*/
|
||||
protected Map<String, WarpEntry> warps = new HashMap<>();
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public static class RegionEntry {
|
||||
|
||||
protected String prototype = null;
|
||||
|
||||
/**
|
||||
* Flag name to flag value name and "testblockSchematic" to the SchematicNode id
|
||||
*/
|
||||
protected Map<String, Object> flagStorage = new HashMap<>();
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public static class WarpEntry {
|
||||
|
||||
protected double x;
|
||||
protected double y;
|
||||
protected double z;
|
||||
protected float yaw;
|
||||
protected float pitch;
|
||||
protected String material;
|
||||
}
|
||||
|
||||
public static WorldDataV1 parse(String yapion) {
|
||||
Map<String, Object> object = YapionReader.parse(yapion);
|
||||
WorldDataV1 worldData = new WorldDataV1();
|
||||
|
||||
if (object.get("regions") instanceof Map<?, ?> regions) {
|
||||
regions.forEach((name, value) -> {
|
||||
if (!(value instanceof Map<?, ?> region)) return;
|
||||
RegionEntry entry = new RegionEntry();
|
||||
if (region.get("prototype") instanceof String prototype) {
|
||||
entry.prototype = prototype;
|
||||
}
|
||||
if (region.get("flagStorage") instanceof Map<?, ?> flagStorage) {
|
||||
flagStorage.forEach((key, flag) -> entry.flagStorage.put((String) key, flag));
|
||||
}
|
||||
worldData.regions.put((String) name, entry);
|
||||
});
|
||||
}
|
||||
|
||||
if (object.get("warps") instanceof Map<?, ?> warps) {
|
||||
warps.forEach((name, value) -> {
|
||||
if (!(value instanceof Map<?, ?> warp)) return;
|
||||
WarpEntry entry = new WarpEntry();
|
||||
entry.x = ((Number) warp.get("x")).doubleValue();
|
||||
entry.y = ((Number) warp.get("y")).doubleValue();
|
||||
entry.z = ((Number) warp.get("z")).doubleValue();
|
||||
entry.yaw = ((Number) warp.get("yaw")).floatValue();
|
||||
entry.pitch = ((Number) warp.get("pitch")).floatValue();
|
||||
entry.material = (String) warp.get("material");
|
||||
worldData.warps.put((String) name, entry);
|
||||
});
|
||||
}
|
||||
return worldData;
|
||||
}
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2026 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.bausystem.worlddata.converter;
|
||||
|
||||
import de.steamwar.bausystem.config.ConfigConverter;
|
||||
import de.steamwar.bausystem.worlddata.WorldData;
|
||||
import de.steamwar.bausystem.worlddata.WorldDataV1;
|
||||
|
||||
@Deprecated
|
||||
public class WorldDataV1ToV2Converter implements ConfigConverter<WorldDataV1, WorldData> {
|
||||
|
||||
private static final String GLOBAL_REGION = "global";
|
||||
private static final String TESTBLOCK_SCHEMATIC = "testblockSchematic";
|
||||
|
||||
@Override
|
||||
public int version() {
|
||||
return WorldDataV1.VERSION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WorldData convert(WorldDataV1 old) {
|
||||
WorldData worldData = new WorldData();
|
||||
old.getRegions().forEach((name, region) -> {
|
||||
if (name.equals(GLOBAL_REGION)) {
|
||||
worldData.setGlobalRegion(convert(region));
|
||||
} else {
|
||||
worldData.getRegions().put(name, convert(region));
|
||||
}
|
||||
});
|
||||
old.getWarps().forEach((name, warp) -> worldData.getWarps().put(name, convert(warp)));
|
||||
return worldData;
|
||||
}
|
||||
|
||||
private WorldData.RegionEntry convert(WorldDataV1.RegionEntry old) {
|
||||
WorldData.RegionEntry region = new WorldData.RegionEntry();
|
||||
region.setPrototype(old.getPrototype());
|
||||
old.getFlagStorage().forEach((key, value) -> {
|
||||
if (value == null) return;
|
||||
if (key.equals(TESTBLOCK_SCHEMATIC)) {
|
||||
region.setTestblockSchematic(((Number) value).intValue());
|
||||
} else {
|
||||
region.getFlags().put(key, value.toString());
|
||||
}
|
||||
});
|
||||
return region;
|
||||
}
|
||||
|
||||
private WorldData.WarpEntry convert(WorldDataV1.WarpEntry old) {
|
||||
WorldData.WarpEntry warp = new WorldData.WarpEntry();
|
||||
warp.setX(old.getX());
|
||||
warp.setY(old.getY());
|
||||
warp.setZ(old.getZ());
|
||||
warp.setYaw(old.getYaw());
|
||||
warp.setPitch(old.getPitch());
|
||||
warp.setMaterial(old.getMaterial());
|
||||
return warp;
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -25,12 +25,12 @@ import de.steamwar.bausystem.region.flags.ColorMode;
|
||||
import de.steamwar.bausystem.region.flags.Flag;
|
||||
import de.steamwar.bausystem.region.flags.ProtectMode;
|
||||
import de.steamwar.bausystem.region.flags.TNTMode;
|
||||
import de.steamwar.bausystem.worlddata.WorldData;
|
||||
import lombok.NonNull;
|
||||
import yapion.hierarchy.types.YAPIONObject;
|
||||
|
||||
public class FixedGlobalRegionData extends RegionData {
|
||||
|
||||
public FixedGlobalRegionData(YAPIONObject data, Runnable onChange) {
|
||||
public FixedGlobalRegionData(WorldData.RegionEntry data, Runnable onChange) {
|
||||
super(data, onChange);
|
||||
}
|
||||
|
||||
|
||||
+3
-2
@@ -26,6 +26,7 @@ import de.steamwar.bausystem.region.flags.Flag;
|
||||
import de.steamwar.bausystem.region.flags.TestblockMode;
|
||||
import de.steamwar.bausystem.utils.FlatteningWrapper;
|
||||
import de.steamwar.bausystem.utils.PasteBuilder;
|
||||
import de.steamwar.bausystem.worlddata.WorldData;
|
||||
import de.steamwar.core.Core;
|
||||
import de.steamwar.sql.GameModeConfig;
|
||||
import de.steamwar.sql.SchematicType;
|
||||
@@ -139,7 +140,7 @@ public class FixedRegion implements Region {
|
||||
}
|
||||
}
|
||||
|
||||
public FixedRegion(String name, FixedRegionData flagStorage, Prototype prototype, YAPIONObject regionConfig, YAPIONObject regionData) {
|
||||
public FixedRegion(String name, FixedRegionData flagStorage, Prototype prototype, YAPIONObject regionConfig, WorldData.RegionEntry regionData) {
|
||||
this.name = name;
|
||||
uuid = UUID.nameUUIDFromBytes(name.getBytes(StandardCharsets.UTF_8));
|
||||
this.flagStorage = flagStorage;
|
||||
@@ -325,7 +326,7 @@ public class FixedRegion implements Region {
|
||||
}
|
||||
|
||||
floorLevel = prototype.getFloorOffset() != 0 ? minPoint.getY() + prototype.getFloorOffset() : 0;
|
||||
waterLevel = prototype.getWaterOffset() != 0 ? minPoint.getX() + prototype.getWaterOffset() : 0;
|
||||
waterLevel = prototype.getWaterOffset() != 0 ? minPoint.getY() + prototype.getWaterOffset() : 0;
|
||||
|
||||
File found = null;
|
||||
File baseFile = new File(BauSystem.getInstance().getDataFolder().getParentFile(), "FightSystem");
|
||||
|
||||
+2
-2
@@ -22,12 +22,12 @@ package de.steamwar.bausystem.region.fixed;
|
||||
import de.steamwar.bausystem.region.RegionData;
|
||||
import de.steamwar.bausystem.region.RegionFlagPolicy;
|
||||
import de.steamwar.bausystem.region.flags.Flag;
|
||||
import de.steamwar.bausystem.worlddata.WorldData;
|
||||
import lombok.NonNull;
|
||||
import yapion.hierarchy.types.YAPIONObject;
|
||||
|
||||
public class FixedRegionData extends RegionData {
|
||||
|
||||
public FixedRegionData(YAPIONObject data, Runnable onChange) {
|
||||
public FixedRegionData(WorldData.RegionEntry data, Runnable onChange) {
|
||||
super(data, onChange);
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -210,14 +210,14 @@ public class Prototype {
|
||||
}
|
||||
}
|
||||
|
||||
public static void generateRegion(String name, YAPIONObject regionConfig, YAPIONObject regionData) {
|
||||
public static void generateRegion(String name, YAPIONObject regionConfig, WorldData.RegionEntry regionData) {
|
||||
Prototype prototype;
|
||||
if (regionData.containsKey("prototype", String.class)) {
|
||||
prototype = PROTOTYPE_MAP.get(regionData.getPlainValue("prototype"));
|
||||
if (regionData.getPrototype() != null) {
|
||||
prototype = PROTOTYPE_MAP.get(regionData.getPrototype());
|
||||
} else {
|
||||
prototype = PROTOTYPE_MAP.get(regionConfig.getPlainValue("prototype"));
|
||||
}
|
||||
FixedRegionData flagStorage = new FixedRegionData(regionData, WorldData::write);
|
||||
FixedRegionData flagStorage = new FixedRegionData(regionData, WorldData.STORE::save);
|
||||
FixedRegionSystem.addRegion(new FixedRegion(name, flagStorage, prototype, regionConfig, regionData));
|
||||
}
|
||||
}
|
||||
|
||||
+3
-15
@@ -28,7 +28,6 @@ import org.bukkit.Bukkit;
|
||||
import yapion.hierarchy.diff.DiffChange;
|
||||
import yapion.hierarchy.diff.YAPIONDiff;
|
||||
import yapion.hierarchy.types.YAPIONObject;
|
||||
import yapion.hierarchy.types.YAPIONType;
|
||||
import yapion.parser.YAPIONParser;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
@@ -55,7 +54,7 @@ public class RegionLoader {
|
||||
}
|
||||
loaded = yapionObject;
|
||||
|
||||
YAPIONObject optionsYapionObject = WorldData.getRegionsData();
|
||||
WorldData worldData = WorldData.STORE.get();
|
||||
yapionObject.forEach((key, yapionAnyType) -> {
|
||||
if (key.equals("global")) {
|
||||
return;
|
||||
@@ -65,21 +64,10 @@ public class RegionLoader {
|
||||
}
|
||||
|
||||
YAPIONObject regionConfig = (YAPIONObject) yapionAnyType;
|
||||
YAPIONObject regionData = new YAPIONObject();
|
||||
if (optionsYapionObject.containsKey(key, YAPIONType.OBJECT)) {
|
||||
regionData = optionsYapionObject.getObject(key);
|
||||
} else {
|
||||
optionsYapionObject.add(key, regionData);
|
||||
}
|
||||
|
||||
WorldData.RegionEntry regionData = worldData.getRegions().computeIfAbsent(key, k -> new WorldData.RegionEntry());
|
||||
Prototype.generateRegion(key, regionConfig, regionData);
|
||||
});
|
||||
|
||||
YAPIONObject globalOptions = optionsYapionObject.getObject("global");
|
||||
if (globalOptions == null) {
|
||||
globalOptions = new YAPIONObject();
|
||||
optionsYapionObject.add("global", globalOptions);
|
||||
}
|
||||
FixedGlobalRegion.setFLAG_STORAGE(new FixedGlobalRegionData(globalOptions, WorldData::write));
|
||||
FixedGlobalRegion.setFLAG_STORAGE(new FixedGlobalRegionData(worldData.getGlobalRegion(), WorldData.STORE::save));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2026 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
package de.steamwar.network.packets.server;
|
||||
|
||||
import de.steamwar.network.packets.NetworkPacket;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* A chat message written on an arena. The arena only records it for the replay and never displays it.
|
||||
*/
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Getter
|
||||
public class ChatRecordPacket extends NetworkPacket {
|
||||
private static final long serialVersionUID = -4718360932583127458L;
|
||||
private int playerId;
|
||||
private String message;
|
||||
}
|
||||
@@ -30,6 +30,7 @@ import org.jetbrains.exposed.v1.dao.CompositeEntity
|
||||
import org.jetbrains.exposed.v1.dao.CompositeEntityClass
|
||||
import org.jetbrains.exposed.v1.jdbc.insertIgnore
|
||||
import java.util.*
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
object BauweltMemberTable : CompositeIdTable("BauweltMember") {
|
||||
val bauweltId = reference("BauweltID", SteamwarUserTable).index()
|
||||
@@ -48,10 +49,12 @@ object BauweltMemberTable : CompositeIdTable("BauweltMember") {
|
||||
|
||||
class BauweltMember(id: EntityID<CompositeID>) : CompositeEntity(id) {
|
||||
companion object : CompositeEntityClass<BauweltMember>(BauweltMemberTable) {
|
||||
private val cache = mutableMapOf<Int, BauweltMember>()
|
||||
// Keyed by (bauweltId, memberId). Optional.empty() caches "is not a member", which is the
|
||||
// common case for spectators and would otherwise hit the DB on every permission check.
|
||||
private val cache = ConcurrentHashMap<Pair<Int, Int>, Optional<BauweltMember>>()
|
||||
|
||||
private fun cache(member: BauweltMember) =
|
||||
cache.put(member.memberID, member)
|
||||
cache.put(member.bauweltID to member.memberID, Optional.of(member))
|
||||
|
||||
@JvmStatic
|
||||
fun clear() =
|
||||
@@ -75,20 +78,20 @@ class BauweltMember(id: EntityID<CompositeID>) : CompositeEntity(id) {
|
||||
it[worldEdit] = false
|
||||
it[world] = false
|
||||
}
|
||||
}
|
||||
}.also { cache.remove(ownerId.value to newMemberId.value) }
|
||||
|
||||
@JvmStatic
|
||||
@Deprecated("Use getBauMember(bauwelt: Int, member: Int)")
|
||||
fun getBauMember(bauwelt: UUID, member: UUID) =
|
||||
useDb {
|
||||
find { (bauweltId eq SteamwarUser.get(bauwelt)!!.id) and (BauweltMemberTable.memberId eq SteamwarUser.get(member)!!.id) }.firstOrNull()?.also { cache(it) }
|
||||
}
|
||||
getBauMember(SteamwarUser.get(bauwelt)!!.id.value, SteamwarUser.get(member)!!.id.value)
|
||||
|
||||
@JvmStatic
|
||||
fun getBauMember(bauwelt: Int, member: Int) =
|
||||
useDb {
|
||||
find { (bauweltId eq bauwelt) and (BauweltMemberTable.memberId eq member) }.firstOrNull()?.also { cache(it) }
|
||||
}
|
||||
fun getBauMember(bauwelt: Int, member: Int): BauweltMember? =
|
||||
cache.computeIfAbsent(bauwelt to member) {
|
||||
Optional.ofNullable(useDb {
|
||||
find { (bauweltId eq bauwelt) and (BauweltMemberTable.memberId eq member) }.firstOrNull()
|
||||
})
|
||||
}.orElse(null)
|
||||
|
||||
@JvmStatic
|
||||
@Deprecated("Use getMembers(bauwelt: Int)")
|
||||
@@ -140,8 +143,11 @@ class BauweltMember(id: EntityID<CompositeID>) : CompositeEntity(id) {
|
||||
fun isWorld() =
|
||||
world
|
||||
|
||||
fun remove() =
|
||||
fun remove() {
|
||||
val key = bauweltID to memberID
|
||||
useDb {
|
||||
delete()
|
||||
}
|
||||
cache.remove(key)
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
package de.steamwar.sql;
|
||||
|
||||
import de.steamwar.sql.internal.SQLConfig;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.io.File;
|
||||
@@ -250,6 +251,11 @@ public final class GameModeConfig<M, W> {
|
||||
Times = new TimesConfig(loader.with("Times"));
|
||||
// Arena would be here to be in config order but needs Schematic.Size and EnterStages loaded afterwards
|
||||
Schematic = new SchematicConfig<>(loader.with("Schematic"));
|
||||
for (int i = 0; i < Schematic.Limited.size(); i++) {
|
||||
if (Schematic.Limited.get(i).Materials.isEmpty()) {
|
||||
SQLConfig.impl.getLogger().warning(file.getName() + ": Schematic.Limited[" + i + "] matches no blocks, check its 'Materials' entry");
|
||||
}
|
||||
}
|
||||
GameName = loader.getString("GameName", defaultGameName.apply(file));
|
||||
ActiveMonths = loader.getIntList("ActiveMonths");
|
||||
TeamChatPrefix = loader.getString("TeamChatPrefix", "+");
|
||||
@@ -611,6 +617,15 @@ public final class GameModeConfig<M, W> {
|
||||
*/
|
||||
public final List<SchematicType> SubTypes;
|
||||
|
||||
/**
|
||||
* List of publics that should be used. Mainly useful for an Event Mode
|
||||
* where a subset of the regular publics should be used without duplicating
|
||||
* those in the Database.
|
||||
*
|
||||
* @implSpec {@code []} by default, thus the GameModes publics are used.
|
||||
*/
|
||||
public final List<Integer> Publics;
|
||||
|
||||
/**
|
||||
* Shortcut of the schematic type
|
||||
*
|
||||
@@ -703,7 +718,10 @@ public final class GameModeConfig<M, W> {
|
||||
public final double MaxDesignBlastResistance;
|
||||
|
||||
/**
|
||||
* List of limited block materials
|
||||
* List of limited block materials<br/>
|
||||
* Every inventory block is counted towards exactly one rule: a filled block towards a rule whose
|
||||
* {@link BlockRule#Content} allows all of its items, an empty block towards a rule without
|
||||
* {@link BlockRule#Content}. Empty inventory blocks without such a rule are unlimited.
|
||||
*
|
||||
* @implSpec {@link SQLWrapper#getInventoryMaterials)} are all disallowed by default to have any content but are unlimited in number.
|
||||
* @implSpec Any Block that has more Blast Resistance than {@link #MaxBlastResistance} will be defaulted to 0.
|
||||
@@ -716,6 +734,7 @@ public final class GameModeConfig<M, W> {
|
||||
Inset = new InsetConfig(loader.with("Inset"));
|
||||
Type = null;
|
||||
SubTypesStrings = loader.getStringList("SubTypes");
|
||||
Publics = loader.getIntList("Publics");
|
||||
SubTypes = new ArrayList<>();
|
||||
Shortcut = loader.getString("Shortcut", "");
|
||||
Material = loader.getMaterial("Material", "STONE_BUTTON");
|
||||
@@ -759,18 +778,13 @@ public final class GameModeConfig<M, W> {
|
||||
}
|
||||
|
||||
public boolean isInventory(M material) {
|
||||
return Limited.stream()
|
||||
.filter(rule -> rule.Materials.contains(material))
|
||||
.anyMatch(rule -> rule.Content.loaded);
|
||||
return SQLWrapper.impl.getInventoryMaterials().contains(material) || Limited.stream().anyMatch(rule -> rule.Content.loaded && rule.Materials.contains(material));
|
||||
}
|
||||
|
||||
public Integer getItemAmount(M material, M item) {
|
||||
public List<BlockRule<M>> getRules(M material) {
|
||||
return Limited.stream()
|
||||
.filter(rule -> rule.Materials.contains(material))
|
||||
.filter(rule -> rule.Content.Items.contains(item))
|
||||
.mapToInt(rule -> rule.Content.Amount)
|
||||
.max()
|
||||
.orElse(0);
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@ToString
|
||||
|
||||
@@ -46,7 +46,7 @@ public interface SQLWrapper<M> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Every inventory material - the {@code all} group usable in {@code GameModeConfig}'s
|
||||
* Every inventory material - the {@code inventory} group usable in {@code GameModeConfig}'s
|
||||
* {@code Limited}. Expected to be a superset of {@link #getStorageMaterials()}.
|
||||
*/
|
||||
default Set<M> getInventoryMaterials() {
|
||||
|
||||
@@ -307,12 +307,12 @@ class SteamwarUser(id: EntityID<Int>) : IntEntity(id) {
|
||||
punishments[punishment] = Punishment.createPunishment(this@SteamwarUser.id.value, from, punishment, reason, time, perma)
|
||||
}
|
||||
|
||||
fun setLocale(locale: Locale?, manualeLocale: Boolean) {
|
||||
fun setLocale(locale: Locale?, manualLocale: Boolean) {
|
||||
if (locale == null || (this.manualLocale && !manualLocale)) return
|
||||
|
||||
useDb {
|
||||
this@SteamwarUser.locale = locale
|
||||
this@SteamwarUser.manualLocale = manualeLocale
|
||||
this@SteamwarUser.manualLocale = manualLocale
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -109,7 +109,6 @@ TEAM_READY = §aTeam ready
|
||||
TEAM_NOT_READY = §c§mTeam ready
|
||||
SKIP_READY = §aSkipping to next event
|
||||
SKIP_NOT_READY = §c§mSkipping to next event
|
||||
TEAM_CHAT = {0}{1}§8» {0}{2}
|
||||
CHOOSE_KIT = §eChoose kit
|
||||
RESPAWN = §eRespawn
|
||||
MANAGE_PLAYERS = §cManage players
|
||||
|
||||
@@ -20,19 +20,14 @@
|
||||
package de.steamwar.fightsystem.ai;
|
||||
|
||||
import com.sk89q.worldedit.extent.clipboard.Clipboard;
|
||||
import de.steamwar.fightsystem.Config;
|
||||
import de.steamwar.fightsystem.fight.FightSchematic;
|
||||
import de.steamwar.fightsystem.fight.FightTeam;
|
||||
import de.steamwar.fightsystem.states.FightState;
|
||||
import de.steamwar.sql.SchematicNode;
|
||||
import de.steamwar.sql.SteamwarUser;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
public class DummyAI extends AI {
|
||||
|
||||
private static final Random random = new Random();
|
||||
|
||||
public DummyAI(FightTeam team) {
|
||||
super(team, SteamwarUser.get("public"));
|
||||
|
||||
@@ -41,8 +36,7 @@ public class DummyAI extends AI {
|
||||
|
||||
@Override
|
||||
public SchematicNode chooseSchematic() {
|
||||
List<SchematicNode> publics = SchematicNode.getAllSchematicsOfType(0, Config.GameModeConfig.Schematic.Type.toDB());
|
||||
return publics.get(random.nextInt(publics.size()));
|
||||
return FightSchematic.getPublicSchematic().orElseThrow();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -255,7 +255,15 @@ public class GUI {
|
||||
private static void schemDialog(Player p, SchematicType type, boolean publicSchems, boolean unchecked) {
|
||||
SchematicSelector selector = new SchematicSelector(p, Config.test() ? SchematicSelector.selectSchematic() : SchematicSelector.selectSchematicType(unchecked ? type.checkType() : type), node -> {
|
||||
schemSelect(p, node);
|
||||
});
|
||||
}) {
|
||||
@Override
|
||||
protected List<SchematicNode> getSchematicList(SchematicNode parent) {
|
||||
if (!publicSchems || parent != null) return super.getSchematicList(parent);
|
||||
List<Integer> publics = Config.GameModeConfig.Schematic.Publics;
|
||||
if (publics.isEmpty()) return super.getSchematicList(null);
|
||||
return publics.stream().map(SchematicNode::byId).collect(Collectors.toList());
|
||||
}
|
||||
};
|
||||
selector.setPublicMode(publicSchems ? SchematicSelector.PublicMode.PUBLIC_ONLY : SchematicSelector.PublicMode.PRIVATE_ONLY);
|
||||
selector.open();
|
||||
}
|
||||
|
||||
+32
-15
@@ -46,6 +46,7 @@ import org.bukkit.util.Vector;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Random;
|
||||
import java.util.logging.Level;
|
||||
|
||||
@@ -109,31 +110,47 @@ public class FightSchematic extends StateDependent {
|
||||
clipboard = null;
|
||||
}
|
||||
|
||||
private static final Random random = new Random();
|
||||
|
||||
public static Optional<SchematicNode> getPublicSchematic() {
|
||||
List<Integer> publicsOverridden = Config.GameModeConfig.Schematic.Publics;
|
||||
if (!publicsOverridden.isEmpty()) {
|
||||
int publicId = publicsOverridden.get(random.nextInt(publicsOverridden.size()));
|
||||
SchematicNode schematicNode = SchematicNode.getSchematicNode(publicId);
|
||||
if (schematicNode != null) {
|
||||
return Optional.of(schematicNode);
|
||||
}
|
||||
}
|
||||
|
||||
List<SchematicNode> publics = SchematicNode.getAllSchematicsOfType(0, Config.GameModeConfig.Schematic.Type.toDB());
|
||||
if (publics.isEmpty()) {
|
||||
for (SchematicType type : Config.GameModeConfig.Schematic.SubTypes) {
|
||||
publics = SchematicNode.getAllSchematicsOfType(0, type.toDB());
|
||||
if (!publics.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (publics.isEmpty()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
return Optional.of(publics.get(random.nextInt(publics.size())));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enable() {
|
||||
if (FightState.getFightState() == FightState.SPECTATE) return;
|
||||
|
||||
if (clipboard == null) {
|
||||
List<SchematicNode> publics = SchematicNode.getAllSchematicsOfType(0, Config.GameModeConfig.Schematic.Type.toDB());
|
||||
if (publics.isEmpty()) {
|
||||
for (SchematicType type : Config.GameModeConfig.Schematic.SubTypes) {
|
||||
publics = SchematicNode.getAllSchematicsOfType(0, type.toDB());
|
||||
if (!publics.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (publics.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setSchematic(publics.get(new Random().nextInt(publics.size())));
|
||||
Optional<SchematicNode> schematicNode = getPublicSchematic();
|
||||
if (schematicNode.isEmpty()) return;
|
||||
setSchematic(schematicNode.get());
|
||||
}
|
||||
|
||||
if (ArenaMode.AntiReplay.contains(Config.mode)) {
|
||||
boolean changeRotation = false;
|
||||
if (Config.GameModeConfig.WinConditions.contains(Winconditions.RANDOM_ROTATE)) {
|
||||
changeRotation = new Random().nextBoolean();
|
||||
changeRotation = random.nextBoolean();
|
||||
usedRotate = rotate ^ changeRotation;
|
||||
}
|
||||
if (team.isBlue()) {
|
||||
|
||||
@@ -256,10 +256,6 @@ public class FightTeam {
|
||||
broadcast(player -> FightSystem.getMessage().send(message, player, params));
|
||||
}
|
||||
|
||||
public void broadcastChat(Player sender, String message) {
|
||||
broadcast(player -> FightSystem.getMessage().sendPrefixless("TEAM_CHAT", player, ChatMessageType.CHAT, prefix, sender.getName(), message));
|
||||
}
|
||||
|
||||
private void broadcast(Consumer<Player> f) {
|
||||
players.forEach((uuid, fightPlayer) -> {
|
||||
Player player = Bukkit.getPlayer(uuid);
|
||||
|
||||
@@ -19,50 +19,41 @@
|
||||
|
||||
package de.steamwar.fightsystem.listener;
|
||||
|
||||
import de.steamwar.fightsystem.ArenaMode;
|
||||
import de.steamwar.fightsystem.Config;
|
||||
import de.steamwar.fightsystem.FightSystem;
|
||||
import de.steamwar.fightsystem.fight.Fight;
|
||||
import de.steamwar.fightsystem.fight.FightTeam;
|
||||
import de.steamwar.fightsystem.record.GlobalRecorder;
|
||||
import de.steamwar.fightsystem.states.FightState;
|
||||
import de.steamwar.fightsystem.states.StateDependentListener;
|
||||
import de.steamwar.linkage.Linked;
|
||||
import de.steamwar.network.packets.PacketHandler;
|
||||
import de.steamwar.network.packets.server.ChatRecordPacket;
|
||||
import de.steamwar.sql.SteamwarUser;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.AsyncPlayerChatEvent;
|
||||
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
* Chat is handled by Velocity. Messages written on this server are only sent here to be recorded for the replay.
|
||||
*/
|
||||
@Linked
|
||||
public class Chat implements Listener {
|
||||
public class Chat extends PacketHandler {
|
||||
|
||||
public Chat() {
|
||||
new StateDependentListener(ArenaMode.All, FightState.All, this);
|
||||
}
|
||||
@Handler
|
||||
public void handleChatRecord(ChatRecordPacket packet) {
|
||||
SteamwarUser user = SteamwarUser.byId(packet.getPlayerId());
|
||||
String message = packet.getMessage();
|
||||
FightSystem.getPlugin().getLogger().log(Level.INFO, user.getUserName() + "» " + message);
|
||||
|
||||
@EventHandler
|
||||
public void handlePlayerChat(AsyncPlayerChatEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
String message = event.getMessage();
|
||||
|
||||
FightSystem.getPlugin().getLogger().log(Level.INFO, player.getName() + "» " + message);
|
||||
FightTeam fightTeam = Fight.getPlayerTeam(player);
|
||||
Player player = Bukkit.getPlayer(user.getUUID());
|
||||
FightTeam fightTeam = player != null ? Fight.getPlayerTeam(player) : null;
|
||||
if (fightTeam != null) {
|
||||
String teamName = fightTeam.getColoredName();
|
||||
if (message.startsWith(Config.GameModeConfig.TeamChatPrefix)) {
|
||||
fightTeam.broadcastChat(player, message.substring(1));
|
||||
} else {
|
||||
broadcastChat("PARTICIPANT_CHAT", teamName, player.getName(), message);
|
||||
}
|
||||
} else if (Config.isReferee(player)) {
|
||||
broadcastChat("FIGHTLEADER_CHAT", player.getName(), message);
|
||||
GlobalRecorder.getInstance().chat("PARTICIPANT_CHAT", fightTeam.getColoredName(), user.getUserName(), message);
|
||||
} else if (player != null && Config.isReferee(player)) {
|
||||
GlobalRecorder.getInstance().chat("FIGHTLEADER_CHAT", user.getUserName(), message);
|
||||
} else {
|
||||
broadcastChat("SPECTATOR_CHAT", player.getName(), message);
|
||||
GlobalRecorder.getInstance().chat("SPECTATOR_CHAT", user.getUserName(), message);
|
||||
}
|
||||
|
||||
event.setCancelled(true);
|
||||
}
|
||||
|
||||
public static void broadcastChat(String message, Object... params) {
|
||||
|
||||
@@ -49,76 +49,341 @@ PORTAL_NO_WORLDEDIT_SELECTION = §cNo WorldEdit selection
|
||||
PARTICLE_INVENTORY = §0Particle
|
||||
PARTICLE_DESELECT = §8No particle
|
||||
|
||||
PARTICLE_SHOW_HAS_ALL = §8You have all particles
|
||||
PARTICLE_SHOW_ALL = §aShow all
|
||||
PARTICLE_SHOW_UNLOCKED = §cShow unlocked
|
||||
|
||||
PARTICLE_UNLOCKED_BY = §eUnlocked by
|
||||
PARTICLE_UNLOCKED_BY_TEAM = §fJoin a team
|
||||
PARTICLE_UNLOCKED_BY_SPECIFIC_TEAM = §fTeam {0}
|
||||
PARTICLE_UNLOCKED_BY_SPECIFIC_TEAM_EASTER = §fTeam {0} (Easter Egg Hunt)
|
||||
PARTICLE_UNLOCKED_BY_SPECIFIC_USER = §fUser {0}
|
||||
PARTICLE_UNLOCKED_BY_SPECIFIC_USER_EASTER = §fUser {0} (Easter Egg Hunt)
|
||||
PARTICLE_UNLOCKED_BY_EVENT = §fEvent participation
|
||||
PARTICLE_UNLOCKED_BY_SERVER_TEAM = §fServer Team
|
||||
PARTICLE_UNLOCKED_BY_EVENT = §fEvent participation
|
||||
PARTICLE_UNLOCKED_BY_EVENT_PLACEMENT = §f{0} 1., 2. or 3. Place
|
||||
PARTICLE_UNLOCKED_BY_EVENT_PARTICIPATION = §f{0}
|
||||
PARTICLE_UNLOCKED_BY_EASTER_EGG_HUNT_HALF = §fFind 45 Easter Eggs
|
||||
PARTICLE_UNLOCKED_BY_EASTER_EGG_HUNT_DIFFICULTY = §fFind all Easter Eggs with difficulty {0}
|
||||
|
||||
PARTICLE_ATTRIBUTE = §eAttributes§7:
|
||||
PARTICLE_ATTRIBUTE_CIRCLE = §8-§f Ring
|
||||
PARTICLE_ATTRIBUTE_BI_CIRCLE = §8-§f Double ring
|
||||
PARTICLE_ATTRIBUTE_TRI_CIRCLE = §8-§f Tripple ring
|
||||
PARTICLE_ATTRIBUTE_CLOUD = §8-§f Cloud
|
||||
PARTICLE_ATTRIBUTE_TICK = §8-§f Always active
|
||||
PARTICLE_ATTRIBUTE_NON_FLOOR = §8-§f In air
|
||||
PARTICLE_ATTRIBUTE_FLOOR = §8-§f On ground
|
||||
PARTICLE_ATTRIBUTE_NON_FLYING = §8-§f Not flying
|
||||
PARTICLE_ATTRIBUTE_FLYING = §8-§f Flying
|
||||
PARTICLE_ATTRIBUTE_WING = §8-§f Wings
|
||||
PARTICLE_ATTRIBUTE_SNEAKING = §8-§f Sneaking
|
||||
PARTICLE_ATTRIBUTE_NON_MOVING = §8-§f Not moving
|
||||
PARTICLE_ATTRIBUTE_SEPARATOR = §f
|
||||
|
||||
PARTICLE_SELECT = §eClick to select
|
||||
PARTICLE_LOCKED = §c§lLocked
|
||||
PARTICLE_DEACTIVATED = §cThere are too many players online, particles are deactivated
|
||||
|
||||
PARTICLE_SNEEZE = §aSneeze
|
||||
PARTICLE_SMOKE = §7Smoke
|
||||
PARTICLE_FIRE = §cFire
|
||||
PARTICLE_WATER = §bWater
|
||||
PARTICLE_HEART = §cHearts
|
||||
PARTICLE_NOTES = §eNotes
|
||||
PARTICLE_NAUTILUS = §aNautilus
|
||||
PARTICLE_SNOWBALL = §fSnowball
|
||||
PARTICLE_EFFECT = §5Effect
|
||||
PARTICLE_CAMPFIRE = §7Smoke
|
||||
PARTICLE_MAGIC = §5Magic
|
||||
PARTICLE_ANGRY = §4Angry
|
||||
PARTICLE_SLIME = §aSlime
|
||||
PARTICLE_MOB = §7Mob
|
||||
PARTICLE_SQUID = §8Squid
|
||||
PARTICLE_BUBBLE = §aBubbles
|
||||
PARTICLE_HONEY = §6Honey
|
||||
PARTICLE_NECTAR = §6Nectar
|
||||
PARTICLE_FIREWORK = §7Firework
|
||||
PARTICLE_DRAGON_BREATH = §5Dragon breath
|
||||
PARTICLE_DAMAGE = §5Damage
|
||||
PARTICLE_DOLPHIN = §dDolphin
|
||||
PARTICLE_WITCH = §5Witch
|
||||
PARTICLE_ENCHANTING = §eEnchantment
|
||||
PARTICLE_HAPPY = §2Joy
|
||||
PARTICLE_FLAME = §7Flames
|
||||
PARTICLE_END_ROD = §fEnd Rod
|
||||
PARTICLE_CLOUD = §7Cloud
|
||||
PARTICLE_TOTEM = §aTotem
|
||||
PARTICLE_TOWN = §5Town
|
||||
PARTICLE_ENCHANTING_CIRCLE = §fEnchanted
|
||||
PARTICLE_WATER_FIRE = §bWater§7/§cFire
|
||||
PARTICLE_MAGIC_ENCHANTING = §5Magic/Enchantment
|
||||
PARTICLE_WINGS_EVIL = §5Purple wings
|
||||
PARTICLE_BACK = §7Back
|
||||
PARTICLE_SLOT_OPEN = §eClick to open
|
||||
PARTICLE_SLOT_CLICK_USE = §eClick to use
|
||||
PARTICLE_SLOT_CLICK_STOP = §eClick to stop using
|
||||
PARTICLE_SLOT_SHIFT_EDIT = §eShift click to edit
|
||||
PARTICLE_SLOT_NAME = §f{0} §8(§e{1}§8/§7{2}§8)
|
||||
PARTICLE_SLOT_TITLE = §0{0} §8({1}/{2})
|
||||
PARTICLE_SHIFT_TO_DELETE = §7Shift click to delete
|
||||
PARTICLE_SECTION_DESIGN = §6§lDesign
|
||||
PARTICLE_SECTION_PARTICLE = §6§lParticle
|
||||
PARTICLE_SECTION_SHAPE = §6§lShape
|
||||
PARTICLE_SECTION_TIMING = §6§lTiming and position
|
||||
PARTICLE_SECTION_CONDITIONS = §6§lConditions
|
||||
PARTICLE_FIXED_LOOK = §7This design always looks the same
|
||||
PARTICLE_VALUE_CLICK = §eClick to change
|
||||
PARTICLE_VALUE_TOGGLE = §eClick to toggle
|
||||
PARTICLE_VALUE_CYCLE = §eLeft click§8: §7next, §eright click§8: §7previous
|
||||
PARTICLE_VALUE_SELECTED = §aSelected
|
||||
PARTICLE_VALUE_RESET = §eReset to {0}
|
||||
PARTICLE_NUMBER_RANGE = §7Range§8: §e{0} §8- §e{1}
|
||||
PARTICLE_NUMBER_TYPE = §eClick to type a value
|
||||
PARTICLE_NUMBER_RESULT = §7→ §e{0}
|
||||
PARTICLE_NUMBER_AT_MAX = §7→ §e{0} §8(max)
|
||||
PARTICLE_NUMBER_AT_MIN = §7→ §e{0} §8(min)
|
||||
PARTICLE_COLOR_EDITOR = §0Colours
|
||||
PARTICLE_COLOR_REMOVE = §eClick to remove
|
||||
PARTICLE_COLOR_ADD = §aAdd colour
|
||||
PARTICLE_COLOR_HEX = §eEnter hex code
|
||||
PARTICLE_COLOR_HEX_EXAMPLE = §7For example #FF8800
|
||||
PARTICLE_COLOR_HEX_TITLE = Hex colour
|
||||
PARTICLE_COLOR_HEX_INVALID = §cThat is not a hex colour like #FF8800
|
||||
PARTICLE_COLOR_RANDOM = §eUse random colours
|
||||
PARTICLE_SLOT_EMPTY = §7Empty slot
|
||||
PARTICLE_SLOT_CREATE = §eClick to create
|
||||
PARTICLE_SLOT_ENTRIES = §7{0} entries
|
||||
PARTICLE_SLOT_ACTIVE = §aActive
|
||||
PARTICLE_SLOT_VIEW = §eClick to view
|
||||
PARTICLE_SLOT_ADD_ENTRY = §aAdd entry
|
||||
PARTICLE_SLOT_MAX_ENTRIES = §cThis slot is full
|
||||
PARTICLE_SLOT_OVER_BUDGET = §cThat would spawn too many particles for this slot
|
||||
PARTICLE_SLOT_RENAME = §eRename
|
||||
PARTICLE_SLOT_RENAME_TITLE = Slot name
|
||||
PARTICLE_SLOT_ICON = §eChange icon
|
||||
PARTICLE_SLOT_ICON_TITLE = §0Slot icon
|
||||
PARTICLE_SLOT_DELETE = §cDelete slot
|
||||
PARTICLE_SLOT_COST = §7Particles per tick§8: §e{0}§8/§7{1}
|
||||
|
||||
PARTICLE_ENTRY_EDITOR = §0{0}
|
||||
PARTICLE_ENTRY_DESIGN = §7Design§8: {0}
|
||||
PARTICLE_ENTRY_PARTICLE = §7Particle§8: {0}
|
||||
PARTICLE_ENTRY_LOCKED = §cDesign locked, not shown
|
||||
PARTICLE_ENTRY_COST = §7Particles per tick§8: §e{0}
|
||||
PARTICLE_ENTRY_COST_SHARE = §7This entry§8: §e{0}
|
||||
PARTICLE_ENTRY_EDIT = §eClick to edit
|
||||
PARTICLE_ENTRY_DELETE = §cDelete entry
|
||||
|
||||
PARTICLE_DESIGN_PICKER = §0Choose design
|
||||
PARTICLE_CHOICE_PICKER = §0{0}
|
||||
PARTICLE_COLOR_PICKER = §0Choose colour
|
||||
|
||||
PARTICLE_VALUE = §7Value§8: {0}
|
||||
PARTICLE_VALUE_RANDOM = §7Random colours
|
||||
PARTICLE_VALUE_YES = §aYes
|
||||
PARTICLE_VALUE_NO = §cNo
|
||||
|
||||
PARTICLE_DESIGN_SIMPLE = §fSimple
|
||||
PARTICLE_DESIGN_CIRCLE = §fCircle
|
||||
PARTICLE_DESIGN_WING = §fWings
|
||||
PARTICLE_DESIGN_CLOUD = §fCloud
|
||||
PARTICLE_DESIGN_BURST = §fBurst
|
||||
PARTICLE_DESIGN_FOOTPRINTS = §fFootprints
|
||||
PARTICLE_DESIGN_TEAM_LINK = §fTeam link
|
||||
PARTICLE_DESIGN_PULSE_SHIMMER = §fShimmer
|
||||
PARTICLE_DESIGN_HEART_BEAT = §fHeartbeat
|
||||
|
||||
PARTICLE_PARAM_PARTICLE = §fParticle
|
||||
PARTICLE_PARAM_GLYPH_FLIGHT = §fGlyph flight
|
||||
PARTICLE_PARAM_BURST_TRIGGER = §fWhen
|
||||
PARTICLE_PARAM_BURST_SHAPE = §fShape
|
||||
PARTICLE_PARAM_BURST_RADIUS = §fRing radius
|
||||
PARTICLE_PARAM_BURST_FALL = §fMinimum fall height
|
||||
PARTICLE_PARAM_FOOTPRINT_LIFETIME = §fPrint lifetime (ticks)
|
||||
PARTICLE_PARAM_FOOTPRINT_STEP = §fDistance between prints
|
||||
PARTICLE_PARAM_LINK_SPACING = §fDistance between particles
|
||||
PARTICLE_PARAM_PULSE_SPEED = §fPulse duration (ticks)
|
||||
PARTICLE_PARAM_PULSE_ROTATION = §fRotation
|
||||
PARTICLE_PARAM_HEART_BEAT_SPEED = §fBeat duration (ticks)
|
||||
PARTICLE_PARAM_COUNT = §fAmount
|
||||
PARTICLE_PARAM_SPREAD_X = §fSpread X
|
||||
PARTICLE_PARAM_SPREAD_Y = §fSpread Y
|
||||
PARTICLE_PARAM_SPREAD_Z = §fSpread Z
|
||||
PARTICLE_PARAM_SPEED = §fSpeed
|
||||
PARTICLE_PARAM_COLORS = §fColours
|
||||
PARTICLE_PARAM_BLOCK = §fBlock
|
||||
PARTICLE_PARAM_TRAIL_DURATION = §fTrail duration (ticks)
|
||||
PARTICLE_PARAM_TRAIL_TARGET_X = §fTrail target X
|
||||
PARTICLE_PARAM_TRAIL_TARGET_Y = §fTrail target Y
|
||||
PARTICLE_PARAM_TRAIL_TARGET_Z = §fTrail target Z
|
||||
PARTICLE_PARAM_OFFSET_X = §fOffset X
|
||||
PARTICLE_PARAM_OFFSET_Y = §fOffset Y
|
||||
PARTICLE_PARAM_OFFSET_Z = §fOffset Z
|
||||
PARTICLE_PARAM_INTERVAL = §fEvery n ticks
|
||||
PARTICLE_PARAM_PLACEMENT = §fRelative to
|
||||
PARTICLE_PARAM_TICK_MODE = §fShown
|
||||
PARTICLE_PARAM_MOVEMENT = §fMovement
|
||||
PARTICLE_PARAM_GLIDING = §fElytra
|
||||
PARTICLE_PARAM_GROUND = §fGround
|
||||
PARTICLE_PARAM_SNEAK = §fSneaking
|
||||
PARTICLE_PARAM_VISIBILITY = §fVisible to
|
||||
PARTICLE_PARAM_RADIUS = §fRadius
|
||||
PARTICLE_PARAM_CIRCLE_SPEED = §fRotation speed
|
||||
PARTICLE_PARAM_PHASE = §fStart angle
|
||||
PARTICLE_PARAM_RISE = §fUp and down height
|
||||
PARTICLE_PARAM_RISE_SPEED = §fUp and down speed
|
||||
PARTICLE_PARAM_BOUNCE = §fBounce back
|
||||
PARTICLE_PARAM_INVERTED = §fStart at the top
|
||||
PARTICLE_PARAM_WING_IMAGE = §fWing shape
|
||||
PARTICLE_PARAM_WING_SIZE = §fWing size
|
||||
PARTICLE_PARAM_WING_DETAIL = §fPoints per wing
|
||||
PARTICLE_PARAM_WING_COLORING = §fColour mode
|
||||
PARTICLE_PARAM_WING_SPARKLE = §fSparkle (points shown, %)
|
||||
PARTICLE_PARAM_WING_MOTION = §fMotion
|
||||
|
||||
PARTICLE_CHOICE_TICKMODE_MOVE = §fWhile moving
|
||||
PARTICLE_CHOICE_TICKMODE_ALWAYS = §fAlways
|
||||
PARTICLE_CHOICE_PLACEMENT_WORLD = §fWorld (X east, Y up, Z south)
|
||||
PARTICLE_CHOICE_PLACEMENT_BODY = §fBody (X right, Y up, Z forward)
|
||||
PARTICLE_CHOICE_MOVEMENT_ANY = §fAny movement
|
||||
PARTICLE_CHOICE_MOVEMENT_MOVING = §fOnly while moving
|
||||
PARTICLE_CHOICE_MOVEMENT_STANDING_STILL = §fOnly while standing still
|
||||
PARTICLE_CHOICE_GLIDING_ANY = §fWith or without elytra
|
||||
PARTICLE_CHOICE_GLIDING_GLIDING = §fOnly while gliding
|
||||
PARTICLE_CHOICE_GLIDING_NOT_GLIDING = §fNot while gliding
|
||||
PARTICLE_CHOICE_GROUND_ANY = §fOn ground and in air
|
||||
PARTICLE_CHOICE_GROUND_ON_GROUND = §fOnly on ground
|
||||
PARTICLE_CHOICE_GROUND_IN_AIR = §fOnly in air
|
||||
PARTICLE_CHOICE_SNEAK_ANY = §fSneaking or not
|
||||
PARTICLE_CHOICE_SNEAK_SNEAKING = §fOnly while sneaking
|
||||
PARTICLE_CHOICE_SNEAK_NOT_SNEAKING = §fNot while sneaking
|
||||
PARTICLE_CHOICE_VISIBILITY_EVERYONE = §fEveryone
|
||||
PARTICLE_CHOICE_VISIBILITY_ONLY_SELF = §fOnly me
|
||||
PARTICLE_CHOICE_VISIBILITY_ONLY_OTHERS = §fOnly others
|
||||
PARTICLE_CHOICE_IMAGE_SIMPLE = §fSimple
|
||||
PARTICLE_CHOICE_IMAGE_COMPLEX = §fComplex
|
||||
PARTICLE_CHOICE_IMAGE_SWORD = §fSword
|
||||
PARTICLE_CHOICE_IMAGE_BAT = §fBat
|
||||
PARTICLE_CHOICE_IMAGE_BUTTERFLY = §fButterfly
|
||||
PARTICLE_CHOICE_IMAGE_FAIRY = §fFairy
|
||||
PARTICLE_CHOICE_IMAGE_MWGL = §fMiniWarGear League
|
||||
PARTICLE_CHOICE_IMAGE_SW = §fSteamWar
|
||||
PARTICLE_CHOICE_IMAGE_WGS = §fWarGear Season
|
||||
PARTICLE_CHOICE_IMAGE_SWORD_CROSSED = §fCrossed Swords
|
||||
PARTICLE_CHOICE_COLORING_OVER_TIME = §fChange over time
|
||||
PARTICLE_CHOICE_COLORING_ALONG_WING = §fFrom the back to the tips
|
||||
PARTICLE_CHOICE_MOTION_NONE = §fNone
|
||||
PARTICLE_CHOICE_MOTION_FLAP_SLOW = §fFlap slowly
|
||||
PARTICLE_CHOICE_MOTION_FLAP_FAST = §fFlap quickly
|
||||
PARTICLE_CHOICE_MOTION_BEAT_ON_JUMP = §fBeat on jumping
|
||||
PARTICLE_CHOICE_TRIGGER_JUMP = §fOn jumping
|
||||
PARTICLE_CHOICE_TRIGGER_LAND = §fOn landing
|
||||
PARTICLE_CHOICE_TRIGGER_SNEAK = §fOn sneaking
|
||||
PARTICLE_CHOICE_BURSTSHAPE_PUFF = §fPuff
|
||||
PARTICLE_CHOICE_BURSTSHAPE_RING = §fRing
|
||||
PARTICLE_CHOICE_GLYPHFLIGHT_NORMAL = §fFloat down
|
||||
PARTICLE_CHOICE_GLYPHFLIGHT_INTO_PLAYER = §fInto the player
|
||||
PARTICLE_CHOICE_GLYPHFLIGHT_OUT_OF_PLAYER = §fOut of the player
|
||||
|
||||
# Particle types
|
||||
PARTICLE_TYPE_DUST = §fDust
|
||||
PARTICLE_TYPE_DUST_COLOR_TRANSITION = §fColour fading dust
|
||||
PARTICLE_TYPE_ENTITY_EFFECT = §fPotion swirl
|
||||
PARTICLE_TYPE_TINTED_LEAVES = §fTinted leaves
|
||||
PARTICLE_TYPE_BLOCK = §fBlock breaking
|
||||
PARTICLE_TYPE_BLOCK_CRUMBLE = §fBlock crumbling
|
||||
PARTICLE_TYPE_BLOCK_MARKER = §fBlock marker
|
||||
PARTICLE_TYPE_DUST_PILLAR = §fDust pillar
|
||||
PARTICLE_TYPE_FALLING_DUST = §fFalling dust
|
||||
PARTICLE_TYPE_ITEM = §fItem breaking
|
||||
PARTICLE_TYPE_TRAIL = §fTrail
|
||||
PARTICLE_TYPE_FLAME = §fFlame
|
||||
PARTICLE_TYPE_SMALL_FLAME = §fSmall flame
|
||||
PARTICLE_TYPE_SOUL_FIRE_FLAME = §fSoul fire flame
|
||||
PARTICLE_TYPE_SOUL = §fSoul
|
||||
PARTICLE_TYPE_LAVA = §fLava pop
|
||||
PARTICLE_TYPE_SMOKE = §fSmoke
|
||||
PARTICLE_TYPE_LARGE_SMOKE = §fLarge smoke
|
||||
PARTICLE_TYPE_WHITE_SMOKE = §fWhite smoke
|
||||
PARTICLE_TYPE_CAMPFIRE_COSY_SMOKE = §fCampfire smoke
|
||||
PARTICLE_TYPE_CAMPFIRE_SIGNAL_SMOKE = §fSignal smoke
|
||||
PARTICLE_TYPE_ASH = §fAsh
|
||||
PARTICLE_TYPE_WHITE_ASH = §fWhite ash
|
||||
PARTICLE_TYPE_POOF = §fPoof
|
||||
PARTICLE_TYPE_CLOUD = §fCloud
|
||||
PARTICLE_TYPE_SNEEZE = §fSneeze
|
||||
PARTICLE_TYPE_SPIT = §fSpit
|
||||
PARTICLE_TYPE_EXPLOSION = §fExplosion
|
||||
PARTICLE_TYPE_SONIC_BOOM = §fSonic boom
|
||||
PARTICLE_TYPE_SWEEP_ATTACK = §fSweep attack
|
||||
PARTICLE_TYPE_GUST = §fGust
|
||||
PARTICLE_TYPE_SMALL_GUST = §fSmall gust
|
||||
PARTICLE_TYPE_DRIPPING_WATER = §fDripping water
|
||||
PARTICLE_TYPE_FALLING_WATER = §fFalling water
|
||||
PARTICLE_TYPE_DRIPPING_LAVA = §fDripping lava
|
||||
PARTICLE_TYPE_FALLING_LAVA = §fFalling lava
|
||||
PARTICLE_TYPE_LANDING_LAVA = §fLanding lava
|
||||
PARTICLE_TYPE_DRIPPING_HONEY = §fDripping honey
|
||||
PARTICLE_TYPE_FALLING_HONEY = §fFalling honey
|
||||
PARTICLE_TYPE_LANDING_HONEY = §fLanding honey
|
||||
PARTICLE_TYPE_FALLING_NECTAR = §fFalling nectar
|
||||
PARTICLE_TYPE_DRIPPING_OBSIDIAN_TEAR = §fDripping obsidian tear
|
||||
PARTICLE_TYPE_FALLING_OBSIDIAN_TEAR = §fFalling obsidian tear
|
||||
PARTICLE_TYPE_LANDING_OBSIDIAN_TEAR = §fLanding obsidian tear
|
||||
PARTICLE_TYPE_DRIPPING_DRIPSTONE_WATER = §fDripstone water
|
||||
PARTICLE_TYPE_FALLING_DRIPSTONE_WATER = §fFalling dripstone water
|
||||
PARTICLE_TYPE_DRIPPING_DRIPSTONE_LAVA = §fDripstone lava
|
||||
PARTICLE_TYPE_FALLING_DRIPSTONE_LAVA = §fFalling dripstone lava
|
||||
PARTICLE_TYPE_RAIN = §fRain
|
||||
PARTICLE_TYPE_SPLASH = §fSplash
|
||||
PARTICLE_TYPE_FISHING = §fFishing wake
|
||||
PARTICLE_TYPE_UNDERWATER = §fUnderwater
|
||||
PARTICLE_TYPE_BUBBLE_POP = §fBubble pop
|
||||
PARTICLE_TYPE_DOLPHIN = §fDolphin trail
|
||||
PARTICLE_TYPE_NAUTILUS = §fNautilus
|
||||
PARTICLE_TYPE_SQUID_INK = §fSquid ink
|
||||
PARTICLE_TYPE_GLOW_SQUID_INK = §fGlow squid ink
|
||||
PARTICLE_TYPE_GLOW = §fGlow
|
||||
PARTICLE_TYPE_FALLING_SPORE_BLOSSOM = §fFalling spore blossom
|
||||
PARTICLE_TYPE_SPORE_BLOSSOM_AIR = §fFloating spores
|
||||
PARTICLE_TYPE_CHERRY_LEAVES = §fCherry leaves
|
||||
PARTICLE_TYPE_PALE_OAK_LEAVES = §fPale oak leaves
|
||||
PARTICLE_TYPE_CRIMSON_SPORE = §fCrimson spores
|
||||
PARTICLE_TYPE_WARPED_SPORE = §fWarped spores
|
||||
PARTICLE_TYPE_MYCELIUM = §fMycelium
|
||||
PARTICLE_TYPE_COMPOSTER = §fComposter
|
||||
PARTICLE_TYPE_FIREFLY = §fFirefly
|
||||
PARTICLE_TYPE_SNOWFLAKE = §fSnowflake
|
||||
PARTICLE_TYPE_ITEM_SNOWBALL = §fSnowball
|
||||
PARTICLE_TYPE_ITEM_SLIME = §fSlime
|
||||
PARTICLE_TYPE_ITEM_COBWEB = §fCobweb
|
||||
PARTICLE_TYPE_EGG_CRACK = §fEgg crack
|
||||
PARTICLE_TYPE_DUST_PLUME = §fDust plume
|
||||
PARTICLE_TYPE_INFESTED = §fInfested
|
||||
PARTICLE_TYPE_HEART = §fHeart
|
||||
PARTICLE_TYPE_NOTE = §fNote
|
||||
PARTICLE_TYPE_HAPPY_VILLAGER = §fHappy villager
|
||||
PARTICLE_TYPE_ANGRY_VILLAGER = §fAngry villager
|
||||
PARTICLE_TYPE_DAMAGE_INDICATOR = §fDamage
|
||||
PARTICLE_TYPE_CRIT = §fCritical hit
|
||||
PARTICLE_TYPE_ENCHANTED_HIT = §fEnchanted hit
|
||||
PARTICLE_TYPE_ENCHANT = §fEnchanting glyphs
|
||||
PARTICLE_TYPE_EFFECT = §fPotion effect
|
||||
PARTICLE_TYPE_INSTANT_EFFECT = §fInstant effect
|
||||
PARTICLE_TYPE_WITCH = §fWitch magic
|
||||
PARTICLE_TYPE_END_ROD = §fEnd rod
|
||||
PARTICLE_TYPE_PORTAL = §fPortal
|
||||
PARTICLE_TYPE_REVERSE_PORTAL = §fReverse portal
|
||||
PARTICLE_TYPE_DRAGON_BREATH = §fDragon breath
|
||||
PARTICLE_TYPE_FIREWORK = §fFirework spark
|
||||
PARTICLE_TYPE_TOTEM_OF_UNDYING = §fTotem
|
||||
PARTICLE_TYPE_ELECTRIC_SPARK = §fElectric spark
|
||||
PARTICLE_TYPE_SCRAPE = §fScrape
|
||||
PARTICLE_TYPE_WAX_ON = §fWax on
|
||||
PARTICLE_TYPE_WAX_OFF = §fWax off
|
||||
PARTICLE_TYPE_SCULK_SOUL = §fSculk soul
|
||||
PARTICLE_TYPE_SCULK_CHARGE = §fSculk charge
|
||||
PARTICLE_TYPE_SCULK_CHARGE_POP = §fSculk charge pop
|
||||
PARTICLE_TYPE_SHRIEK = §fShriek
|
||||
PARTICLE_TYPE_RAID_OMEN = §fRaid omen
|
||||
PARTICLE_TYPE_TRIAL_OMEN = §fTrial omen
|
||||
PARTICLE_TYPE_OMINOUS_SPAWNING = §fOminous spawning
|
||||
PARTICLE_TYPE_TRIAL_SPAWNER_DETECTION = §fTrial spawner flame
|
||||
PARTICLE_TYPE_TRIAL_SPAWNER_DETECTION_OMINOUS = §fOminous trial spawner flame
|
||||
PARTICLE_TYPE_VAULT_CONNECTION = §fVault connection
|
||||
|
||||
# Particle blocks
|
||||
PARTICLE_CHOICE_BLOCK_STONE = §fStone
|
||||
PARTICLE_CHOICE_BLOCK_COBBLESTONE = §fCobblestone
|
||||
PARTICLE_CHOICE_BLOCK_DIRT = §fDirt
|
||||
PARTICLE_CHOICE_BLOCK_GRASS_BLOCK = §fGrass block
|
||||
PARTICLE_CHOICE_BLOCK_SAND = §fSand
|
||||
PARTICLE_CHOICE_BLOCK_RED_SAND = §fRed sand
|
||||
PARTICLE_CHOICE_BLOCK_GRAVEL = §fGravel
|
||||
PARTICLE_CHOICE_BLOCK_OAK_PLANKS = §fOak planks
|
||||
PARTICLE_CHOICE_BLOCK_OAK_LEAVES = §fOak leaves
|
||||
PARTICLE_CHOICE_BLOCK_CHERRY_LEAVES = §fCherry leaves
|
||||
PARTICLE_CHOICE_BLOCK_SNOW_BLOCK = §fSnow block
|
||||
PARTICLE_CHOICE_BLOCK_ICE = §fIce
|
||||
PARTICLE_CHOICE_BLOCK_GLASS = §fGlass
|
||||
PARTICLE_CHOICE_BLOCK_TNT = §fTNT
|
||||
PARTICLE_CHOICE_BLOCK_PUMPKIN = §fPumpkin
|
||||
PARTICLE_CHOICE_BLOCK_MELON = §fMelon
|
||||
PARTICLE_CHOICE_BLOCK_HONEY_BLOCK = §fHoney block
|
||||
PARTICLE_CHOICE_BLOCK_SLIME_BLOCK = §fSlime block
|
||||
PARTICLE_CHOICE_BLOCK_NETHERRACK = §fNetherrack
|
||||
PARTICLE_CHOICE_BLOCK_SOUL_SAND = §fSoul sand
|
||||
PARTICLE_CHOICE_BLOCK_MAGMA_BLOCK = §fMagma block
|
||||
PARTICLE_CHOICE_BLOCK_END_STONE = §fEnd stone
|
||||
PARTICLE_CHOICE_BLOCK_OBSIDIAN = §fObsidian
|
||||
PARTICLE_CHOICE_BLOCK_PRISMARINE = §fPrismarine
|
||||
PARTICLE_CHOICE_BLOCK_SCULK = §fSculk
|
||||
PARTICLE_CHOICE_BLOCK_AMETHYST_BLOCK = §fAmethyst block
|
||||
PARTICLE_CHOICE_BLOCK_COAL_BLOCK = §fBlock of coal
|
||||
PARTICLE_CHOICE_BLOCK_IRON_BLOCK = §fBlock of iron
|
||||
PARTICLE_CHOICE_BLOCK_GOLD_BLOCK = §fBlock of gold
|
||||
PARTICLE_CHOICE_BLOCK_REDSTONE_BLOCK = §fBlock of redstone
|
||||
PARTICLE_CHOICE_BLOCK_LAPIS_BLOCK = §fBlock of lapis lazuli
|
||||
PARTICLE_CHOICE_BLOCK_EMERALD_BLOCK = §fBlock of emerald
|
||||
PARTICLE_CHOICE_BLOCK_DIAMOND_BLOCK = §fBlock of diamond
|
||||
PARTICLE_CHOICE_BLOCK_WHITE_WOOL = §fWhite wool
|
||||
PARTICLE_CHOICE_BLOCK_RED_WOOL = §fRed wool
|
||||
PARTICLE_CHOICE_BLOCK_ORANGE_WOOL = §fOrange wool
|
||||
PARTICLE_CHOICE_BLOCK_YELLOW_WOOL = §fYellow wool
|
||||
PARTICLE_CHOICE_BLOCK_LIME_WOOL = §fLime wool
|
||||
PARTICLE_CHOICE_BLOCK_LIGHT_BLUE_WOOL = §fLight blue wool
|
||||
PARTICLE_CHOICE_BLOCK_BLUE_WOOL = §fBlue wool
|
||||
PARTICLE_CHOICE_BLOCK_PURPLE_WOOL = §fPurple wool
|
||||
PARTICLE_CHOICE_BLOCK_PINK_WOOL = §fPink wool
|
||||
PARTICLE_CHOICE_BLOCK_BLACK_WOOL = §fBlack wool
|
||||
|
||||
PARTICLE_PLAYER_HAYLIM_AURA = §fHaylim\'s Aura
|
||||
PARTICLE_PLAYER_1063 = §7Circling Circle
|
||||
@@ -136,34 +401,12 @@ PARTICLE_TEAM_PULSE_AURA_3 = §fPulse Aura §fEnchanted
|
||||
PARTICLE_TEAM_PULSE_LOGO = §fPulse Logo
|
||||
PARTICLE_TEAM_PULSE_HEART_BEAT = §cHeart Beat
|
||||
|
||||
PARTICLE_RAINCLOUD_NORMAL = §fRaincloud
|
||||
PARTICLE_RAINCLOUD_RED = §fLava cloud
|
||||
PARTICLE_RAINCLOUD_YELLOW = §fNectar cloud
|
||||
PARTICLE_RAINCLOUD_ORANGE = §fHoney cloud
|
||||
PARTICLE_RAINCLOUD_GREEN = §fSpore Blossom cloud
|
||||
PARTICLE_RAINCLOUD_PURPLE = §fObsidian Tears cloud
|
||||
PARTICLE_RAINCLOUD_NAUTILUS = §fNautilus cloud
|
||||
PARTICLE_RAINCLOUD_ENCHANTMENT = §fEnchantment cloud
|
||||
PARTICLE_RAINCLOUD_SLIME = §fSlime cloud
|
||||
PARTICLE_RAINCLOUD_HAIL = §fHail cloud
|
||||
PARTICLE_RAINCLOUD_SNOW = §fSnow cloud
|
||||
PARTICLE_RAINCLOUD_TOTEM = §fTotem cloud
|
||||
|
||||
PARTICLE_EVENT_ENCHANTING = §cEnchantment
|
||||
PARTICLE_EVENT_CLOUD = §fClouds
|
||||
PARTICLE_EVENT_SMOKE = §7Smoke
|
||||
PARTICLE_EVENT_WATER = §bWater
|
||||
PARTICLE_EVENT_WINGS = §fWings
|
||||
PARTICLE_EVENT_RAIN_CLOUD = §fRandom Raincloud
|
||||
PARTICLE_EVENT_WGS = §fWGS
|
||||
PARTICLE_EVENT_WARGEARCLASH = §fClash
|
||||
|
||||
PARTICLE_EGG_HUNT_EASY = §aGreenery
|
||||
PARTICLE_EGG_HUNT_MEDIUM = §eHide and Seek
|
||||
PARTICLE_EGG_HUNT_HARD = §cOrbit
|
||||
PARTICLE_EGG_HUNT_EXTREME = §5Purple rain
|
||||
PARTICLE_EGG_HUNT_ADVANCED = §5Lift off
|
||||
PARTICLE_EGG_HUNT_HALF = §fThe hunt is on
|
||||
|
||||
JUMP_AND_RUN_PROGRESS = §e{0}§8/§f{1} §c{2} §7{3}
|
||||
JUMP_AND_RUN_CANCEL = {0}
|
||||
|
||||
@@ -49,109 +49,353 @@ PORTAL_NO_WORLDEDIT_SELECTION = §cKeine WorldEdit Selection
|
||||
PARTICLE_INVENTORY = §0Partikel
|
||||
PARTICLE_DESELECT = §8Keine Partikel
|
||||
|
||||
PARTICLE_SHOW_HAS_ALL = §8Du hast alle Partikel
|
||||
PARTICLE_SHOW_ALL = §aZeige alle
|
||||
PARTICLE_SHOW_UNLOCKED = §cZeige freigeschaltete
|
||||
|
||||
PARTICLE_UNLOCKED_BY = §eFreigeschaltet durch
|
||||
PARTICLE_UNLOCKED_BY_TEAM = §fTeambeitritt
|
||||
PARTICLE_UNLOCKED_BY_SPECIFIC_TEAM = §fTeam {0}
|
||||
PARTICLE_UNLOCKED_BY_SPECIFIC_TEAM_EASTER = §fTeam {0} (Oster-Eierer-Suche)
|
||||
PARTICLE_UNLOCKED_BY_SPECIFIC_USER = §fUser {0}
|
||||
PARTICLE_UNLOCKED_BY_SPECIFIC_USER_EASTER = §fUser {0} (Oster-Eierer-Suche)
|
||||
PARTICLE_UNLOCKED_BY_EVENT = §fEventteilnahme
|
||||
PARTICLE_UNLOCKED_BY_SERVER_TEAM = §fServerteam
|
||||
PARTICLE_UNLOCKED_BY_EVENT = §fEventteilnahme
|
||||
PARTICLE_UNLOCKED_BY_EVENT_PLACEMENT = §f{0} 1., 2. oder 3. Platz
|
||||
PARTICLE_UNLOCKED_BY_EVENT_PARTICIPATION = §f{0}
|
||||
PARTICLE_UNLOCKED_BY_EASTER_EGG_HUNT_HALF = §fFinde 45 Oster-Eierer
|
||||
PARTICLE_UNLOCKED_BY_EASTER_EGG_HUNT_DIFFICULTY = §fAlle Eierer mit Schwierigkeit {0} finden
|
||||
|
||||
PARTICLE_ATTRIBUTE_CIRCLE = §8-§f Ring
|
||||
PARTICLE_ATTRIBUTE_BI_CIRCLE = §8-§f Doppelring
|
||||
PARTICLE_ATTRIBUTE_TRI_CIRCLE = §8-§f Dreifachring
|
||||
PARTICLE_ATTRIBUTE_CLOUD = §8-§f Wolke
|
||||
PARTICLE_ATTRIBUTE_TICK = §8-§f Immer aktiv
|
||||
PARTICLE_ATTRIBUTE_NON_FLOOR = §8-§f In der Luft
|
||||
PARTICLE_ATTRIBUTE_FLOOR = §8-§f Auf dem Boden
|
||||
PARTICLE_ATTRIBUTE_NON_FLYING = §8-§f Nicht am Fliegen
|
||||
PARTICLE_ATTRIBUTE_FLYING = §8-§f Am Fliegen
|
||||
PARTICLE_ATTRIBUTE_WING = §8-§f Flügel
|
||||
PARTICLE_ATTRIBUTE_SNEAKING = §8-§f Ducken
|
||||
PARTICLE_ATTRIBUTE_NON_MOVING = §8-§f Beim Stehen
|
||||
PARTICLE_ATTRIBUTE = §eAttribute§7:
|
||||
|
||||
PARTICLE_SELECT = §eZum Auswählen klicken
|
||||
PARTICLE_LOCKED = §c§lGesperrt
|
||||
PARTICLE_DEACTIVATED = §cEs sind zu viele Spieler online! Partikel sind deaktiviert.
|
||||
|
||||
PARTICLE_SNEEZE = §aSneeze
|
||||
PARTICLE_SMOKE = §7Rauch
|
||||
PARTICLE_FIRE = §cFeuer
|
||||
PARTICLE_WATER = §bWasser
|
||||
PARTICLE_HEART = §cHerzen
|
||||
PARTICLE_NOTES = §eNoten
|
||||
PARTICLE_NAUTILUS = §aNautilus
|
||||
PARTICLE_SNOWBALL = §fSchneeball
|
||||
PARTICLE_EFFECT = §5Effekt
|
||||
PARTICLE_CAMPFIRE = §7Rauch
|
||||
PARTICLE_MAGIC = §5Magie
|
||||
PARTICLE_ANGRY = §4Wut
|
||||
PARTICLE_SLIME = §aSchleim
|
||||
PARTICLE_MOB = §7Mob
|
||||
PARTICLE_SQUID = §8Tintenfisch
|
||||
PARTICLE_BUBBLE = §aBlasen
|
||||
PARTICLE_HONEY = §6Honig
|
||||
PARTICLE_NECTAR = §6Nektar
|
||||
PARTICLE_FIREWORK = §7Feuerwerk
|
||||
PARTICLE_DRAGON_BREATH = §5Drachenatem
|
||||
PARTICLE_DAMAGE = §5Schaden
|
||||
PARTICLE_DOLPHIN = §dDelphin
|
||||
PARTICLE_WITCH = §5Hexe
|
||||
PARTICLE_ENCHANTING = §eZauber
|
||||
PARTICLE_HAPPY = §2Freude
|
||||
PARTICLE_FLAME = §7Flammen
|
||||
PARTICLE_END_ROD = §fEnd Rod
|
||||
PARTICLE_CLOUD = §7Wolke
|
||||
PARTICLE_TOTEM = §aTotem
|
||||
PARTICLE_TOWN = §5Town
|
||||
PARTICLE_ENCHANTING_CIRCLE = §fVerzaubert
|
||||
PARTICLE_WATER_FIRE = §bWasser§7/§cFeuer
|
||||
PARTICLE_MAGIC_ENCHANTING = §5Magie§7/§eZauber
|
||||
PARTICLE_WINGS_EVIL = §5Lila Flügel
|
||||
PARTICLE_BACK = §7Zurück
|
||||
PARTICLE_SLOT_OPEN = §eZum Öffnen klicken
|
||||
PARTICLE_SLOT_CLICK_USE = §eZum Verwenden klicken
|
||||
PARTICLE_SLOT_CLICK_STOP = §eZum Beenden klicken
|
||||
PARTICLE_SLOT_SHIFT_EDIT = §eZum Bearbeiten Shift-Klick
|
||||
PARTICLE_SLOT_NAME = §f{0} §8(§e{1}§8/§7{2}§8)
|
||||
PARTICLE_SLOT_TITLE = §0{0} §8({1}/{2})
|
||||
PARTICLE_SHIFT_TO_DELETE = §7Zum Löschen Shift-Klick
|
||||
PARTICLE_SECTION_DESIGN = §6§lDesign
|
||||
PARTICLE_SECTION_PARTICLE = §6§lPartikel
|
||||
PARTICLE_SECTION_SHAPE = §6§lForm
|
||||
PARTICLE_SECTION_TIMING = §6§lZeit und Position
|
||||
PARTICLE_SECTION_CONDITIONS = §6§lBedingungen
|
||||
PARTICLE_FIXED_LOOK = §7Dieses Design sieht immer gleich aus
|
||||
PARTICLE_VALUE_CLICK = §eZum Ändern klicken
|
||||
PARTICLE_VALUE_TOGGLE = §eZum Umschalten klicken
|
||||
PARTICLE_VALUE_CYCLE = §eLinksklick§8: §7weiter, §eRechtsklick§8: §7zurück
|
||||
PARTICLE_VALUE_SELECTED = §aAusgewählt
|
||||
PARTICLE_VALUE_RESET = §eAuf {0} zurücksetzen
|
||||
PARTICLE_NUMBER_RANGE = §7Bereich§8: §e{0} §8- §e{1}
|
||||
PARTICLE_NUMBER_TYPE = §eZum Eingeben klicken
|
||||
PARTICLE_NUMBER_RESULT = §7→ §e{0}
|
||||
PARTICLE_NUMBER_AT_MAX = §7→ §e{0} §8(Max)
|
||||
PARTICLE_NUMBER_AT_MIN = §7→ §e{0} §8(Min)
|
||||
PARTICLE_COLOR_EDITOR = §0Farben
|
||||
PARTICLE_COLOR_REMOVE = §eZum Entfernen klicken
|
||||
PARTICLE_COLOR_ADD = §aFarbe hinzufügen
|
||||
PARTICLE_COLOR_HEX = §eHex-Code eingeben
|
||||
PARTICLE_COLOR_HEX_EXAMPLE = §7Zum Beispiel #FF8800
|
||||
PARTICLE_COLOR_HEX_TITLE = Hex-Farbe
|
||||
PARTICLE_COLOR_HEX_INVALID = §cDas ist keine Hex-Farbe wie #FF8800
|
||||
PARTICLE_COLOR_RANDOM = §eZufällige Farben verwenden
|
||||
PARTICLE_SLOT_EMPTY = §7Leerer Slot
|
||||
PARTICLE_SLOT_CREATE = §eZum Erstellen klicken
|
||||
PARTICLE_SLOT_ENTRIES = §7{0} Einträge
|
||||
PARTICLE_SLOT_ACTIVE = §aAktiv
|
||||
PARTICLE_SLOT_VIEW = §eZum Ansehen klicken
|
||||
PARTICLE_SLOT_ADD_ENTRY = §aEintrag hinzufügen
|
||||
PARTICLE_SLOT_MAX_ENTRIES = §cDieser Slot ist voll
|
||||
PARTICLE_SLOT_OVER_BUDGET = §cDamit würde dieser Slot zu viele Partikel erzeugen
|
||||
PARTICLE_SLOT_RENAME = §eUmbenennen
|
||||
PARTICLE_SLOT_RENAME_TITLE = Slotname
|
||||
PARTICLE_SLOT_ICON = §eSymbol ändern
|
||||
PARTICLE_SLOT_ICON_TITLE = §0Slotsymbol
|
||||
PARTICLE_SLOT_DELETE = §cSlot löschen
|
||||
PARTICLE_SLOT_COST = §7Partikel pro Tick§8: §e{0}§8/§7{1}
|
||||
|
||||
PARTICLE_ENTRY_EDITOR = §0{0}
|
||||
PARTICLE_ENTRY_DESIGN = §7Design§8: {0}
|
||||
PARTICLE_ENTRY_PARTICLE = §7Partikel§8: {0}
|
||||
PARTICLE_ENTRY_LOCKED = §cDesign gesperrt, wird nicht angezeigt
|
||||
PARTICLE_ENTRY_COST = §7Partikel pro Tick§8: §e{0}
|
||||
PARTICLE_ENTRY_COST_SHARE = §7Dieser Eintrag§8: §e{0}
|
||||
PARTICLE_ENTRY_EDIT = §eZum Bearbeiten klicken
|
||||
PARTICLE_ENTRY_DELETE = §cEintrag löschen
|
||||
|
||||
PARTICLE_DESIGN_PICKER = §0Design wählen
|
||||
PARTICLE_CHOICE_PICKER = §0{0}
|
||||
PARTICLE_COLOR_PICKER = §0Farbe wählen
|
||||
|
||||
PARTICLE_VALUE = §7Wert§8: {0}
|
||||
PARTICLE_VALUE_RANDOM = §7Zufällige Farben
|
||||
PARTICLE_VALUE_YES = §aJa
|
||||
PARTICLE_VALUE_NO = §cNein
|
||||
|
||||
PARTICLE_DESIGN_SIMPLE = §fEinfach
|
||||
PARTICLE_DESIGN_CIRCLE = §fKreis
|
||||
PARTICLE_DESIGN_WING = §fFlügel
|
||||
PARTICLE_DESIGN_CLOUD = §fWolke
|
||||
PARTICLE_DESIGN_BURST = §fStoß
|
||||
PARTICLE_DESIGN_FOOTPRINTS = §fFußspuren
|
||||
PARTICLE_DESIGN_TEAM_LINK = §fTeamband
|
||||
PARTICLE_DESIGN_PULSE_SHIMMER = §fSchimmer
|
||||
PARTICLE_DESIGN_HEART_BEAT = §fHerzschlag
|
||||
|
||||
PARTICLE_PARAM_PARTICLE = §fPartikel
|
||||
PARTICLE_PARAM_GLYPH_FLIGHT = §fGlyphenflug
|
||||
PARTICLE_PARAM_BURST_TRIGGER = §fWann
|
||||
PARTICLE_PARAM_BURST_SHAPE = §fForm
|
||||
PARTICLE_PARAM_BURST_RADIUS = §fRingradius
|
||||
PARTICLE_PARAM_BURST_FALL = §fMindestfallhöhe
|
||||
PARTICLE_PARAM_FOOTPRINT_LIFETIME = §fSpurdauer (Ticks)
|
||||
PARTICLE_PARAM_FOOTPRINT_STEP = §fAbstand der Spuren
|
||||
PARTICLE_PARAM_LINK_SPACING = §fAbstand der Partikel
|
||||
PARTICLE_PARAM_PULSE_SPEED = §fPulsdauer (Ticks)
|
||||
PARTICLE_PARAM_PULSE_ROTATION = §fDrehung
|
||||
PARTICLE_PARAM_HEART_BEAT_SPEED = §fSchlagdauer (Ticks)
|
||||
PARTICLE_PARAM_COUNT = §fAnzahl
|
||||
PARTICLE_PARAM_SPREAD_X = §fStreuung X
|
||||
PARTICLE_PARAM_SPREAD_Y = §fStreuung Y
|
||||
PARTICLE_PARAM_SPREAD_Z = §fStreuung Z
|
||||
PARTICLE_PARAM_SPEED = §fGeschwindigkeit
|
||||
PARTICLE_PARAM_COLORS = §fFarben
|
||||
PARTICLE_PARAM_BLOCK = §fBlock
|
||||
PARTICLE_PARAM_TRAIL_DURATION = §fSpurdauer (Ticks)
|
||||
PARTICLE_PARAM_TRAIL_TARGET_X = §fSpurziel X
|
||||
PARTICLE_PARAM_TRAIL_TARGET_Y = §fSpurziel Y
|
||||
PARTICLE_PARAM_TRAIL_TARGET_Z = §fSpurziel Z
|
||||
PARTICLE_PARAM_OFFSET_X = §fVersatz X
|
||||
PARTICLE_PARAM_OFFSET_Y = §fVersatz Y
|
||||
PARTICLE_PARAM_OFFSET_Z = §fVersatz Z
|
||||
PARTICLE_PARAM_INTERVAL = §fAlle n Ticks
|
||||
PARTICLE_PARAM_PLACEMENT = §fAusgerichtet an
|
||||
PARTICLE_PARAM_TICK_MODE = §fAnzeige
|
||||
PARTICLE_PARAM_MOVEMENT = §fBewegung
|
||||
PARTICLE_PARAM_GLIDING = §fElytra
|
||||
PARTICLE_PARAM_GROUND = §fBoden
|
||||
PARTICLE_PARAM_SNEAK = §fDucken
|
||||
PARTICLE_PARAM_VISIBILITY = §fSichtbar für
|
||||
PARTICLE_PARAM_RADIUS = §fRadius
|
||||
PARTICLE_PARAM_CIRCLE_SPEED = §fDrehgeschwindigkeit
|
||||
PARTICLE_PARAM_PHASE = §fStartwinkel
|
||||
PARTICLE_PARAM_RISE = §fAuf und ab Höhe
|
||||
PARTICLE_PARAM_RISE_SPEED = §fAuf und ab Geschwindigkeit
|
||||
PARTICLE_PARAM_BOUNCE = §fZurückspringen
|
||||
PARTICLE_PARAM_INVERTED = §fOben beginnen
|
||||
PARTICLE_PARAM_WING_IMAGE = §fFlügelform
|
||||
PARTICLE_PARAM_WING_SIZE = §fFlügelgröße
|
||||
PARTICLE_PARAM_WING_DETAIL = §fPunkte pro Flügel
|
||||
PARTICLE_PARAM_WING_COLORING = §fFarbmodus
|
||||
PARTICLE_PARAM_WING_SPARKLE = §fFunkeln (sichtbare Punkte, %)
|
||||
PARTICLE_PARAM_WING_MOTION = §fBewegung
|
||||
|
||||
PARTICLE_CHOICE_TICKMODE_MOVE = §fBeim Bewegen
|
||||
PARTICLE_CHOICE_TICKMODE_ALWAYS = §fImmer
|
||||
PARTICLE_CHOICE_PLACEMENT_WORLD = §fWelt (X Osten, Y oben, Z Süden)
|
||||
PARTICLE_CHOICE_PLACEMENT_BODY = §fKörper (X rechts, Y oben, Z vorne)
|
||||
PARTICLE_CHOICE_MOVEMENT_ANY = §fJede Bewegung
|
||||
PARTICLE_CHOICE_MOVEMENT_MOVING = §fNur beim Bewegen
|
||||
PARTICLE_CHOICE_MOVEMENT_STANDING_STILL = §fNur beim Stehen
|
||||
PARTICLE_CHOICE_GLIDING_ANY = §fMit oder ohne Elytra
|
||||
PARTICLE_CHOICE_GLIDING_GLIDING = §fNur beim Gleiten
|
||||
PARTICLE_CHOICE_GLIDING_NOT_GLIDING = §fNicht beim Gleiten
|
||||
PARTICLE_CHOICE_GROUND_ANY = §fAm Boden und in der Luft
|
||||
PARTICLE_CHOICE_GROUND_ON_GROUND = §fNur am Boden
|
||||
PARTICLE_CHOICE_GROUND_IN_AIR = §fNur in der Luft
|
||||
PARTICLE_CHOICE_SNEAK_ANY = §fDucken egal
|
||||
PARTICLE_CHOICE_SNEAK_SNEAKING = §fNur beim Ducken
|
||||
PARTICLE_CHOICE_SNEAK_NOT_SNEAKING = §fNicht beim Ducken
|
||||
PARTICLE_CHOICE_VISIBILITY_EVERYONE = §fAlle
|
||||
PARTICLE_CHOICE_VISIBILITY_ONLY_SELF = §fNur ich
|
||||
PARTICLE_CHOICE_VISIBILITY_ONLY_OTHERS = §fNur andere
|
||||
PARTICLE_CHOICE_IMAGE_SIMPLE = §fEinfach
|
||||
PARTICLE_CHOICE_IMAGE_COMPLEX = §fKomplex
|
||||
PARTICLE_CHOICE_IMAGE_SWORD = §fSchwert
|
||||
PARTICLE_CHOICE_IMAGE_BAT = §fFledermaus
|
||||
PARTICLE_CHOICE_IMAGE_BUTTERFLY = §fSchmetterling
|
||||
PARTICLE_CHOICE_IMAGE_FAIRY = §fFee
|
||||
PARTICLE_CHOICE_IMAGE_MWGL = §fMiniWarGear Liga
|
||||
PARTICLE_CHOICE_IMAGE_SW = §fSteamWar
|
||||
PARTICLE_CHOICE_IMAGE_WGS = §fWarGear Saison
|
||||
PARTICLE_CHOICE_IMAGE_SWORD_CROSSED = §fGekreuzte Schwerter
|
||||
PARTICLE_CHOICE_COLORING_OVER_TIME = §fWechseln mit der Zeit
|
||||
PARTICLE_CHOICE_COLORING_ALONG_WING = §fVom Rücken zu den Spitzen
|
||||
PARTICLE_CHOICE_MOTION_NONE = §fKeine
|
||||
PARTICLE_CHOICE_MOTION_FLAP_SLOW = §fLangsam schlagen
|
||||
PARTICLE_CHOICE_MOTION_FLAP_FAST = §fSchnell schlagen
|
||||
PARTICLE_CHOICE_MOTION_BEAT_ON_JUMP = §fSchlag beim Springen
|
||||
PARTICLE_CHOICE_TRIGGER_JUMP = §fBeim Springen
|
||||
PARTICLE_CHOICE_TRIGGER_LAND = §fBeim Landen
|
||||
PARTICLE_CHOICE_TRIGGER_SNEAK = §fBeim Schleichen
|
||||
PARTICLE_CHOICE_BURSTSHAPE_PUFF = §fWolke
|
||||
PARTICLE_CHOICE_BURSTSHAPE_RING = §fRing
|
||||
PARTICLE_CHOICE_GLYPHFLIGHT_NORMAL = §fHerabschweben
|
||||
PARTICLE_CHOICE_GLYPHFLIGHT_INTO_PLAYER = §fIn den Spieler
|
||||
PARTICLE_CHOICE_GLYPHFLIGHT_OUT_OF_PLAYER = §fAus dem Spieler
|
||||
|
||||
# Particle types
|
||||
PARTICLE_TYPE_DUST = §fStaub
|
||||
PARTICLE_TYPE_DUST_COLOR_TRANSITION = §fFarbverlaufsstaub
|
||||
PARTICLE_TYPE_ENTITY_EFFECT = §fTrankwirbel
|
||||
PARTICLE_TYPE_TINTED_LEAVES = §fGefärbte Blätter
|
||||
PARTICLE_TYPE_BLOCK = §fBlocksplitter
|
||||
PARTICLE_TYPE_BLOCK_CRUMBLE = §fBröckelnder Block
|
||||
PARTICLE_TYPE_BLOCK_MARKER = §fBlockmarkierung
|
||||
PARTICLE_TYPE_DUST_PILLAR = §fStaubsäule
|
||||
PARTICLE_TYPE_FALLING_DUST = §fFallender Staub
|
||||
PARTICLE_TYPE_ITEM = §fGegenstandssplitter
|
||||
PARTICLE_TYPE_TRAIL = §fSpur
|
||||
PARTICLE_TYPE_FLAME = §fFlamme
|
||||
PARTICLE_TYPE_SMALL_FLAME = §fKleine Flamme
|
||||
PARTICLE_TYPE_SOUL_FIRE_FLAME = §fSeelenfeuerflamme
|
||||
PARTICLE_TYPE_SOUL = §fSeele
|
||||
PARTICLE_TYPE_LAVA = §fLavafunken
|
||||
PARTICLE_TYPE_SMOKE = §fRauch
|
||||
PARTICLE_TYPE_LARGE_SMOKE = §fGroßer Rauch
|
||||
PARTICLE_TYPE_WHITE_SMOKE = §fWeißer Rauch
|
||||
PARTICLE_TYPE_CAMPFIRE_COSY_SMOKE = §fLagerfeuerrauch
|
||||
PARTICLE_TYPE_CAMPFIRE_SIGNAL_SMOKE = §fSignalrauch
|
||||
PARTICLE_TYPE_ASH = §fAsche
|
||||
PARTICLE_TYPE_WHITE_ASH = §fWeiße Asche
|
||||
PARTICLE_TYPE_POOF = §fPuff
|
||||
PARTICLE_TYPE_CLOUD = §fWolke
|
||||
PARTICLE_TYPE_SNEEZE = §fNiesen
|
||||
PARTICLE_TYPE_SPIT = §fSpucke
|
||||
PARTICLE_TYPE_EXPLOSION = §fExplosion
|
||||
PARTICLE_TYPE_SONIC_BOOM = §fSchallwelle
|
||||
PARTICLE_TYPE_SWEEP_ATTACK = §fSchwungangriff
|
||||
PARTICLE_TYPE_GUST = §fWindstoß
|
||||
PARTICLE_TYPE_SMALL_GUST = §fKleiner Windstoß
|
||||
PARTICLE_TYPE_DRIPPING_WATER = §fTropfendes Wasser
|
||||
PARTICLE_TYPE_FALLING_WATER = §fFallendes Wasser
|
||||
PARTICLE_TYPE_DRIPPING_LAVA = §fTropfende Lava
|
||||
PARTICLE_TYPE_FALLING_LAVA = §fFallende Lava
|
||||
PARTICLE_TYPE_LANDING_LAVA = §fLandende Lava
|
||||
PARTICLE_TYPE_DRIPPING_HONEY = §fTropfender Honig
|
||||
PARTICLE_TYPE_FALLING_HONEY = §fFallender Honig
|
||||
PARTICLE_TYPE_LANDING_HONEY = §fLandender Honig
|
||||
PARTICLE_TYPE_FALLING_NECTAR = §fFallender Nektar
|
||||
PARTICLE_TYPE_DRIPPING_OBSIDIAN_TEAR = §fTropfende Obsidianträne
|
||||
PARTICLE_TYPE_FALLING_OBSIDIAN_TEAR = §fFallende Obsidianträne
|
||||
PARTICLE_TYPE_LANDING_OBSIDIAN_TEAR = §fLandende Obsidianträne
|
||||
PARTICLE_TYPE_DRIPPING_DRIPSTONE_WATER = §fTropfsteinwasser
|
||||
PARTICLE_TYPE_FALLING_DRIPSTONE_WATER = §fFallendes Tropfsteinwasser
|
||||
PARTICLE_TYPE_DRIPPING_DRIPSTONE_LAVA = §fTropfsteinlava
|
||||
PARTICLE_TYPE_FALLING_DRIPSTONE_LAVA = §fFallende Tropfsteinlava
|
||||
PARTICLE_TYPE_RAIN = §fRegen
|
||||
PARTICLE_TYPE_SPLASH = §fSpritzer
|
||||
PARTICLE_TYPE_FISHING = §fAngelspur
|
||||
PARTICLE_TYPE_UNDERWATER = §fUnterwasser
|
||||
PARTICLE_TYPE_BUBBLE_POP = §fPlatzende Blase
|
||||
PARTICLE_TYPE_DOLPHIN = §fDelfinspur
|
||||
PARTICLE_TYPE_NAUTILUS = §fNautilus
|
||||
PARTICLE_TYPE_SQUID_INK = §fTintenfischtinte
|
||||
PARTICLE_TYPE_GLOW_SQUID_INK = §fLeuchttintenfischtinte
|
||||
PARTICLE_TYPE_GLOW = §fLeuchten
|
||||
PARTICLE_TYPE_FALLING_SPORE_BLOSSOM = §fFallende Sporenblüte
|
||||
PARTICLE_TYPE_SPORE_BLOSSOM_AIR = §fSchwebende Sporen
|
||||
PARTICLE_TYPE_CHERRY_LEAVES = §fKirschblätter
|
||||
PARTICLE_TYPE_PALE_OAK_LEAVES = §fBlasseichenblätter
|
||||
PARTICLE_TYPE_CRIMSON_SPORE = §fKarmesinsporen
|
||||
PARTICLE_TYPE_WARPED_SPORE = §fWirrsporen
|
||||
PARTICLE_TYPE_MYCELIUM = §fMyzel
|
||||
PARTICLE_TYPE_COMPOSTER = §fKomposter
|
||||
PARTICLE_TYPE_FIREFLY = §fGlühwürmchen
|
||||
PARTICLE_TYPE_SNOWFLAKE = §fSchneeflocke
|
||||
PARTICLE_TYPE_ITEM_SNOWBALL = §fSchneeball
|
||||
PARTICLE_TYPE_ITEM_SLIME = §fSchleim
|
||||
PARTICLE_TYPE_ITEM_COBWEB = §fSpinnennetz
|
||||
PARTICLE_TYPE_EGG_CRACK = §fEierschale
|
||||
PARTICLE_TYPE_DUST_PLUME = §fStaubwolke
|
||||
PARTICLE_TYPE_INFESTED = §fBefallen
|
||||
PARTICLE_TYPE_HEART = §fHerz
|
||||
PARTICLE_TYPE_NOTE = §fNote
|
||||
PARTICLE_TYPE_HAPPY_VILLAGER = §fFröhlicher Dorfbewohner
|
||||
PARTICLE_TYPE_ANGRY_VILLAGER = §fWütender Dorfbewohner
|
||||
PARTICLE_TYPE_DAMAGE_INDICATOR = §fSchaden
|
||||
PARTICLE_TYPE_CRIT = §fVolltreffer
|
||||
PARTICLE_TYPE_ENCHANTED_HIT = §fVerzauberter Treffer
|
||||
PARTICLE_TYPE_ENCHANT = §fZauberzeichen
|
||||
PARTICLE_TYPE_EFFECT = §fTrankeffekt
|
||||
PARTICLE_TYPE_INSTANT_EFFECT = §fSoforteffekt
|
||||
PARTICLE_TYPE_WITCH = §fHexenmagie
|
||||
PARTICLE_TYPE_END_ROD = §fEndstab
|
||||
PARTICLE_TYPE_PORTAL = §fPortal
|
||||
PARTICLE_TYPE_REVERSE_PORTAL = §fUmgekehrtes Portal
|
||||
PARTICLE_TYPE_DRAGON_BREATH = §fDrachenatem
|
||||
PARTICLE_TYPE_FIREWORK = §fFeuerwerksfunke
|
||||
PARTICLE_TYPE_TOTEM_OF_UNDYING = §fTotem
|
||||
PARTICLE_TYPE_ELECTRIC_SPARK = §fElektrischer Funke
|
||||
PARTICLE_TYPE_SCRAPE = §fAbkratzen
|
||||
PARTICLE_TYPE_WAX_ON = §fWachsen
|
||||
PARTICLE_TYPE_WAX_OFF = §fEntwachsen
|
||||
PARTICLE_TYPE_SCULK_SOUL = §fSculk-Seele
|
||||
PARTICLE_TYPE_SCULK_CHARGE = §fSculk-Ladung
|
||||
PARTICLE_TYPE_SCULK_CHARGE_POP = §fSculk-Entladung
|
||||
PARTICLE_TYPE_SHRIEK = §fKreischen
|
||||
PARTICLE_TYPE_RAID_OMEN = §fÜberfall-Omen
|
||||
PARTICLE_TYPE_TRIAL_OMEN = §fPrüfungs-Omen
|
||||
PARTICLE_TYPE_OMINOUS_SPAWNING = §fUnheilvolles Erscheinen
|
||||
PARTICLE_TYPE_TRIAL_SPAWNER_DETECTION = §fPrüfungsspawner-Flamme
|
||||
PARTICLE_TYPE_TRIAL_SPAWNER_DETECTION_OMINOUS = §fUnheilvolle Prüfungsspawner-Flamme
|
||||
PARTICLE_TYPE_VAULT_CONNECTION = §fTresorverbindung
|
||||
|
||||
# Particle blocks
|
||||
PARTICLE_CHOICE_BLOCK_STONE = §fStein
|
||||
PARTICLE_CHOICE_BLOCK_COBBLESTONE = §fBruchstein
|
||||
PARTICLE_CHOICE_BLOCK_DIRT = §fErde
|
||||
PARTICLE_CHOICE_BLOCK_GRASS_BLOCK = §fGrasblock
|
||||
PARTICLE_CHOICE_BLOCK_SAND = §fSand
|
||||
PARTICLE_CHOICE_BLOCK_RED_SAND = §fRoter Sand
|
||||
PARTICLE_CHOICE_BLOCK_GRAVEL = §fKies
|
||||
PARTICLE_CHOICE_BLOCK_OAK_PLANKS = §fEichenholzbretter
|
||||
PARTICLE_CHOICE_BLOCK_OAK_LEAVES = §fEichenlaub
|
||||
PARTICLE_CHOICE_BLOCK_CHERRY_LEAVES = §fKirschlaub
|
||||
PARTICLE_CHOICE_BLOCK_SNOW_BLOCK = §fSchneeblock
|
||||
PARTICLE_CHOICE_BLOCK_ICE = §fEis
|
||||
PARTICLE_CHOICE_BLOCK_GLASS = §fGlas
|
||||
PARTICLE_CHOICE_BLOCK_TNT = §fTNT
|
||||
PARTICLE_CHOICE_BLOCK_PUMPKIN = §fKürbis
|
||||
PARTICLE_CHOICE_BLOCK_MELON = §fMelone
|
||||
PARTICLE_CHOICE_BLOCK_HONEY_BLOCK = §fHonigblock
|
||||
PARTICLE_CHOICE_BLOCK_SLIME_BLOCK = §fSchleimblock
|
||||
PARTICLE_CHOICE_BLOCK_NETHERRACK = §fNetherrack
|
||||
PARTICLE_CHOICE_BLOCK_SOUL_SAND = §fSeelensand
|
||||
PARTICLE_CHOICE_BLOCK_MAGMA_BLOCK = §fMagmablock
|
||||
PARTICLE_CHOICE_BLOCK_END_STONE = §fEndstein
|
||||
PARTICLE_CHOICE_BLOCK_OBSIDIAN = §fObsidian
|
||||
PARTICLE_CHOICE_BLOCK_PRISMARINE = §fPrismarin
|
||||
PARTICLE_CHOICE_BLOCK_SCULK = §fSculk
|
||||
PARTICLE_CHOICE_BLOCK_AMETHYST_BLOCK = §fAmethystblock
|
||||
PARTICLE_CHOICE_BLOCK_COAL_BLOCK = §fKohleblock
|
||||
PARTICLE_CHOICE_BLOCK_IRON_BLOCK = §fEisenblock
|
||||
PARTICLE_CHOICE_BLOCK_GOLD_BLOCK = §fGoldblock
|
||||
PARTICLE_CHOICE_BLOCK_REDSTONE_BLOCK = §fRedstone-Block
|
||||
PARTICLE_CHOICE_BLOCK_LAPIS_BLOCK = §fLapislazuliblock
|
||||
PARTICLE_CHOICE_BLOCK_EMERALD_BLOCK = §fSmaragdblock
|
||||
PARTICLE_CHOICE_BLOCK_DIAMOND_BLOCK = §fDiamantblock
|
||||
PARTICLE_CHOICE_BLOCK_WHITE_WOOL = §fWeiße Wolle
|
||||
PARTICLE_CHOICE_BLOCK_RED_WOOL = §fRote Wolle
|
||||
PARTICLE_CHOICE_BLOCK_ORANGE_WOOL = §fOrange Wolle
|
||||
PARTICLE_CHOICE_BLOCK_YELLOW_WOOL = §fGelbe Wolle
|
||||
PARTICLE_CHOICE_BLOCK_LIME_WOOL = §fHellgrüne Wolle
|
||||
PARTICLE_CHOICE_BLOCK_LIGHT_BLUE_WOOL = §fHellblaue Wolle
|
||||
PARTICLE_CHOICE_BLOCK_BLUE_WOOL = §fBlaue Wolle
|
||||
PARTICLE_CHOICE_BLOCK_PURPLE_WOOL = §fViolette Wolle
|
||||
PARTICLE_CHOICE_BLOCK_PINK_WOOL = §fRosa Wolle
|
||||
PARTICLE_CHOICE_BLOCK_BLACK_WOOL = §fSchwarze Wolle
|
||||
|
||||
PARTICLE_PLAYER_1063 = §7Kreisende Kreise
|
||||
PARTICLE_PLAYER_10697_AURA = §7Rauchgranate
|
||||
|
||||
PARTICLE_TEAM_PULSE_HEART_BEAT = §cHerzschlag
|
||||
|
||||
PARTICLE_RAINCLOUD_NORMAL = §fRegenwolke
|
||||
PARTICLE_RAINCLOUD_RED = §fLavawolke
|
||||
PARTICLE_RAINCLOUD_YELLOW = §fNektarwolke
|
||||
PARTICLE_RAINCLOUD_ORANGE = §fHonigwolke
|
||||
PARTICLE_RAINCLOUD_GREEN = §fSporenwolke
|
||||
PARTICLE_RAINCLOUD_PURPLE = §fObsidanwolke
|
||||
PARTICLE_RAINCLOUD_NAUTILUS = §fNautiluswolke
|
||||
PARTICLE_RAINCLOUD_ENCHANTMENT = §fVerzauberungswolke
|
||||
PARTICLE_RAINCLOUD_SLIME = §fSchleimwolke
|
||||
PARTICLE_RAINCLOUD_HAIL = §fHagelwolke
|
||||
PARTICLE_RAINCLOUD_SNOW = §fSchneewolke
|
||||
PARTICLE_RAINCLOUD_TOTEM = §fTotemwolke
|
||||
|
||||
PARTICLE_EVENT_ENCHANTING = §cVerzaubert
|
||||
PARTICLE_EVENT_CLOUD = §fWolken
|
||||
PARTICLE_EVENT_SMOKE = §7Rauch
|
||||
PARTICLE_EVENT_WATER = §bWasser
|
||||
PARTICLE_EVENT_WINGS = §fFlügel
|
||||
PARTICLE_EVENT_RAIN_CLOUD = §fRegenwolke
|
||||
PARTICLE_EVENT_WGS = §fWGS
|
||||
PARTICLE_EVENT_WARGEARCLASH = §fClash
|
||||
|
||||
PARTICLE_EGG_HUNT_EASY = §aGrünplfanzen
|
||||
PARTICLE_EGG_HUNT_MEDIUM = §eVersteckspiel
|
||||
PARTICLE_EGG_HUNT_HARD = §cOrbit
|
||||
PARTICLE_EGG_HUNT_EXTREME = §5Lila regen
|
||||
PARTICLE_EGG_HUNT_ADVANCED = §5Abheben
|
||||
PARTICLE_EGG_HUNT_HALF = §fDie Jagd ist eröffnet
|
||||
|
||||
JUMP_AND_RUN_FINISHED = §aBeendet in {0} mit {1} Fails
|
||||
JUMP_AND_RUN_PERSONAL_BEST = §aNice! Du hast deinen Rekord um {0} verbessert!
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2026 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.lobby.particle;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.entity.Pose;
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
// The player's body as a coordinate system: origin at the feet, up towards the head, forward out of the chest.
|
||||
// Standing it turns with the body (as BodyMotion tracks it); gliding or swimming the body lies along the look direction.
|
||||
@Getter
|
||||
public class BodyFrame {
|
||||
|
||||
private static final Vector WORLD_UP = new Vector(0, 1, 0);
|
||||
private static final double HALF_BODY = 0.9;
|
||||
|
||||
private final Location origin;
|
||||
private final Vector right;
|
||||
private final Vector up;
|
||||
private final Vector forward;
|
||||
// Reference directions of rings around the body; they do not turn with the body
|
||||
private final Vector ringStart;
|
||||
private final Vector ringSide;
|
||||
|
||||
private BodyFrame(Location origin, Vector right, Vector up, Vector forward) {
|
||||
this.origin = origin;
|
||||
this.right = right;
|
||||
this.up = up;
|
||||
this.forward = forward;
|
||||
|
||||
// World east projected onto the plane around the body, so a standing ring matches the world ring
|
||||
Vector start = new Vector(1, 0, 0).subtract(up.clone().multiply(up.getX()));
|
||||
if (start.lengthSquared() < 1e-6) {
|
||||
start = new Vector(0, 0, 1).subtract(up.clone().multiply(up.getZ()));
|
||||
}
|
||||
this.ringStart = start.normalize();
|
||||
this.ringSide = up.clone().crossProduct(ringStart);
|
||||
}
|
||||
|
||||
public static BodyFrame of(Player player) {
|
||||
Location location = player.getLocation();
|
||||
Pose pose = player.getPose();
|
||||
if (pose == Pose.FALL_FLYING || pose == Pose.SWIMMING) {
|
||||
Vector up = location.getDirection().normalize();
|
||||
Vector right = up.clone().crossProduct(WORLD_UP);
|
||||
if (right.lengthSquared() < 1e-6) {
|
||||
right = horizontal(BodyMotion.yaw(player)).crossProduct(WORLD_UP);
|
||||
}
|
||||
right.normalize();
|
||||
Vector forward = up.clone().crossProduct(right).normalize();
|
||||
// The lying body is centred in its short hitbox, its feet lie behind the centre
|
||||
Location origin = location.clone().add(0, player.getHeight() / 2, 0).subtract(up.clone().multiply(HALF_BODY));
|
||||
return new BodyFrame(origin, right, up, forward);
|
||||
}
|
||||
|
||||
Vector forward = horizontal(BodyMotion.yaw(player));
|
||||
Vector right = forward.clone().crossProduct(WORLD_UP).normalize();
|
||||
return new BodyFrame(location, right, WORLD_UP.clone(), forward);
|
||||
}
|
||||
|
||||
private static Vector horizontal(float yaw) {
|
||||
double radians = Math.toRadians(yaw);
|
||||
return new Vector(-Math.sin(radians), 0, Math.cos(radians));
|
||||
}
|
||||
|
||||
public Vector local(double right, double up, double forward) {
|
||||
return this.right.clone().multiply(right)
|
||||
.add(this.up.clone().multiply(up))
|
||||
.add(this.forward.clone().multiply(forward));
|
||||
}
|
||||
|
||||
// A point on a ring around the up axis; angle 0 and the turning direction match the world rings
|
||||
public Vector ring(double distance, double angle) {
|
||||
return ringStart.clone().multiply(Math.cos(angle) * distance).add(ringSide.clone().multiply(Math.sin(angle) * distance));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2026 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.lobby.particle;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
// How a player's body moves: the direction it faces, worked out the way the Minecraft client turns the body, because
|
||||
// the server's own body rotation for players does not follow what other players see, and how fast it moves.
|
||||
// Walking turns the body towards the walking direction; standing still it only turns once the head looks more than
|
||||
// 50 degrees to the side. It also notices jumps, landings and starting to sneak, which reward touches react to.
|
||||
@UtilityClass
|
||||
public class BodyMotion {
|
||||
|
||||
private final float MAX_HEAD_TURN = 50;
|
||||
private final double MOVING = 0.0025;
|
||||
|
||||
private final Map<Player, State> states = new WeakHashMap<>();
|
||||
// Counts server ticks, so events can be matched to the tick they happened in
|
||||
private long tick;
|
||||
|
||||
private static class State {
|
||||
private float body;
|
||||
private double x;
|
||||
private double y;
|
||||
private double z;
|
||||
// Blocks per tick, smoothed so single steps do not jerk the wings
|
||||
private double speed;
|
||||
private boolean onGround = true;
|
||||
private boolean sneaking;
|
||||
// Highest point of the current time in the air
|
||||
private double peakY;
|
||||
private long jumpTick = Long.MIN_VALUE;
|
||||
private long landTick = Long.MIN_VALUE;
|
||||
private double landFall;
|
||||
private long sneakTick = Long.MIN_VALUE;
|
||||
}
|
||||
|
||||
// Called once per server tick before the players are updated
|
||||
public void nextTick() {
|
||||
tick++;
|
||||
}
|
||||
|
||||
// Called once per server tick for every online player
|
||||
@SuppressWarnings("deprecation")
|
||||
public void tick(Player player) {
|
||||
Location location = player.getLocation();
|
||||
State state = states.get(player);
|
||||
if (state == null) {
|
||||
state = new State();
|
||||
state.body = location.getYaw();
|
||||
state.x = location.getX();
|
||||
state.y = location.getY();
|
||||
state.z = location.getZ();
|
||||
states.put(player, state);
|
||||
return;
|
||||
}
|
||||
|
||||
float head = location.getYaw();
|
||||
double dx = location.getX() - state.x;
|
||||
double dy = location.getY() - state.y;
|
||||
double dz = location.getZ() - state.z;
|
||||
state.x = location.getX();
|
||||
state.y = location.getY();
|
||||
state.z = location.getZ();
|
||||
state.speed = state.speed * 0.8 + Math.sqrt(dx * dx + dy * dy + dz * dz) * 0.2;
|
||||
|
||||
boolean onGround = player.isOnGround();
|
||||
if (state.onGround && !onGround && dy > 0) {
|
||||
state.jumpTick = tick;
|
||||
}
|
||||
if (!onGround) {
|
||||
state.peakY = state.onGround ? location.getY() : Math.max(state.peakY, location.getY());
|
||||
} else if (!state.onGround) {
|
||||
state.landTick = tick;
|
||||
state.landFall = state.peakY - location.getY();
|
||||
}
|
||||
state.onGround = onGround;
|
||||
if (player.isSneaking() && !state.sneaking) {
|
||||
state.sneakTick = tick;
|
||||
}
|
||||
state.sneaking = player.isSneaking();
|
||||
|
||||
float target = state.body;
|
||||
if (dx * dx + dz * dz > MOVING) {
|
||||
target = (float) Math.toDegrees(Math.atan2(dz, dx)) - 90;
|
||||
// Walking backwards keeps the body facing forwards
|
||||
float away = Math.abs(wrap(head) - target);
|
||||
if (away > 95 && away < 265) {
|
||||
target -= 180;
|
||||
}
|
||||
}
|
||||
state.body += wrap(target - state.body) * 0.3f;
|
||||
float headTurn = wrap(head - state.body);
|
||||
if (Math.abs(headTurn) > MAX_HEAD_TURN) {
|
||||
state.body += headTurn - Math.signum(headTurn) * MAX_HEAD_TURN;
|
||||
}
|
||||
}
|
||||
|
||||
// The tracked body direction in degrees, or the head direction for players not tracked yet
|
||||
public float yaw(Player player) {
|
||||
State state = states.get(player);
|
||||
return state == null ? player.getLocation().getYaw() : state.body;
|
||||
}
|
||||
|
||||
// Smoothed speed in blocks per tick; sprinting is about 0.28, fast elytra flight well above 1
|
||||
public double speed(Player player) {
|
||||
State state = states.get(player);
|
||||
return state == null ? 0 : state.speed;
|
||||
}
|
||||
|
||||
public long currentTick() {
|
||||
return tick;
|
||||
}
|
||||
|
||||
public boolean jumped(Player player) {
|
||||
State state = states.get(player);
|
||||
return state != null && state.jumpTick == tick;
|
||||
}
|
||||
|
||||
// Ticks since the last jump, or a large number if there was none
|
||||
public long ticksSinceJump(Player player) {
|
||||
State state = states.get(player);
|
||||
return state == null || state.jumpTick == Long.MIN_VALUE ? Long.MAX_VALUE : tick - state.jumpTick;
|
||||
}
|
||||
|
||||
// How far the player fell if they landed this tick, otherwise -1
|
||||
public double landed(Player player) {
|
||||
State state = states.get(player);
|
||||
return state != null && state.landTick == tick ? state.landFall : -1;
|
||||
}
|
||||
|
||||
public boolean startedSneaking(Player player) {
|
||||
State state = states.get(player);
|
||||
return state != null && state.sneakTick == tick;
|
||||
}
|
||||
|
||||
private float wrap(float degrees) {
|
||||
float wrapped = degrees % 360;
|
||||
if (wrapped >= 180) wrapped -= 360;
|
||||
if (wrapped < -180) wrapped += 360;
|
||||
return wrapped;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2026 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.lobby.particle;
|
||||
|
||||
import de.steamwar.lobby.particle.slot.ParticleEntry;
|
||||
import de.steamwar.lobby.particle.slot.ParticleSet;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class CompiledSet {
|
||||
|
||||
private final List<ParticleElement> always = new ArrayList<>();
|
||||
private final List<ParticleElement> move = new ArrayList<>();
|
||||
|
||||
public static CompiledSet of(ParticleElement... particleElements) {
|
||||
CompiledSet compiledSet = new CompiledSet();
|
||||
for (ParticleElement particleElement : particleElements) {
|
||||
compiledSet.add(particleElement);
|
||||
}
|
||||
return compiledSet;
|
||||
}
|
||||
|
||||
// Entries whose design or particle type is not unlocked are skipped, not removed
|
||||
public static CompiledSet of(ParticleSet particleSet, UnlockContext context) {
|
||||
CompiledSet compiledSet = new CompiledSet();
|
||||
for (ParticleEntry entry : particleSet.getEntries()) {
|
||||
if (entry.isUnlocked(context)) {
|
||||
compiledSet.add(entry.build());
|
||||
}
|
||||
}
|
||||
return compiledSet;
|
||||
}
|
||||
|
||||
public void add(ParticleElement particleElement) {
|
||||
if (particleElement.tickType() == ParticleTickType.ALWAYS) {
|
||||
always.add(particleElement);
|
||||
} else {
|
||||
move.add(particleElement);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasAlways() {
|
||||
return !always.isEmpty();
|
||||
}
|
||||
|
||||
public boolean hasMove() {
|
||||
return !move.isEmpty();
|
||||
}
|
||||
|
||||
public void tickAlways(ParticleTickData particleTickData) {
|
||||
always.forEach(particleElement -> particleElement.tick(particleTickData));
|
||||
}
|
||||
|
||||
public void tickMove(ParticleTickData particleTickData) {
|
||||
move.forEach(particleElement -> particleElement.tick(particleTickData));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2026 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.lobby.particle;
|
||||
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
// Wing shapes drawn from lines and curves instead of images, so their number of points can be chosen.
|
||||
// Coordinates use the same units as the wing images: x is sideways from the spine, y upwards, about 11 by 13.
|
||||
public enum DrawnWings {
|
||||
|
||||
// An arm bone along the top, fingers spreading from the wrist and skin hanging between them
|
||||
BAT {
|
||||
@Override
|
||||
List<List<Vector>> rightWing() {
|
||||
List<List<Vector>> lines = new ArrayList<>();
|
||||
lines.add(curve(p(0.5, 7), p(3, 9.5), p(6, 11), p(11, 12.5)));
|
||||
lines.add(curve(p(6, 11), p(7, 4)));
|
||||
lines.add(curve(p(6, 11), p(9, 6)));
|
||||
lines.add(curve(p(6, 11), p(10.8, 9)));
|
||||
lines.add(curve(p(0.5, 5), p(3.5, 4.6), p(5.5, 3.4), p(7, 4), p(7.6, 5.4), p(9, 6), p(9.6, 7.6), p(10.8, 9), p(11.2, 10.8), p(11, 12.5)));
|
||||
return lines;
|
||||
}
|
||||
},
|
||||
// The butterfly curve r = e^cos(t) - 2 cos(4t), which already holds both wings; its small loops around the body are left out
|
||||
BUTTERFLY {
|
||||
@Override
|
||||
List<List<Vector>> rightWing() {
|
||||
List<List<Vector>> lines = new ArrayList<>();
|
||||
List<Vector> current = new ArrayList<>();
|
||||
for (int i = 0; i <= 400; i++) {
|
||||
double t = Math.PI * i / 400;
|
||||
double r = Math.exp(Math.cos(t)) - 2 * Math.cos(4 * t);
|
||||
if (Math.abs(r) < 0.9) {
|
||||
if (current.size() > 1) lines.add(current);
|
||||
current = new ArrayList<>();
|
||||
continue;
|
||||
}
|
||||
current.add(p(Math.sin(t) * r * 3, Math.cos(t) * r * 2 + 6));
|
||||
}
|
||||
if (current.size() > 1) lines.add(current);
|
||||
return lines;
|
||||
}
|
||||
},
|
||||
// Two thin rounded pairs, a long one above and a short one below
|
||||
FAIRY {
|
||||
@Override
|
||||
List<List<Vector>> rightWing() {
|
||||
return List.of(ellipse(5.2, 9.3, 5, 1.9, 35), ellipse(3.8, 4.6, 3.6, 1.5, -30));
|
||||
}
|
||||
},
|
||||
;
|
||||
|
||||
// The right wing as lines; the left wing mirrors it
|
||||
abstract List<List<Vector>> rightWing();
|
||||
|
||||
private static final Map<String, WingDesign> CACHE = new HashMap<>();
|
||||
// The shapes are drawn with the shoulder joint at y = 7; moving it to 3.3 puts it at the shoulder blades
|
||||
// (about 1.3 blocks above the feet at the default size) instead of at head height
|
||||
private static final double SHOULDER_SHIFT = 7 - 3.3;
|
||||
|
||||
// Both wings with the given number of points per wing, spread evenly along the lines
|
||||
public WingDesign withDetail(int pointsPerWing) {
|
||||
return CACHE.computeIfAbsent(name() + "@" + pointsPerWing, key -> {
|
||||
List<Vector> right = sample(rightWing(), pointsPerWing);
|
||||
right.forEach(point -> point.setY(point.getY() - SHOULDER_SHIFT));
|
||||
List<Vector> both = new ArrayList<>(right);
|
||||
right.forEach(point -> both.add(new Vector(-point.getX(), point.getY(), 0)));
|
||||
return new WingDesign.WingDesignImpl(both.toArray(new Vector[0]));
|
||||
});
|
||||
}
|
||||
|
||||
private static List<Vector> sample(List<List<Vector>> lines, int count) {
|
||||
double total = lines.stream().mapToDouble(DrawnWings::length).sum();
|
||||
List<Vector> points = new ArrayList<>();
|
||||
for (int i = 0; i < count; i++) {
|
||||
double distance = total * (i + 0.5) / count;
|
||||
for (List<Vector> line : lines) {
|
||||
double length = length(line);
|
||||
if (distance <= length) {
|
||||
points.add(at(line, distance));
|
||||
break;
|
||||
}
|
||||
distance -= length;
|
||||
}
|
||||
}
|
||||
return points;
|
||||
}
|
||||
|
||||
private static double length(List<Vector> line) {
|
||||
double length = 0;
|
||||
for (int i = 1; i < line.size(); i++) length += line.get(i).distance(line.get(i - 1));
|
||||
return length;
|
||||
}
|
||||
|
||||
private static Vector at(List<Vector> line, double distance) {
|
||||
for (int i = 1; i < line.size(); i++) {
|
||||
double segment = line.get(i).distance(line.get(i - 1));
|
||||
if (distance <= segment) {
|
||||
return line.get(i - 1).clone().add(line.get(i).clone().subtract(line.get(i - 1)).multiply(segment == 0 ? 0 : distance / segment));
|
||||
}
|
||||
distance -= segment;
|
||||
}
|
||||
return line.get(line.size() - 1).clone();
|
||||
}
|
||||
|
||||
private static Vector p(double x, double y) {
|
||||
return new Vector(x, y, 0);
|
||||
}
|
||||
|
||||
// A smooth curve through the given points (Catmull-Rom), as many short segments
|
||||
private static List<Vector> curve(Vector... points) {
|
||||
List<Vector> result = new ArrayList<>();
|
||||
for (int i = 0; i < points.length - 1; i++) {
|
||||
Vector p0 = points[Math.max(0, i - 1)], p1 = points[i], p2 = points[i + 1], p3 = points[Math.min(points.length - 1, i + 2)];
|
||||
for (int step = 0; step < 16; step++) {
|
||||
double t = step / 16.0, t2 = t * t, t3 = t2 * t;
|
||||
result.add(p1.clone().multiply(2)
|
||||
.add(p2.clone().subtract(p0).multiply(t))
|
||||
.add(p0.clone().multiply(2).subtract(p1.clone().multiply(5)).add(p2.clone().multiply(4)).subtract(p3).multiply(t2))
|
||||
.add(p1.clone().multiply(3).subtract(p0).subtract(p2.clone().multiply(3)).add(p3).multiply(t3))
|
||||
.multiply(0.5));
|
||||
}
|
||||
}
|
||||
result.add(points[points.length - 1].clone());
|
||||
return result;
|
||||
}
|
||||
|
||||
private static List<Vector> ellipse(double centerX, double centerY, double radiusX, double radiusY, double tiltDegrees) {
|
||||
double tilt = Math.toRadians(tiltDegrees);
|
||||
List<Vector> result = new ArrayList<>();
|
||||
for (int i = 0; i <= 64; i++) {
|
||||
double a = 2 * Math.PI * i / 64;
|
||||
double x = Math.cos(a) * radiusX, y = Math.sin(a) * radiusY;
|
||||
result.add(p(centerX + x * Math.cos(tilt) - y * Math.sin(tilt), centerY + x * Math.sin(tilt) + y * Math.cos(tilt)));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2025 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.lobby.particle;
|
||||
|
||||
import de.steamwar.inventory.SWItem;
|
||||
import de.steamwar.lobby.LobbySystem;
|
||||
import de.steamwar.sql.SteamwarUser;
|
||||
import lombok.Getter;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class ParticleData {
|
||||
|
||||
private final Material material;
|
||||
private final String name;
|
||||
private final List<String> attributes = new ArrayList<>();
|
||||
|
||||
@Getter
|
||||
private final ParticleRequirement requirement;
|
||||
|
||||
@Getter
|
||||
private final ParticleElement particleElement;
|
||||
|
||||
public ParticleData(Material material, String name, ParticleElement particleElement) {
|
||||
this.material = material;
|
||||
this.name = name;
|
||||
this.requirement = ParticleRequirement.NO_REQUIRMENT;
|
||||
this.particleElement = particleElement;
|
||||
particleElement.aggregateAttributes(this);
|
||||
}
|
||||
|
||||
public ParticleData(Material material, String name, ParticleRequirement requirement, ParticleElement particleElement) {
|
||||
this.material = material;
|
||||
this.name = name;
|
||||
this.requirement = requirement;
|
||||
this.particleElement = particleElement;
|
||||
particleElement.aggregateAttributes(this);
|
||||
}
|
||||
|
||||
public ParticleData add(String attribute) {
|
||||
attributes.add(attribute);
|
||||
return this;
|
||||
}
|
||||
|
||||
public SWItem toSWItem(Player player, SteamwarUser user, Set<Integer> eventTeilnahme, String eggHuntConfig) {
|
||||
String translatedName;
|
||||
try {
|
||||
translatedName = LobbySystem.getMessage().parse(name, player);
|
||||
} catch (Exception e) {
|
||||
translatedName = name;
|
||||
}
|
||||
|
||||
List<String> lore = new ArrayList<>();
|
||||
lore.add("");
|
||||
if (!attributes.isEmpty()) {
|
||||
lore.add(LobbySystem.getMessage().parse("PARTICLE_ATTRIBUTE", player));
|
||||
attributes.forEach(attribute -> lore.add(LobbySystem.getMessage().parse(attribute, player)));
|
||||
lore.add("");
|
||||
}
|
||||
|
||||
String unlockedBy = requirement.getRequirementName(player);
|
||||
if (unlockedBy != null) {
|
||||
lore.add(LobbySystem.getMessage().parse("PARTICLE_UNLOCKED_BY", player));
|
||||
lore.add(unlockedBy);
|
||||
lore.add("");
|
||||
}
|
||||
if (requirement.test(user, eventTeilnahme, eggHuntConfig)) {
|
||||
lore.add(LobbySystem.getMessage().parse("PARTICLE_SELECT", player));
|
||||
} else {
|
||||
lore.add(LobbySystem.getMessage().parse("PARTICLE_LOCKED", player));
|
||||
}
|
||||
return new SWItem(material, translatedName, lore, false, clickType -> {
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -19,9 +19,7 @@
|
||||
|
||||
package de.steamwar.lobby.particle;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Color;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Particle;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
@@ -47,23 +45,18 @@ public interface ParticleElement {
|
||||
return new Particle.DustOptions(Color.fromRGB(r, g, b), (float) (RANDOM.nextFloat() / 2 + 0.5));
|
||||
}
|
||||
|
||||
default void display(Location location, Player root, boolean onlySelf, boolean onlyOther, Consumer<Player> consumer) {
|
||||
if (onlySelf) {
|
||||
default void display(ParticleTickData particleTickData, Consumer<Player> consumer) {
|
||||
Player root = particleTickData.getPlayer();
|
||||
if (particleTickData.isOnlySelf()) {
|
||||
consumer.accept(root);
|
||||
return;
|
||||
}
|
||||
Bukkit.getOnlinePlayers().forEach(player -> {
|
||||
if (onlyOther && player == root) {
|
||||
return;
|
||||
for (Player player : particleTickData.getViewers()) {
|
||||
if (particleTickData.isOnlyOthers() && player == root) {
|
||||
continue;
|
||||
}
|
||||
int viewDistance = player.getClientViewDistance() * 16;
|
||||
if (location.distanceSquared(player.getLocation()) <= viewDistance * viewDistance) {
|
||||
consumer.accept(player);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
default void aggregateAttributes(ParticleData particleData) {
|
||||
consumer.accept(player);
|
||||
}
|
||||
}
|
||||
|
||||
default ParticleTickType tickType() {
|
||||
|
||||
@@ -1,145 +0,0 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2025 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.lobby.particle;
|
||||
|
||||
import de.steamwar.inventory.SWItem;
|
||||
import de.steamwar.inventory.SWListInv;
|
||||
import de.steamwar.lobby.LobbySystem;
|
||||
import de.steamwar.lobby.particle.particles.*;
|
||||
import de.steamwar.lobby.particle.particles.custom.CustomEasterParticle;
|
||||
import de.steamwar.lobby.particle.particles.custom.CustomPlayerParticle;
|
||||
import de.steamwar.lobby.particle.particles.custom.CustomTeamParticle;
|
||||
import de.steamwar.lobby.special.easter.EggHunt;
|
||||
import de.steamwar.lobby.util.LobbyPlayer;
|
||||
import de.steamwar.sql.Event;
|
||||
import de.steamwar.sql.SteamwarUser;
|
||||
import de.steamwar.sql.TeamTeilnahme;
|
||||
import de.steamwar.sql.UserConfig;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@UtilityClass
|
||||
public class ParticleInventory {
|
||||
|
||||
private final Class<?>[] PARTICLES = {
|
||||
PlayerParticle.class,
|
||||
CustomPlayerParticle.class,
|
||||
TeamParticle.class,
|
||||
CustomTeamParticle.class,
|
||||
RainCloudParticle.class,
|
||||
ServerTeamParticle.class,
|
||||
EventParticle.class,
|
||||
EventParticlePlacement.class,
|
||||
EventParticleParticipation.class,
|
||||
EasterParticle.class,
|
||||
CustomEasterParticle.class,
|
||||
};
|
||||
|
||||
public String convertToString(ParticleEnum particleEnum) {
|
||||
return particleEnum.getClass().getSimpleName() + "@" + ((Enum<?>) particleEnum).name();
|
||||
}
|
||||
|
||||
public ParticleEnum convertFromString(String string) {
|
||||
String[] split = string.split("@");
|
||||
Class<?> clazz = null;
|
||||
for (Class<?> aClass : PARTICLES) {
|
||||
if (aClass.getSimpleName().equals(split[0])) {
|
||||
clazz = aClass;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (clazz != null) {
|
||||
try {
|
||||
return (ParticleEnum) Enum.valueOf((Class<? extends Enum>) clazz, split[1]);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void openInventory(Player player, boolean onlyUnlocked) {
|
||||
LobbyPlayer lobbyPlayer = LobbyPlayer.getLobbyPlayer(player.getUniqueId());
|
||||
|
||||
SteamwarUser user = SteamwarUser.get(player.getUniqueId());
|
||||
Set<Integer> events = user.getTeam() == 0 ? new HashSet<>() : TeamTeilnahme.getEvents(user.getTeam()).stream().map(Event::getEventID).collect(Collectors.toSet());
|
||||
String eggHuntConfig = UserConfig.getConfig(user.getId(), EggHunt.EGG_HUNT_CONFIG_KEY);
|
||||
|
||||
AtomicBoolean hasOneLocked = new AtomicBoolean(false);
|
||||
List<SWListInv.SWListEntry<ParticleEnum>> particleList = new ArrayList<>();
|
||||
for (Class<?> clazz : PARTICLES) {
|
||||
addParticles(particleList, (ParticleEnum[]) clazz.getEnumConstants(), player, onlyUnlocked, hasOneLocked, user, events, eggHuntConfig);
|
||||
}
|
||||
for (int i = 0; i < particleList.size() % 45; i++) {
|
||||
particleList.add(new SWListInv.SWListEntry<>(new SWItem(Material.BLACK_STAINED_GLASS_PANE, " "), null));
|
||||
}
|
||||
|
||||
SWListInv<ParticleEnum> particleSWListInv = new SWListInv<>(player, LobbySystem.getMessage().parse("PARTICLE_INVENTORY", player), false, particleList, (clickType, particle) -> {
|
||||
if (particle == null) return;
|
||||
lobbyPlayer.setParticle(particle);
|
||||
player.closeInventory();
|
||||
if (ParticleListener.disabled(player)) {
|
||||
LobbySystem.getMessage().send("PARTICLE_DEACTIVATED", player);
|
||||
}
|
||||
});
|
||||
particleSWListInv.setItem(48, Material.BARRIER, LobbySystem.getMessage().parse("PARTICLE_DESELECT", player), new ArrayList<>(), false, clickType -> {
|
||||
lobbyPlayer.setParticle(null);
|
||||
});
|
||||
if (!hasOneLocked.get()) {
|
||||
particleSWListInv.setItem(50, Material.GRAY_BANNER, LobbySystem.getMessage().parse("PARTICLE_SHOW_HAS_ALL", player), new ArrayList<>(), false, clickType -> {
|
||||
});
|
||||
} else if (onlyUnlocked) {
|
||||
particleSWListInv.setItem(50, Material.LIME_BANNER, LobbySystem.getMessage().parse("PARTICLE_SHOW_ALL", player), new ArrayList<>(), false, clickType -> {
|
||||
openInventory(player, false);
|
||||
});
|
||||
} else {
|
||||
particleSWListInv.setItem(50, Material.RED_BANNER, LobbySystem.getMessage().parse("PARTICLE_SHOW_UNLOCKED", player), new ArrayList<>(), false, clickType -> {
|
||||
openInventory(player, true);
|
||||
});
|
||||
}
|
||||
particleSWListInv.open();
|
||||
}
|
||||
|
||||
private void addParticles(List<SWListInv.SWListEntry<ParticleEnum>> particleList, ParticleEnum[] particleEnums, Player player, boolean onlyUnlocked, AtomicBoolean hasOneLocked, SteamwarUser user, Set<Integer> events, String eggHuntConfig) {
|
||||
for (ParticleEnum particle : particleEnums) {
|
||||
ParticleData particleData = particle.getParticle();
|
||||
SWItem swItem = particleData.toSWItem(player, user, events, eggHuntConfig);
|
||||
if (particleData.getRequirement().test(user, events, eggHuntConfig)) {
|
||||
particleList.add(new SWListInv.SWListEntry<>(swItem, particle));
|
||||
} else {
|
||||
hasOneLocked.set(true);
|
||||
if (!onlyUnlocked) {
|
||||
particleList.add(new SWListInv.SWListEntry<>(swItem, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < particleList.size() % 9; i++) {
|
||||
particleList.add(new SWListInv.SWListEntry<>(new SWItem(Material.BLACK_STAINED_GLASS_PANE, " "), null));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,20 +23,25 @@ import de.steamwar.linkage.Linked;
|
||||
import de.steamwar.lobby.LobbySystem;
|
||||
import de.steamwar.lobby.jumpandrun.JumpAndRun;
|
||||
import de.steamwar.lobby.listener.PlayerSpawn;
|
||||
import de.steamwar.lobby.particle.gui.ParticleMenu;
|
||||
import de.steamwar.lobby.util.LobbyPlayer;
|
||||
import de.steamwar.sql.SteamwarUser;
|
||||
import de.steamwar.sql.UserPerm;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerInteractEntityEvent;
|
||||
import org.bukkit.event.player.PlayerInteractEvent;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerMoveEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Linked
|
||||
@@ -55,17 +60,14 @@ public class ParticleListener implements Listener {
|
||||
movingPlayers.entrySet().removeIf(entry -> entry.getValue() <= 0);
|
||||
deg += 0.1;
|
||||
if (deg > 360) deg = 0;
|
||||
BodyMotion.nextTick();
|
||||
Bukkit.getOnlinePlayers().forEach(BodyMotion::tick);
|
||||
Bukkit.getOnlinePlayers().forEach(player -> {
|
||||
if (disabled(player)) return;
|
||||
if (JumpAndRun.isPlayerInJumpAndRun(player)) return;
|
||||
LobbyPlayer lobbyPlayer = LobbyPlayer.getLobbyPlayer(player.getUniqueId());
|
||||
ParticleEnum particle = lobbyPlayer.getParticle();
|
||||
if (particle == null) return;
|
||||
ParticleData particleData = particle.getParticle();
|
||||
ParticleElement particleElement = particleData.getParticleElement();
|
||||
if (particleElement.tickType() == ParticleTickType.ALWAYS) {
|
||||
particleElement.tick(new ParticleTickData(player.getWorld(), player, deg, movingPlayers.containsKey(player)));
|
||||
}
|
||||
CompiledSet compiledSet = LobbyPlayer.getLobbyPlayer(player.getUniqueId()).getCompiledSet();
|
||||
if (compiledSet == null || !compiledSet.hasAlways()) return;
|
||||
compiledSet.tickAlways(new ParticleTickData(player.getWorld(), player, deg, movingPlayers.containsKey(player), viewers(player)));
|
||||
});
|
||||
}, 0, 1);
|
||||
}
|
||||
@@ -77,7 +79,7 @@ public class ParticleListener implements Listener {
|
||||
if (!PlayerSpawn.PARTICLE.equals(event.getItem())) return;
|
||||
event.setCancelled(true);
|
||||
|
||||
ParticleInventory.openInventory(player, true);
|
||||
ParticleMenu.open(player);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
@@ -85,20 +87,23 @@ public class ParticleListener implements Listener {
|
||||
Player player = event.getPlayer();
|
||||
if (disabled(player)) return;
|
||||
if (JumpAndRun.isPlayerInJumpAndRun(player)) return;
|
||||
LobbyPlayer lobbyPlayer = LobbyPlayer.getLobbyPlayer(player.getUniqueId());
|
||||
ParticleEnum particle = lobbyPlayer.getParticle();
|
||||
if (particle == null) return;
|
||||
CompiledSet compiledSet = LobbyPlayer.getLobbyPlayer(player.getUniqueId()).getCompiledSet();
|
||||
if (compiledSet == null) return;
|
||||
|
||||
ParticleData particleData = particle.getParticle();
|
||||
ParticleElement particleElement = particleData.getParticleElement();
|
||||
if (particleElement.tickType() == ParticleTickType.MOVE) {
|
||||
particleElement.tick(new ParticleTickData(player.getWorld(), player, deg, true));
|
||||
if (compiledSet.hasMove()) {
|
||||
compiledSet.tickMove(new ParticleTickData(player.getWorld(), player, deg, true, viewers(player)));
|
||||
}
|
||||
if (event.getFrom().getX() != event.getTo().getX() || event.getFrom().getY() != event.getTo().getY() || event.getFrom().getZ() != event.getTo().getZ()) {
|
||||
movingPlayers.put(player, 5);
|
||||
}
|
||||
}
|
||||
|
||||
// Ranks may have changed since the last session, which can lock or unlock the active slot
|
||||
@EventHandler
|
||||
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||
LobbyPlayer.getLobbyPlayer(event.getPlayer()).recompile();
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onPlayerQuit(PlayerQuitEvent event) {
|
||||
movingPlayers.remove(event.getPlayer());
|
||||
@@ -111,6 +116,18 @@ public class ParticleListener implements Listener {
|
||||
}
|
||||
}
|
||||
|
||||
private static List<Player> viewers(Player root) {
|
||||
Location location = root.getLocation();
|
||||
List<Player> viewers = new ArrayList<>();
|
||||
for (Player player : root.getWorld().getPlayers()) {
|
||||
int viewDistance = player.getClientViewDistance() * 16;
|
||||
if (location.distanceSquared(player.getLocation()) <= viewDistance * viewDistance) {
|
||||
viewers.add(player);
|
||||
}
|
||||
}
|
||||
return viewers;
|
||||
}
|
||||
|
||||
public static boolean disabled(Player player) {
|
||||
SteamwarUser user = SteamwarUser.get(player.getUniqueId());
|
||||
if (Bukkit.getOnlinePlayers().size() > SERVER_TEAM_MAX_SIZE) return true;
|
||||
|
||||
@@ -39,7 +39,13 @@ public interface ParticleRequirement {
|
||||
|
||||
String getRequirementName(Player player);
|
||||
|
||||
boolean test(SteamwarUser user, Set<Integer> eventTeilname, String eggHuntConfig);
|
||||
// Whether the player meets this requirement
|
||||
boolean matches(UnlockContext context);
|
||||
|
||||
// Administrators pass every requirement; lore still names the regular requirement
|
||||
default boolean test(UnlockContext context) {
|
||||
return context.getUser().hasPerm(UserPerm.ADMINISTRATION) || matches(context);
|
||||
}
|
||||
|
||||
ParticleRequirement SERVER_TEAM = new ParticleRequirement() {
|
||||
@Override
|
||||
@@ -48,8 +54,8 @@ public interface ParticleRequirement {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(SteamwarUser user, Set<Integer> eventTeilname, String eggHuntConfig) {
|
||||
return user.hasPerm(UserPerm.TEAM);
|
||||
public boolean matches(UnlockContext context) {
|
||||
return context.getUser().hasPerm(UserPerm.TEAM);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -60,21 +66,23 @@ public interface ParticleRequirement {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(SteamwarUser user, Set<Integer> eventTeilname, String eggHuntConfig) {
|
||||
public boolean matches(UnlockContext context) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
ParticleRequirement HAS_TEAM = new ParticleRequirement() {
|
||||
|
||||
// Does not also pass for the server team
|
||||
ParticleRequirement TEAM_MEMBER = new ParticleRequirement() {
|
||||
@Override
|
||||
public String getRequirementName(Player player) {
|
||||
return LobbySystem.getMessage().parse("PARTICLE_UNLOCKED_BY_TEAM", player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(SteamwarUser user, Set<Integer> eventTeilname, String eggHuntConfig) {
|
||||
return user.getTeam() != 0;
|
||||
public boolean matches(UnlockContext context) {
|
||||
return context.getUser().getTeam() != 0;
|
||||
}
|
||||
}.or(SERVER_TEAM);
|
||||
};
|
||||
ParticleRequirement EVENT_PARTICIPATION = new ParticleRequirement() {
|
||||
@Override
|
||||
public String getRequirementName(Player player) {
|
||||
@@ -82,8 +90,8 @@ public interface ParticleRequirement {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(SteamwarUser user, Set<Integer> eventTeilname, String eggHuntConfig) {
|
||||
return !eventTeilname.isEmpty();
|
||||
public boolean matches(UnlockContext context) {
|
||||
return !context.getEvents().isEmpty();
|
||||
}
|
||||
}.or(SERVER_TEAM);
|
||||
ParticleRequirement EGG_HUNT_EASY = _easterEggHuntDifficulty(EggDifficulty.EASY);
|
||||
@@ -98,7 +106,8 @@ public interface ParticleRequirement {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(SteamwarUser user, Set<Integer> eventTeilname, String eggHuntConfig) {
|
||||
public boolean matches(UnlockContext context) {
|
||||
String eggHuntConfig = context.getEggHuntConfig();
|
||||
if (eggHuntConfig == null) return false;
|
||||
int count = 0;
|
||||
for (int i = 0; i < eggHuntConfig.length(); i++) {
|
||||
@@ -120,8 +129,8 @@ public interface ParticleRequirement {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(SteamwarUser user, Set<Integer> eventTeilname, String eggHuntConfig) {
|
||||
return user.getTeam() == teamId;
|
||||
public boolean matches(UnlockContext context) {
|
||||
return context.getUser().getTeam() == teamId;
|
||||
}
|
||||
}.or(SERVER_TEAM);
|
||||
}
|
||||
@@ -135,8 +144,8 @@ public interface ParticleRequirement {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(SteamwarUser user, Set<Integer> eventTeilname, String eggHuntConfig) {
|
||||
return eventTeilname.contains(eventId);
|
||||
public boolean matches(UnlockContext context) {
|
||||
return context.getEvents().contains(eventId);
|
||||
}
|
||||
}.or(SERVER_TEAM);
|
||||
}
|
||||
@@ -152,9 +161,9 @@ public interface ParticleRequirement {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(SteamwarUser user, Set<Integer> eventTeilname, String eggHuntConfig) {
|
||||
if (!eventTeilname.contains(eventId)) return false;
|
||||
return teams.contains(user.getTeam());
|
||||
public boolean matches(UnlockContext context) {
|
||||
if (!teams.contains(context.getUser().getTeam())) return false;
|
||||
return context.getEvents().contains(eventId);
|
||||
}
|
||||
}.or(SERVER_TEAM);
|
||||
}
|
||||
@@ -181,7 +190,8 @@ public interface ParticleRequirement {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(SteamwarUser user, Set<Integer> eventTeilname, String eggHuntConfig) {
|
||||
public boolean matches(UnlockContext context) {
|
||||
String eggHuntConfig = context.getEggHuntConfig();
|
||||
if (eggHuntConfig == null) return false;
|
||||
return eggs.stream().allMatch(id -> eggHuntConfig.length() > id && eggHuntConfig.charAt(id) == '1');
|
||||
}
|
||||
@@ -197,8 +207,8 @@ public interface ParticleRequirement {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(SteamwarUser user, Set<Integer> eventTeilname, String eggHuntConfig) {
|
||||
return user.getId() == userId;
|
||||
public boolean matches(UnlockContext context) {
|
||||
return context.getUser().getId() == userId;
|
||||
}
|
||||
}.or(SERVER_TEAM);
|
||||
}
|
||||
@@ -212,8 +222,8 @@ public interface ParticleRequirement {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(SteamwarUser user, Set<Integer> eventTeilname, String eggHuntConfig) {
|
||||
return user.getId() == userId;
|
||||
public boolean matches(UnlockContext context) {
|
||||
return context.getUser().getId() == userId;
|
||||
}
|
||||
}.or(SERVER_TEAM);
|
||||
}
|
||||
@@ -228,12 +238,31 @@ public interface ParticleRequirement {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(SteamwarUser user, Set<Integer> eventTeilname, String eggHuntConfig) {
|
||||
return user.getTeam() == teamId;
|
||||
public boolean matches(UnlockContext context) {
|
||||
return context.getUser().getTeam() == teamId;
|
||||
}
|
||||
}.or(SERVER_TEAM);
|
||||
}
|
||||
|
||||
// Met by meeting any of the requirements; the lore lists them one per line
|
||||
static ParticleRequirement anyOf(ParticleRequirement... requirements) {
|
||||
return new ParticleRequirement() {
|
||||
@Override
|
||||
public String getRequirementName(Player player) {
|
||||
return java.util.Arrays.stream(requirements).map(requirement -> requirement.getRequirementName(player)).filter(Objects::nonNull).collect(Collectors.joining("\n"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(UnlockContext context) {
|
||||
for (ParticleRequirement requirement : requirements) {
|
||||
if (requirement.test(context)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// The alternative is checked first, as it is usually the cheap server team check
|
||||
default ParticleRequirement or(ParticleRequirement particleRequirement) {
|
||||
return new ParticleRequirement() {
|
||||
@Override
|
||||
@@ -242,8 +271,8 @@ public interface ParticleRequirement {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(SteamwarUser user, Set<Integer> eventTeilname, String eggHuntConfig) {
|
||||
return ParticleRequirement.this.test(user, eventTeilname, eggHuntConfig) || particleRequirement.test(user, eventTeilname, eggHuntConfig);
|
||||
public boolean matches(UnlockContext context) {
|
||||
return particleRequirement.test(context) || ParticleRequirement.this.test(context);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
@@ -36,8 +38,13 @@ public class ParticleTickData {
|
||||
private Location location;
|
||||
private final double deg;
|
||||
private final boolean isMoving;
|
||||
private final List<Player> viewers;
|
||||
private boolean onlySelf = false;
|
||||
private boolean onlyOthers = false;
|
||||
// Set for entries placed relative to the body, null for world placement
|
||||
private BodyFrame frame;
|
||||
// Where a point sits along its shape, from 0 to 1; when set, gradients colour by it instead of by time
|
||||
private Double shade;
|
||||
|
||||
public ParticleTickData withLocation(Location location) {
|
||||
ParticleTickData particleTickData = copy();
|
||||
@@ -45,6 +52,24 @@ public class ParticleTickData {
|
||||
return particleTickData;
|
||||
}
|
||||
|
||||
public ParticleTickData withFrame(BodyFrame frame) {
|
||||
ParticleTickData particleTickData = copy();
|
||||
particleTickData.frame = frame;
|
||||
particleTickData.location = frame.getOrigin().clone();
|
||||
return particleTickData;
|
||||
}
|
||||
|
||||
public ParticleTickData withShade(double shade) {
|
||||
ParticleTickData particleTickData = copy();
|
||||
particleTickData.shade = shade;
|
||||
return particleTickData;
|
||||
}
|
||||
|
||||
// The gradient position for this tick: along the shape when a shade is set, otherwise moving with time
|
||||
public double gradientPosition() {
|
||||
return shade != null ? Math.min(shade, 0.9999) * 360 : (deg * 10) % 360;
|
||||
}
|
||||
|
||||
public ParticleTickData onlySelf() {
|
||||
ParticleTickData particleTickData = copy();
|
||||
particleTickData.onlySelf = true;
|
||||
@@ -65,6 +90,6 @@ public class ParticleTickData {
|
||||
}
|
||||
|
||||
public ParticleTickData copy() {
|
||||
return new ParticleTickData(world, player, location, deg, isMoving, onlySelf, onlyOthers);
|
||||
return new ParticleTickData(world, player, location, deg, isMoving, viewers, onlySelf, onlyOthers, frame, shade);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2026 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.lobby.particle;
|
||||
|
||||
import de.steamwar.lobby.special.easter.EggHunt;
|
||||
import de.steamwar.sql.Event;
|
||||
import de.steamwar.sql.SteamwarUser;
|
||||
import de.steamwar.sql.TeamTeilnahme;
|
||||
import de.steamwar.sql.UserConfig;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
// Everything requirements check against, loaded only when a requirement needs it
|
||||
public class UnlockContext {
|
||||
|
||||
@Getter
|
||||
private final SteamwarUser user;
|
||||
private Set<Integer> events;
|
||||
private String eggHuntConfig;
|
||||
private boolean eggHuntConfigLoaded;
|
||||
|
||||
public UnlockContext(SteamwarUser user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
public Set<Integer> getEvents() {
|
||||
if (events == null) {
|
||||
events = user.getTeam() == 0 ? Set.of() : TeamTeilnahme.getEvents(user.getTeam()).stream().map(Event::getEventID).collect(Collectors.toSet());
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
public String getEggHuntConfig() {
|
||||
if (!eggHuntConfigLoaded) {
|
||||
eggHuntConfig = UserConfig.getConfig(user.getId(), EggHunt.EGG_HUNT_CONFIG_KEY);
|
||||
eggHuntConfigLoaded = true;
|
||||
}
|
||||
return eggHuntConfig;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2026 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.lobby.particle.design;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
|
||||
public class BooleanParameter extends Parameter<Boolean> {
|
||||
|
||||
public BooleanParameter(String key, String nameKey, boolean defaultValue) {
|
||||
super(key, nameKey, defaultValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement toJson(Boolean value) {
|
||||
return new JsonPrimitive(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean read(JsonElement json) {
|
||||
return json.getAsBoolean();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2026 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.lobby.particle.design;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import de.steamwar.lobby.particle.ParticleRequirement;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@Getter
|
||||
public class ChoiceParameter<E> extends Parameter<E> {
|
||||
|
||||
// Internal choices are usable by presets but never offered in the builder or accepted from storage. Locked choices
|
||||
// are offered with their requirement; the requirement is looked up lazily as it may live in a reward design.
|
||||
public record Choice<E>(String id, E value, boolean internal, Supplier<ParticleRequirement> requirement) {
|
||||
|
||||
public Choice(String id, E value, boolean internal) {
|
||||
this(id, value, internal, () -> ParticleRequirement.NO_REQUIRMENT);
|
||||
}
|
||||
}
|
||||
|
||||
private final List<Choice<E>> choices;
|
||||
|
||||
public ChoiceParameter(String key, String nameKey, E defaultValue, List<Choice<E>> choices) {
|
||||
super(key, nameKey, defaultValue);
|
||||
this.choices = choices;
|
||||
}
|
||||
|
||||
public static <E extends Enum<E>> ChoiceParameter<E> ofEnum(String key, String nameKey, E defaultValue, Predicate<E> internal, Function<E, ParticleRequirement> requirement) {
|
||||
List<Choice<E>> choices = new ArrayList<>();
|
||||
for (E value : defaultValue.getDeclaringClass().getEnumConstants()) {
|
||||
choices.add(new Choice<>(value.name(), value, internal.test(value), () -> requirement.apply(value)));
|
||||
}
|
||||
return new ChoiceParameter<>(key, nameKey, defaultValue, choices);
|
||||
}
|
||||
|
||||
public static <E extends Enum<E>> ChoiceParameter<E> ofEnum(String key, String nameKey, E defaultValue, Predicate<E> internal) {
|
||||
return ofEnum(key, nameKey, defaultValue, internal, value -> ParticleRequirement.NO_REQUIRMENT);
|
||||
}
|
||||
|
||||
public static <E extends Enum<E>> ChoiceParameter<E> ofEnum(String key, String nameKey, E defaultValue) {
|
||||
return ofEnum(key, nameKey, defaultValue, value -> false);
|
||||
}
|
||||
|
||||
public List<E> getBuildableValues() {
|
||||
return choices.stream().filter(choice -> !choice.internal()).map(Choice::value).toList();
|
||||
}
|
||||
|
||||
public ParticleRequirement requirement(E value) {
|
||||
return choiceOf(value).requirement().get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement toJson(E value) {
|
||||
return new JsonPrimitive(choiceOf(value).id());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected E read(JsonElement json) {
|
||||
String id = json.getAsString();
|
||||
return choices.stream()
|
||||
.filter(choice -> !choice.internal() && choice.id().equals(id))
|
||||
.map(Choice::value)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public E validate(E value) {
|
||||
return choices.stream().anyMatch(choice -> choice.value() == value) ? value : getDefaultValue();
|
||||
}
|
||||
|
||||
private Choice<E> choiceOf(E value) {
|
||||
return choices.stream()
|
||||
.filter(choice -> choice.value() == value)
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new IllegalArgumentException("Unknown choice for " + getKey()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2026 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.lobby.particle.design;
|
||||
|
||||
public enum DesignVisibility {
|
||||
// Offered in the builder to everyone
|
||||
BASIC,
|
||||
// Offered in the builder, usable once its requirement is met
|
||||
REWARD,
|
||||
// Only used by presets, never offered in the builder or accepted from storage
|
||||
INTERNAL,
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2026 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.lobby.particle.design;
|
||||
|
||||
import de.steamwar.lobby.particle.ParticleElement;
|
||||
import de.steamwar.lobby.particle.ParticleRequirement;
|
||||
import de.steamwar.lobby.particle.DrawnWings;
|
||||
import de.steamwar.lobby.particle.WingDesign;
|
||||
import de.steamwar.lobby.particle.elements.Circle;
|
||||
import de.steamwar.lobby.particle.elements.Cloud;
|
||||
import de.steamwar.lobby.particle.elements.Wing;
|
||||
import de.steamwar.lobby.particle.elements.custom.YOffset;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import org.bukkit.Material;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@UtilityClass
|
||||
public class Designs {
|
||||
|
||||
// Built on first use, so the design classes never depend on each other's initialisation order
|
||||
private Map<String, ParticleDesign> registry;
|
||||
|
||||
public final ParticleDesign SIMPLE = new ParticleDesign("simple", Material.BLAZE_POWDER, "PARTICLE_DESIGN_SIMPLE") {
|
||||
@Override
|
||||
public ParticleElement build(ParameterValues values, ParticleElement particle) {
|
||||
return particle;
|
||||
}
|
||||
};
|
||||
|
||||
public final DoubleParameter CIRCLE_RADIUS = new DoubleParameter("radius", "PARTICLE_PARAM_RADIUS", 1, 0.2, 3, 0.1);
|
||||
public final DoubleParameter CIRCLE_SPEED = new DoubleParameter("circleSpeed", "PARTICLE_PARAM_CIRCLE_SPEED", 1, -20, 20, 0.5);
|
||||
public final DoubleParameter CIRCLE_PHASE = new DoubleParameter("phase", "PARTICLE_PARAM_PHASE", 0, 0, 345, 15);
|
||||
public final DoubleParameter CIRCLE_RISE = new DoubleParameter("rise", "PARTICLE_PARAM_RISE", 0, 0, 3, 0.1);
|
||||
public final DoubleParameter CIRCLE_RISE_SPEED = new DoubleParameter("riseSpeed", "PARTICLE_PARAM_RISE_SPEED", 20, 0, 100, 5);
|
||||
public final BooleanParameter CIRCLE_BOUNCE = new BooleanParameter("bounce", "PARTICLE_PARAM_BOUNCE", true);
|
||||
public final BooleanParameter CIRCLE_INVERTED = new BooleanParameter("inverted", "PARTICLE_PARAM_INVERTED", false);
|
||||
|
||||
public final ParticleDesign CIRCLE = new ParticleDesign("circle", Material.MAGMA_CREAM, "PARTICLE_DESIGN_CIRCLE",
|
||||
CIRCLE_RADIUS, CIRCLE_SPEED, CIRCLE_PHASE, CIRCLE_RISE, CIRCLE_RISE_SPEED, CIRCLE_BOUNCE, CIRCLE_INVERTED) {
|
||||
@Override
|
||||
public ParticleElement build(ParameterValues values, ParticleElement particle) {
|
||||
double rise = values.get(CIRCLE_RISE);
|
||||
if (rise > 0) {
|
||||
particle = new YOffset(particle, values.get(CIRCLE_RISE_SPEED), 0, rise, values.get(CIRCLE_INVERTED), values.get(CIRCLE_BOUNCE));
|
||||
}
|
||||
return new Circle(particle, values.get(CIRCLE_RADIUS), values.get(CIRCLE_SPEED), values.get(CIRCLE_PHASE));
|
||||
}
|
||||
};
|
||||
|
||||
// Images keep their old ids, so saved wings still load; preset and tournament images stay out of the builder
|
||||
private final List<ChoiceParameter.Choice<WingShape>> WING_SHAPES = List.of(
|
||||
new ChoiceParameter.Choice<>("simple", WingShape.image(WingDesign.SIMPLE, Material.FEATHER), false),
|
||||
new ChoiceParameter.Choice<>("complex", WingShape.image(WingDesign.COMPLEX, Material.PHANTOM_MEMBRANE), false),
|
||||
new ChoiceParameter.Choice<>("sword", WingShape.image(WingDesign.SWORD, Material.STONE_SWORD), false),
|
||||
new ChoiceParameter.Choice<>("bat", WingShape.drawn(DrawnWings.BAT, Material.BAT_SPAWN_EGG), false),
|
||||
new ChoiceParameter.Choice<>("butterfly", WingShape.drawn(DrawnWings.BUTTERFLY, Material.PINK_PETALS), false),
|
||||
new ChoiceParameter.Choice<>("fairy", WingShape.drawn(DrawnWings.FAIRY, Material.ALLAY_SPAWN_EGG), false),
|
||||
// Tournament wing images, unlocked by the placement that earns their reward
|
||||
new ChoiceParameter.Choice<>("mwgl", WingShape.image(WingDesign.MWGL, Material.IRON_SWORD), false, Unlocks.MINI_WARGEAR_LIGA_PLACEMENT),
|
||||
new ChoiceParameter.Choice<>("sw", WingShape.image(WingDesign.SW, Material.CYAN_CARPET), false, Unlocks.UNDERWATER_MWG_PLACEMENT),
|
||||
new ChoiceParameter.Choice<>("wgs", WingShape.image(WingDesign.WGS, Material.DIAMOND_HELMET), false, Unlocks.WARGEAR_SEASON_2022_PLACEMENT),
|
||||
new ChoiceParameter.Choice<>("sword_crossed", WingShape.image(WingDesign.SWORD_CROSSED, Material.GOLDEN_SWORD), false, Unlocks.WARGEAR_CLASH_PLACEMENT),
|
||||
new ChoiceParameter.Choice<>("eclipse", WingShape.image(WingDesign.ECLIPSE), true),
|
||||
new ChoiceParameter.Choice<>("pl", WingShape.image(WingDesign.PL), true),
|
||||
new ChoiceParameter.Choice<>("plompa_easter", WingShape.image(WingDesign.PlompaEasterWings), true),
|
||||
new ChoiceParameter.Choice<>("ely_e", WingShape.image(WingDesign.ELY_E), true),
|
||||
new ChoiceParameter.Choice<>("ely_l", WingShape.image(WingDesign.ELY_L), true),
|
||||
new ChoiceParameter.Choice<>("ely_y", WingShape.image(WingDesign.ELY_Y), true),
|
||||
new ChoiceParameter.Choice<>("ev", WingShape.image(WingDesign.EV), true)
|
||||
);
|
||||
public final ChoiceParameter<WingShape> WING_SHAPE = new ChoiceParameter<>("image", "PARTICLE_PARAM_WING_IMAGE", WING_SHAPES.get(0).value(), WING_SHAPES);
|
||||
// Points per wing of drawn shapes; it replaces the amount, as every point spawns one particle
|
||||
public final IntParameter WING_DETAIL = new IntParameter("detail", "PARTICLE_PARAM_WING_DETAIL", 20, 6, 60);
|
||||
public final DoubleParameter WING_SIZE = new DoubleParameter("size", "PARTICLE_PARAM_WING_SIZE", 0.15, 0.05, 0.3, 0.01);
|
||||
|
||||
public enum WingColoring {
|
||||
// The whole wing changes colour through the entry's colours over time
|
||||
OVER_TIME,
|
||||
// The colours run from the spine to the wing tips
|
||||
ALONG_WING,
|
||||
}
|
||||
|
||||
public final ChoiceParameter<WingColoring> WING_COLORING = ChoiceParameter.ofEnum("coloring", "PARTICLE_PARAM_WING_COLORING", WingColoring.OVER_TIME);
|
||||
// Percent of the points shown each tick; fewer makes the wing twinkle and costs less
|
||||
public final IntParameter WING_SPARKLE = new IntParameter("sparkle", "PARTICLE_PARAM_WING_SPARKLE", 100, 10, 100);
|
||||
public final ChoiceParameter<Wing.Motion> WING_MOTION = ChoiceParameter.ofEnum("motion", "PARTICLE_PARAM_WING_MOTION", Wing.Motion.NONE, motion -> false,
|
||||
motion -> motion == Wing.Motion.BEAT_ON_JUMP ? Unlocks.WARGEAR_SEASON_2022.get() : ParticleRequirement.NO_REQUIRMENT);
|
||||
// Moving wings need to be redrawn often to look smooth; still ones are fine once a second
|
||||
private final int MOVING_WING_INTERVAL = 2;
|
||||
private final int STILL_WING_INTERVAL = 20;
|
||||
|
||||
public final ParticleDesign WING = new ParticleDesign("wing", Material.ELYTRA, "PARTICLE_DESIGN_WING", WING_SHAPE, WING_DETAIL, WING_SIZE, WING_COLORING, WING_SPARKLE, WING_MOTION) {
|
||||
@Override
|
||||
public ParticleElement build(ParameterValues values, ParticleElement particle) {
|
||||
WingShape shape = values.get(WING_SHAPE);
|
||||
return new Wing(particle, values.get(WING_SIZE), shape.design(values.get(WING_DETAIL)),
|
||||
values.get(WING_COLORING) == WingColoring.ALONG_WING, values.get(WING_SPARKLE), values.get(WING_MOTION));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean redrawsAfterJump(ParameterValues values) {
|
||||
return values.get(WING_MOTION) == Wing.Motion.BEAT_ON_JUMP;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int points(ParameterValues values) {
|
||||
int points = values.get(WING_SHAPE).points(values.get(WING_DETAIL));
|
||||
return (int) Math.ceil(points * values.get(WING_SPARKLE) / 100.0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean showsSetting(Parameter<?> parameter, ParameterValues values) {
|
||||
return parameter != WING_DETAIL || values.get(WING_SHAPE).isDrawn();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean replacesAmount(ParameterValues values) {
|
||||
return values.get(WING_SHAPE).isDrawn();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void adjust(Parameter<?> changed, ParameterValues values) {
|
||||
if (changed != WING_MOTION) return;
|
||||
// A beat on jumping is redrawn every tick only while it beats
|
||||
boolean moving = values.get(WING_MOTION) == Wing.Motion.FLAP_SLOW || values.get(WING_MOTION) == Wing.Motion.FLAP_FAST;
|
||||
int interval = values.get(EntryParameters.INTERVAL);
|
||||
if (moving && interval > MOVING_WING_INTERVAL) {
|
||||
values.set(EntryParameters.INTERVAL, MOVING_WING_INTERVAL);
|
||||
} else if (!moving && interval == MOVING_WING_INTERVAL) {
|
||||
values.set(EntryParameters.INTERVAL, STILL_WING_INTERVAL);
|
||||
}
|
||||
}
|
||||
|
||||
// Image wings spawn a particle on every pixel, so they start sparse, and sit on the back even while gliding
|
||||
@Override
|
||||
public void applyDefaults(ParameterValues values) {
|
||||
values.set(EntryParameters.COUNT, 1)
|
||||
.set(EntryParameters.SPEED, 0.0)
|
||||
.set(EntryParameters.TICK_MODE, EntryParameters.TickMode.ALWAYS)
|
||||
.set(EntryParameters.INTERVAL, STILL_WING_INTERVAL)
|
||||
.set(EntryParameters.PLACEMENT, EntryParameters.Placement.BODY);
|
||||
}
|
||||
};
|
||||
|
||||
// The wing shape that shows the given image, for presets
|
||||
public WingShape wingShape(WingDesign image) {
|
||||
return WING_SHAPE.getChoices().stream().map(ChoiceParameter.Choice::value).filter(shape -> shape.shows(image)).findFirst().orElseThrow();
|
||||
}
|
||||
|
||||
public final ParticleDesign CLOUD = new ParticleDesign("cloud", Material.WHITE_WOOL, "PARTICLE_DESIGN_CLOUD") {
|
||||
@Override
|
||||
public ParticleElement build(ParameterValues values, ParticleElement particle) {
|
||||
return new Cloud(particle);
|
||||
}
|
||||
};
|
||||
|
||||
private Map<String, ParticleDesign> registry() {
|
||||
if (registry == null) {
|
||||
Map<String, ParticleDesign> designs = new LinkedHashMap<>();
|
||||
List.of(SIMPLE, CIRCLE, WING, CLOUD).forEach(design -> register(designs, design));
|
||||
RewardDesigns.all().forEach(design -> register(designs, design));
|
||||
InternalDesigns.all().forEach(design -> register(designs, design));
|
||||
registry = designs;
|
||||
}
|
||||
return registry;
|
||||
}
|
||||
|
||||
private void register(Map<String, ParticleDesign> designs, ParticleDesign design) {
|
||||
if (designs.put(design.getId(), design) != null) {
|
||||
throw new IllegalStateException("Duplicate particle design " + design.getId());
|
||||
}
|
||||
}
|
||||
|
||||
public ParticleDesign byId(String id) {
|
||||
return registry().get(id);
|
||||
}
|
||||
|
||||
public List<ParticleDesign> all() {
|
||||
return List.copyOf(registry().values());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2026 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.lobby.particle.design;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public class DoubleParameter extends Parameter<Double> {
|
||||
|
||||
private final double min;
|
||||
private final double max;
|
||||
private final double step;
|
||||
|
||||
public DoubleParameter(String key, String nameKey, double defaultValue, double min, double max, double step) {
|
||||
super(key, nameKey, defaultValue);
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
this.step = step;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement toJson(Double value) {
|
||||
return new JsonPrimitive(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Double read(JsonElement json) {
|
||||
return json.getAsDouble();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double validate(Double value) {
|
||||
if (value.isNaN()) {
|
||||
return getDefaultValue();
|
||||
}
|
||||
return Math.max(min, Math.min(max, value));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2026 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.lobby.particle.design;
|
||||
|
||||
import de.steamwar.lobby.particle.ParticleRequirement;
|
||||
import de.steamwar.lobby.particle.ParticleElement;
|
||||
import de.steamwar.lobby.particle.elements.*;
|
||||
import de.steamwar.lobby.particle.elements.custom.NonMoving;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import org.bukkit.Material;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
// Parameters every entry has, independent of its design
|
||||
@UtilityClass
|
||||
public class EntryParameters {
|
||||
|
||||
public enum TickMode {
|
||||
MOVE,
|
||||
ALWAYS,
|
||||
}
|
||||
|
||||
// Body places offsets, heights, rings and wings around the player's body, even while gliding or swimming
|
||||
public enum Placement {
|
||||
WORLD,
|
||||
BODY,
|
||||
}
|
||||
|
||||
// Enchanting glyphs can fly between each point and the player instead of floating down
|
||||
public enum GlyphFlight {
|
||||
NORMAL,
|
||||
INTO_PLAYER,
|
||||
OUT_OF_PLAYER,
|
||||
}
|
||||
|
||||
public enum Movement {
|
||||
ANY(element -> element),
|
||||
MOVING(element -> data -> {
|
||||
if (data.isMoving()) element.tick(data);
|
||||
}),
|
||||
STANDING_STILL(NonMoving::new),
|
||||
;
|
||||
|
||||
private final UnaryOperator<ParticleElement> wrapper;
|
||||
|
||||
Movement(UnaryOperator<ParticleElement> wrapper) {
|
||||
this.wrapper = wrapper;
|
||||
}
|
||||
|
||||
public ParticleElement wrap(ParticleElement element) {
|
||||
return wrapper.apply(element);
|
||||
}
|
||||
}
|
||||
|
||||
public enum Gliding {
|
||||
ANY(element -> element),
|
||||
GLIDING(Flying::new),
|
||||
NOT_GLIDING(NonFlying::new),
|
||||
;
|
||||
|
||||
private final UnaryOperator<ParticleElement> wrapper;
|
||||
|
||||
Gliding(UnaryOperator<ParticleElement> wrapper) {
|
||||
this.wrapper = wrapper;
|
||||
}
|
||||
|
||||
public ParticleElement wrap(ParticleElement element) {
|
||||
return wrapper.apply(element);
|
||||
}
|
||||
}
|
||||
|
||||
public enum Ground {
|
||||
ANY(element -> element),
|
||||
ON_GROUND(Floor::new),
|
||||
IN_AIR(NonFloor::new),
|
||||
;
|
||||
|
||||
private final UnaryOperator<ParticleElement> wrapper;
|
||||
|
||||
Ground(UnaryOperator<ParticleElement> wrapper) {
|
||||
this.wrapper = wrapper;
|
||||
}
|
||||
|
||||
public ParticleElement wrap(ParticleElement element) {
|
||||
return wrapper.apply(element);
|
||||
}
|
||||
}
|
||||
|
||||
public enum Sneak {
|
||||
ANY(element -> element),
|
||||
SNEAKING(Sneaking::new),
|
||||
NOT_SNEAKING(element -> data -> {
|
||||
if (!data.getPlayer().isSneaking()) element.tick(data);
|
||||
}),
|
||||
;
|
||||
|
||||
private final UnaryOperator<ParticleElement> wrapper;
|
||||
|
||||
Sneak(UnaryOperator<ParticleElement> wrapper) {
|
||||
this.wrapper = wrapper;
|
||||
}
|
||||
|
||||
public ParticleElement wrap(ParticleElement element) {
|
||||
return wrapper.apply(element);
|
||||
}
|
||||
}
|
||||
|
||||
public enum Visibility {
|
||||
EVERYONE(element -> element),
|
||||
ONLY_SELF(OnlySelf::new),
|
||||
ONLY_OTHERS(OnlyOthers::new),
|
||||
;
|
||||
|
||||
private final UnaryOperator<ParticleElement> wrapper;
|
||||
|
||||
Visibility(UnaryOperator<ParticleElement> wrapper) {
|
||||
this.wrapper = wrapper;
|
||||
}
|
||||
|
||||
public ParticleElement wrap(ParticleElement element) {
|
||||
return wrapper.apply(element);
|
||||
}
|
||||
}
|
||||
|
||||
public final ChoiceParameter<ParticleType> PARTICLE = ChoiceParameter.ofEnum("particle", "PARTICLE_PARAM_PARTICLE", ParticleType.FLAME, type -> !type.isBuildable(), ParticleType::getRequirement);
|
||||
public final IntParameter COUNT = new IntParameter("count", "PARTICLE_PARAM_COUNT", 5, 1, 20);
|
||||
// Minecraft scatters particles around their point by about this many blocks; beyond half a block a design dissolves
|
||||
public final DoubleParameter SPREAD_X = new DoubleParameter("spreadX", "PARTICLE_PARAM_SPREAD_X", 0, 0, 0.5, 0.01);
|
||||
public final DoubleParameter SPREAD_Y = new DoubleParameter("spreadY", "PARTICLE_PARAM_SPREAD_Y", 0, 0, 0.5, 0.01);
|
||||
public final DoubleParameter SPREAD_Z = new DoubleParameter("spreadZ", "PARTICLE_PARAM_SPREAD_Z", 0, 0, 0.5, 0.01);
|
||||
// For most particles the speed of their random spray; above 0.2 they shoot blocks away
|
||||
public final DoubleParameter SPEED = new DoubleParameter("speed", "PARTICLE_PARAM_SPEED", 0.01, 0, 0.05, 0.005);
|
||||
public final GradientParameter COLORS = new GradientParameter("colors", "PARTICLE_PARAM_COLORS", 8);
|
||||
// Blocks that are also items, so they work for block and item particles alike
|
||||
public final ChoiceParameter<Material> BLOCK = new ChoiceParameter<>("block", "PARTICLE_PARAM_BLOCK", Material.STONE, List.of(
|
||||
new ChoiceParameter.Choice<>("stone", Material.STONE, false),
|
||||
new ChoiceParameter.Choice<>("cobblestone", Material.COBBLESTONE, false),
|
||||
new ChoiceParameter.Choice<>("dirt", Material.DIRT, false),
|
||||
new ChoiceParameter.Choice<>("grass_block", Material.GRASS_BLOCK, false),
|
||||
new ChoiceParameter.Choice<>("sand", Material.SAND, false),
|
||||
new ChoiceParameter.Choice<>("red_sand", Material.RED_SAND, false),
|
||||
new ChoiceParameter.Choice<>("gravel", Material.GRAVEL, false),
|
||||
new ChoiceParameter.Choice<>("oak_planks", Material.OAK_PLANKS, false),
|
||||
new ChoiceParameter.Choice<>("oak_leaves", Material.OAK_LEAVES, false),
|
||||
new ChoiceParameter.Choice<>("cherry_leaves", Material.CHERRY_LEAVES, false),
|
||||
new ChoiceParameter.Choice<>("snow_block", Material.SNOW_BLOCK, false),
|
||||
new ChoiceParameter.Choice<>("ice", Material.ICE, false),
|
||||
new ChoiceParameter.Choice<>("glass", Material.GLASS, false),
|
||||
new ChoiceParameter.Choice<>("tnt", Material.TNT, false),
|
||||
new ChoiceParameter.Choice<>("pumpkin", Material.PUMPKIN, false),
|
||||
new ChoiceParameter.Choice<>("melon", Material.MELON, false),
|
||||
new ChoiceParameter.Choice<>("honey_block", Material.HONEY_BLOCK, false),
|
||||
new ChoiceParameter.Choice<>("slime_block", Material.SLIME_BLOCK, false),
|
||||
new ChoiceParameter.Choice<>("netherrack", Material.NETHERRACK, false),
|
||||
new ChoiceParameter.Choice<>("soul_sand", Material.SOUL_SAND, false),
|
||||
new ChoiceParameter.Choice<>("magma_block", Material.MAGMA_BLOCK, false),
|
||||
new ChoiceParameter.Choice<>("end_stone", Material.END_STONE, false),
|
||||
new ChoiceParameter.Choice<>("obsidian", Material.OBSIDIAN, false),
|
||||
new ChoiceParameter.Choice<>("prismarine", Material.PRISMARINE, false),
|
||||
new ChoiceParameter.Choice<>("sculk", Material.SCULK, false),
|
||||
new ChoiceParameter.Choice<>("amethyst_block", Material.AMETHYST_BLOCK, false),
|
||||
new ChoiceParameter.Choice<>("coal_block", Material.COAL_BLOCK, false),
|
||||
new ChoiceParameter.Choice<>("iron_block", Material.IRON_BLOCK, false),
|
||||
new ChoiceParameter.Choice<>("gold_block", Material.GOLD_BLOCK, false),
|
||||
new ChoiceParameter.Choice<>("redstone_block", Material.REDSTONE_BLOCK, false),
|
||||
new ChoiceParameter.Choice<>("lapis_block", Material.LAPIS_BLOCK, false),
|
||||
new ChoiceParameter.Choice<>("emerald_block", Material.EMERALD_BLOCK, false),
|
||||
new ChoiceParameter.Choice<>("diamond_block", Material.DIAMOND_BLOCK, false),
|
||||
new ChoiceParameter.Choice<>("white_wool", Material.WHITE_WOOL, false),
|
||||
new ChoiceParameter.Choice<>("red_wool", Material.RED_WOOL, false),
|
||||
new ChoiceParameter.Choice<>("orange_wool", Material.ORANGE_WOOL, false),
|
||||
new ChoiceParameter.Choice<>("yellow_wool", Material.YELLOW_WOOL, false),
|
||||
new ChoiceParameter.Choice<>("lime_wool", Material.LIME_WOOL, false),
|
||||
new ChoiceParameter.Choice<>("light_blue_wool", Material.LIGHT_BLUE_WOOL, false),
|
||||
new ChoiceParameter.Choice<>("blue_wool", Material.BLUE_WOOL, false),
|
||||
new ChoiceParameter.Choice<>("purple_wool", Material.PURPLE_WOOL, false),
|
||||
new ChoiceParameter.Choice<>("pink_wool", Material.PINK_WOOL, false),
|
||||
new ChoiceParameter.Choice<>("black_wool", Material.BLACK_WOOL, false)
|
||||
));
|
||||
public final DoubleParameter OFFSET_X = new DoubleParameter("offsetX", "PARTICLE_PARAM_OFFSET_X", 0, -3, 3, 0.1);
|
||||
public final DoubleParameter OFFSET_Y = new DoubleParameter("offsetY", "PARTICLE_PARAM_OFFSET_Y", 0, -3, 3, 0.1);
|
||||
public final DoubleParameter OFFSET_Z = new DoubleParameter("offsetZ", "PARTICLE_PARAM_OFFSET_Z", 0, -3, 3, 0.1);
|
||||
public final IntParameter INTERVAL = new IntParameter("interval", "PARTICLE_PARAM_INTERVAL", 1, 1, 100);
|
||||
public final IntParameter TRAIL_DURATION = new IntParameter("trailDuration", "PARTICLE_PARAM_TRAIL_DURATION", 20, 1, 200);
|
||||
// Where a trail particle flies to, relative to where it spawns
|
||||
public final DoubleParameter TRAIL_TARGET_X = new DoubleParameter("trailTargetX", "PARTICLE_PARAM_TRAIL_TARGET_X", 0, -5, 5, 0.1);
|
||||
public final DoubleParameter TRAIL_TARGET_Y = new DoubleParameter("trailTargetY", "PARTICLE_PARAM_TRAIL_TARGET_Y", 1, -5, 5, 0.1);
|
||||
public final DoubleParameter TRAIL_TARGET_Z = new DoubleParameter("trailTargetZ", "PARTICLE_PARAM_TRAIL_TARGET_Z", 0, -5, 5, 0.1);
|
||||
public final ChoiceParameter<GlyphFlight> GLYPH_FLIGHT = ChoiceParameter.ofEnum("glyphFlight", "PARTICLE_PARAM_GLYPH_FLIGHT", GlyphFlight.NORMAL, flight -> false,
|
||||
flight -> switch (flight) {
|
||||
case NORMAL -> ParticleRequirement.NO_REQUIRMENT;
|
||||
case INTO_PLAYER -> Unlocks.WARGEAR_SEASON_PLACEMENT.get();
|
||||
case OUT_OF_PLAYER -> Unlocks.WARGEAR_SEASON.get();
|
||||
});
|
||||
public final ChoiceParameter<Placement> PLACEMENT = ChoiceParameter.ofEnum("placement", "PARTICLE_PARAM_PLACEMENT", Placement.WORLD);
|
||||
public final ChoiceParameter<TickMode> TICK_MODE = ChoiceParameter.ofEnum("tickMode", "PARTICLE_PARAM_TICK_MODE", TickMode.MOVE);
|
||||
public final ChoiceParameter<Movement> MOVEMENT = ChoiceParameter.ofEnum("movement", "PARTICLE_PARAM_MOVEMENT", Movement.ANY);
|
||||
public final ChoiceParameter<Gliding> GLIDING = ChoiceParameter.ofEnum("gliding", "PARTICLE_PARAM_GLIDING", Gliding.ANY);
|
||||
public final ChoiceParameter<Ground> GROUND = ChoiceParameter.ofEnum("ground", "PARTICLE_PARAM_GROUND", Ground.ANY);
|
||||
public final ChoiceParameter<Sneak> SNEAK = ChoiceParameter.ofEnum("sneak", "PARTICLE_PARAM_SNEAK", Sneak.ANY);
|
||||
public final ChoiceParameter<Visibility> VISIBILITY = ChoiceParameter.ofEnum("visibility", "PARTICLE_PARAM_VISIBILITY", Visibility.EVERYONE);
|
||||
|
||||
public final List<Parameter<?>> ALL = List.of(
|
||||
PARTICLE, GLYPH_FLIGHT, COUNT, SPREAD_X, SPREAD_Y, SPREAD_Z, SPEED, COLORS, BLOCK,
|
||||
TRAIL_DURATION, TRAIL_TARGET_X, TRAIL_TARGET_Y, TRAIL_TARGET_Z,
|
||||
OFFSET_X, OFFSET_Y, OFFSET_Z, PLACEMENT, INTERVAL, TICK_MODE,
|
||||
MOVEMENT, GLIDING, GROUND, SNEAK, VISIBILITY
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2026 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.lobby.particle.design;
|
||||
|
||||
import de.steamwar.lobby.particle.ParticleElement;
|
||||
import de.steamwar.lobby.particle.ParticleRequirement;
|
||||
import org.bukkit.Material;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
// A design whose look, including its particles, is fixed; only the entry's conditions, timing and offset apply
|
||||
public class FixedDesign extends ParticleDesign {
|
||||
|
||||
private final int points;
|
||||
private final Consumer<ParameterValues> defaults;
|
||||
private final Supplier<ParticleElement> shape;
|
||||
|
||||
public FixedDesign(String id, Material icon, String nameKey, ParticleRequirement requirement, int points, Consumer<ParameterValues> defaults, Supplier<ParticleElement> shape) {
|
||||
super(id, icon, nameKey, DesignVisibility.REWARD, requirement);
|
||||
this.points = points;
|
||||
this.defaults = defaults;
|
||||
this.shape = shape;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParticleElement build(ParameterValues values, ParticleElement particle) {
|
||||
return shape.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int points(ParameterValues values) {
|
||||
return points;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean usesEntryParticle() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyDefaults(ParameterValues values) {
|
||||
defaults.accept(values);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2026 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.lobby.particle.design;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
// An empty gradient means a random colour per spawn
|
||||
@Getter
|
||||
public class GradientParameter extends Parameter<List<Color>> {
|
||||
|
||||
private final int maxColors;
|
||||
|
||||
public GradientParameter(String key, String nameKey, int maxColors) {
|
||||
super(key, nameKey, List.of());
|
||||
this.maxColors = maxColors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement toJson(List<Color> value) {
|
||||
JsonArray array = new JsonArray();
|
||||
value.forEach(color -> array.add(color.getRGB() & 0xFFFFFF));
|
||||
return array;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<Color> read(JsonElement json) {
|
||||
List<Color> colors = new ArrayList<>();
|
||||
for (JsonElement element : json.getAsJsonArray()) {
|
||||
colors.add(new Color(element.getAsInt() & 0xFFFFFF));
|
||||
}
|
||||
return colors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Color> validate(List<Color> value) {
|
||||
return List.copyOf(value.size() > maxColors ? value.subList(0, maxColors) : value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2026 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.lobby.particle.design;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public class IntParameter extends Parameter<Integer> {
|
||||
|
||||
private final int min;
|
||||
private final int max;
|
||||
|
||||
public IntParameter(String key, String nameKey, int defaultValue, int min, int max) {
|
||||
super(key, nameKey, defaultValue);
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement toJson(Integer value) {
|
||||
return new JsonPrimitive(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Integer read(JsonElement json) {
|
||||
return json.getAsInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer validate(Integer value) {
|
||||
return Math.max(min, Math.min(max, value));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2026 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.lobby.particle.design;
|
||||
|
||||
import de.steamwar.lobby.particle.ParticleElement;
|
||||
import de.steamwar.lobby.particle.ParticleRequirement;
|
||||
import de.steamwar.lobby.particle.elements.Circle;
|
||||
import de.steamwar.lobby.particle.elements.custom.HearthBeat;
|
||||
import de.steamwar.lobby.particle.elements.custom.PulseShimmer;
|
||||
import de.steamwar.lobby.particle.elements.custom.YOffset;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import org.bukkit.Material;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
// Shapes only presets use; they are never offered in the builder
|
||||
@UtilityClass
|
||||
public class InternalDesigns {
|
||||
|
||||
public final IntParameter PULSE_SPEED = new IntParameter("pulseSpeed", "PARTICLE_PARAM_PULSE_SPEED", 80, 1, 200);
|
||||
public final IntParameter PULSE_ROTATION = new IntParameter("pulseRotation", "PARTICLE_PARAM_PULSE_ROTATION", 2, 0, 20);
|
||||
|
||||
public final ParticleDesign PULSE_SHIMMER = new ParticleDesign("pulse_shimmer", Material.GRAY_CANDLE, "PARTICLE_DESIGN_PULSE_SHIMMER", DesignVisibility.INTERNAL, ParticleRequirement.NO_REQUIRMENT,
|
||||
PULSE_SPEED, PULSE_ROTATION) {
|
||||
@Override
|
||||
public ParticleElement build(ParameterValues values, ParticleElement particle) {
|
||||
return new PulseShimmer(particle, values.get(PULSE_SPEED), values.get(PULSE_ROTATION));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int points(ParameterValues values) {
|
||||
return 3;
|
||||
}
|
||||
};
|
||||
|
||||
public final DoubleParameter HEART_BEAT_SPEED = new DoubleParameter("heartBeatSpeed", "PARTICLE_PARAM_HEART_BEAT_SPEED", 80, 1, 200, 5);
|
||||
|
||||
public final ParticleDesign HEART_BEAT = new ParticleDesign("heart_beat", Material.APPLE, "PARTICLE_DESIGN_HEART_BEAT", DesignVisibility.INTERNAL, ParticleRequirement.NO_REQUIRMENT,
|
||||
HEART_BEAT_SPEED) {
|
||||
@Override
|
||||
public ParticleElement build(ParameterValues values, ParticleElement particle) {
|
||||
return new HearthBeat(particle, values.get(HEART_BEAT_SPEED));
|
||||
}
|
||||
};
|
||||
|
||||
public final DoubleParameter ORBIT_RADIUS = new DoubleParameter("orbitRadius", "PARTICLE_PARAM_ORBIT_RADIUS", 1.5, 0.2, 3, 0.1);
|
||||
public final DoubleParameter ORBIT_SPEED = new DoubleParameter("orbitSpeed", "PARTICLE_PARAM_ORBIT_SPEED", 1, -20, 20, 0.5);
|
||||
public final DoubleParameter ORBIT_PHASE = new DoubleParameter("orbitPhase", "PARTICLE_PARAM_ORBIT_PHASE", 0, 0, 345, 15);
|
||||
|
||||
// A circle whose centre itself moves on a circle around the player
|
||||
public final ParticleDesign ORBIT = new ParticleDesign("orbit", Material.ENDER_PEARL, "PARTICLE_DESIGN_ORBIT", DesignVisibility.INTERNAL, ParticleRequirement.NO_REQUIRMENT,
|
||||
ORBIT_RADIUS, ORBIT_SPEED, ORBIT_PHASE,
|
||||
Designs.CIRCLE_RADIUS, Designs.CIRCLE_SPEED, Designs.CIRCLE_PHASE, Designs.CIRCLE_RISE, Designs.CIRCLE_RISE_SPEED, Designs.CIRCLE_BOUNCE, Designs.CIRCLE_INVERTED) {
|
||||
@Override
|
||||
public ParticleElement build(ParameterValues values, ParticleElement particle) {
|
||||
double rise = values.get(Designs.CIRCLE_RISE);
|
||||
if (rise > 0) {
|
||||
particle = new YOffset(particle, values.get(Designs.CIRCLE_RISE_SPEED), 0, rise, values.get(Designs.CIRCLE_INVERTED), values.get(Designs.CIRCLE_BOUNCE));
|
||||
}
|
||||
ParticleElement inner = new Circle(particle, values.get(Designs.CIRCLE_RADIUS), values.get(Designs.CIRCLE_SPEED), values.get(Designs.CIRCLE_PHASE));
|
||||
return new Circle(inner, values.get(ORBIT_RADIUS), values.get(ORBIT_SPEED), values.get(ORBIT_PHASE));
|
||||
}
|
||||
};
|
||||
|
||||
public List<ParticleDesign> all() {
|
||||
return List.of(PULSE_SHIMMER, HEART_BEAT, ORBIT);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2026 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.lobby.particle.design;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public abstract class Parameter<T> {
|
||||
|
||||
private final String key;
|
||||
private final String nameKey;
|
||||
private final T defaultValue;
|
||||
|
||||
protected Parameter(String key, String nameKey, T defaultValue) {
|
||||
this.key = key;
|
||||
this.nameKey = nameKey;
|
||||
this.defaultValue = defaultValue;
|
||||
}
|
||||
|
||||
public abstract JsonElement toJson(T value);
|
||||
|
||||
// Returns null when the stored value cannot be used
|
||||
protected abstract T read(JsonElement json);
|
||||
|
||||
public T fromJson(JsonElement json) {
|
||||
if (json == null || json.isJsonNull()) {
|
||||
return defaultValue;
|
||||
}
|
||||
try {
|
||||
T value = read(json);
|
||||
return value == null ? defaultValue : validate(value);
|
||||
} catch (RuntimeException e) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
public T validate(T value) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2026 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.lobby.particle.design;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class ParameterValues {
|
||||
|
||||
private final Map<String, Object> values = new HashMap<>();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T get(Parameter<T> parameter) {
|
||||
Object value = values.get(parameter.getKey());
|
||||
return value == null ? parameter.getDefaultValue() : (T) value;
|
||||
}
|
||||
|
||||
public <T> ParameterValues set(Parameter<T> parameter, T value) {
|
||||
values.put(parameter.getKey(), parameter.validate(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
// Skips the builder's limits; only for presets, which are never stored
|
||||
public <T> ParameterValues setUnchecked(Parameter<T> parameter, T value) {
|
||||
values.put(parameter.getKey(), value);
|
||||
return this;
|
||||
}
|
||||
|
||||
// Puts back values saved with copy()
|
||||
public void restore(ParameterValues saved) {
|
||||
values.clear();
|
||||
values.putAll(saved.values);
|
||||
}
|
||||
|
||||
public ParameterValues copy() {
|
||||
ParameterValues copy = new ParameterValues();
|
||||
copy.values.putAll(values);
|
||||
return copy;
|
||||
}
|
||||
|
||||
public JsonObject toJson(List<Parameter<?>> parameters) {
|
||||
JsonObject json = new JsonObject();
|
||||
parameters.forEach(parameter -> write(json, parameter));
|
||||
return json;
|
||||
}
|
||||
|
||||
public static ParameterValues fromJson(JsonObject json, List<Parameter<?>> parameters) {
|
||||
ParameterValues values = new ParameterValues();
|
||||
parameters.forEach(parameter -> values.values.put(parameter.getKey(), parameter.fromJson(json.get(parameter.getKey()))));
|
||||
return values;
|
||||
}
|
||||
|
||||
private <T> void write(JsonObject json, Parameter<T> parameter) {
|
||||
json.add(parameter.getKey(), parameter.toJson(get(parameter)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2026 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.lobby.particle.design;
|
||||
|
||||
import de.steamwar.lobby.particle.ParticleElement;
|
||||
import de.steamwar.lobby.particle.ParticleRequirement;
|
||||
import lombok.Getter;
|
||||
import org.bukkit.Material;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
public abstract class ParticleDesign {
|
||||
|
||||
private final String id;
|
||||
private final Material icon;
|
||||
private final String nameKey;
|
||||
private final DesignVisibility visibility;
|
||||
private final ParticleRequirement requirement;
|
||||
private final List<Parameter<?>> parameters;
|
||||
|
||||
protected ParticleDesign(String id, Material icon, String nameKey, DesignVisibility visibility, ParticleRequirement requirement, Parameter<?>... parameters) {
|
||||
this.id = id;
|
||||
this.icon = icon;
|
||||
this.nameKey = nameKey;
|
||||
this.visibility = visibility;
|
||||
this.requirement = requirement;
|
||||
this.parameters = List.of(parameters);
|
||||
}
|
||||
|
||||
protected ParticleDesign(String id, Material icon, String nameKey, Parameter<?>... parameters) {
|
||||
this(id, icon, nameKey, DesignVisibility.BASIC, ParticleRequirement.NO_REQUIRMENT, parameters);
|
||||
}
|
||||
|
||||
// Wraps the entry's particle into this design's shape
|
||||
public abstract ParticleElement build(ParameterValues values, ParticleElement particle);
|
||||
|
||||
// Number of particle positions this design spawns per tick
|
||||
public int points(ParameterValues values) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// False for designs with a fixed look that ignore the entry's particle type, count, spread, speed and colours
|
||||
public boolean usesEntryParticle() {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Whether the entry editor shows this design setting; some only matter for some values of others
|
||||
public boolean showsSetting(Parameter<?> parameter, ParameterValues values) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// True when the design decides how many particles each point spawns, so the amount setting is hidden and counts as 1
|
||||
public boolean replacesAmount(ParameterValues values) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Whether the design must be drawn every tick for a moment after a jump, whatever its interval; these extra
|
||||
// redraws do not count towards the budget
|
||||
public boolean redrawsAfterJump(ParameterValues values) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Called after a setting changed, to adjust others that depend on it
|
||||
public void adjust(Parameter<?> changed, ParameterValues values) {
|
||||
}
|
||||
|
||||
// Sets the conditions and timing a new entry of this design starts with
|
||||
public void applyDefaults(ParameterValues values) {
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user