forked from SteamWar/SteamWar
merge upstream
This commit is contained in:
@@ -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 {
|
||||
|
||||
static {
|
||||
SerializeManager.add(new ConfigurationSerializableSerializer());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
private static Config instance;
|
||||
@Setter
|
||||
public class Config implements VersionedConfig {
|
||||
|
||||
{
|
||||
instance = this;
|
||||
}
|
||||
public static final int VERSION = 2;
|
||||
|
||||
private final Map<UUID, YAPIONObject> playerConfigurations = new HashMap<>();
|
||||
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");
|
||||
|
||||
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) {
|
||||
|
||||
@@ -53,7 +53,7 @@ public class NoClipCommand extends SWCommand implements Listener {
|
||||
|
||||
@Override
|
||||
public void onUnmount(SWPlayer player) {
|
||||
player.setGameMode(GameMode.CREATIVE);
|
||||
player.getPlayer().setGameMode(GameMode.CREATIVE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,6 +115,7 @@ public class NoClipCommand extends SWCommand implements Listener {
|
||||
Abilities abilities = (((CraftPlayer) player).getHandle()).getAbilities();
|
||||
abilities.mayBuild = true;
|
||||
abilities.mayfly = true;
|
||||
abilities.instabuild = true;
|
||||
|
||||
swPlayer.setComponent(new NoClipData());
|
||||
BauSystem.MESSAGE.send("OTHER_NOCLIP_SLOT_INFO", player);
|
||||
|
||||
+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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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", "+");
|
||||
@@ -719,7 +725,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.
|
||||
@@ -775,18 +784,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
|
||||
|
||||
@@ -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));
|
||||
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("PARTICIPANT_CHAT", teamName, player.getName(), message);
|
||||
GlobalRecorder.getInstance().chat("SPECTATOR_CHAT", user.getUserName(), message);
|
||||
}
|
||||
} else if (Config.isReferee(player)) {
|
||||
broadcastChat("FIGHTLEADER_CHAT", player.getName(), message);
|
||||
} else {
|
||||
broadcastChat("SPECTATOR_CHAT", player.getName(), message);
|
||||
}
|
||||
|
||||
event.setCancelled(true);
|
||||
}
|
||||
|
||||
public static void broadcastChat(String message, Object... params) {
|
||||
|
||||
@@ -1,58 +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.misslewars.listener;
|
||||
|
||||
import de.steamwar.linkage.Linked;
|
||||
import de.steamwar.misslewars.FightState;
|
||||
import de.steamwar.misslewars.MWTeam;
|
||||
import de.steamwar.misslewars.MissileWars;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.player.AsyncPlayerChatEvent;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
@Linked
|
||||
public class ChatListener extends BasicListener {
|
||||
|
||||
public ChatListener() {
|
||||
super(EnumSet.allOf(FightState.class));
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onChat(AsyncPlayerChatEvent e) {
|
||||
e.setCancelled(true);
|
||||
|
||||
Player p = e.getPlayer();
|
||||
MWTeam team = MissileWars.getTeam(p);
|
||||
String message = e.getPlayer().getDisplayName() + "§8» §7" + e.getMessage();
|
||||
if (team == null) {
|
||||
Bukkit.broadcastMessage(message);
|
||||
} else {
|
||||
if (e.getMessage().startsWith("+")) {
|
||||
String teamMessage = "§8[" + team.getColorCode() + "TEAM§8] " + e.getPlayer().getDisplayName() + "§8» §7" + e.getMessage().substring(1);
|
||||
team.getPlayers().forEach(player -> player.sendMessage(teamMessage));
|
||||
} else {
|
||||
Bukkit.broadcastMessage(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -262,9 +262,8 @@ AUTO_CHECKER_RESULT_BLOCKS = §7Blocks: §c{0}§7, Max: §e{1}
|
||||
AUTO_CHECKER_RESULT_UNKNOWN_MATERIAL = §7Unknown block: §c{0}
|
||||
AUTO_CHECKER_RESULT_TOO_MANY_BLOCK = §7{0}: §c{1}§7, Max: §e{2}
|
||||
AUTO_CHECKER_RESULT_FORBIDDEN_BLOCK = §7Forbidden block: §c{0}
|
||||
AUTO_CHECKER_RESULT_WIND_CHARGES = §7Windcharges: §c{0}§7, Max: §e2048
|
||||
AUTO_CHECKER_RESULT_WIND_CHARGES_DISPENSER = §7Dispenser: §c[{0}, {1}, {2}]§7, Windcharges: §c{3}§7
|
||||
AUTO_CHECKER_RESULT_FORBIDDEN_ITEM = §7Forbidden Item: [{0}, {1}, {2}] -> §c{3}
|
||||
AUTO_CHECKER_RESULT_MIXED_ITEMS = §7Items of different rules in one inventory: [{0}, {1}, {2}] -> §c{3}
|
||||
AUTO_CHECKER_RESULT_DEFUNCT_NBT = §7Defunct NBT: §7[{0}, {1}, {2}]
|
||||
AUTO_CHECKER_RESULT_DESIGN_BLOCK = §7{0} in Design: [{1}, {2}, {3}]
|
||||
AUTO_CHECKER_RESULT_ENTITY = §7Entity: §7[{0}, {1}, {2}]
|
||||
|
||||
@@ -242,9 +242,8 @@ AUTO_CHECKER_RESULT_BLOCKS = §7Blöcke: §c{0}§7, Max: §e{1}
|
||||
AUTO_CHECKER_RESULT_UNKNOWN_MATERIAL = §7Unbekannter Block: §c{0}
|
||||
AUTO_CHECKER_RESULT_TOO_MANY_BLOCK = §7{0}: §c{1}§7, Max: §e{2}
|
||||
AUTO_CHECKER_RESULT_FORBIDDEN_BLOCK = §7Verbotener Block: §c{0}
|
||||
AUTO_CHECKER_RESULT_WIND_CHARGES = §7Windcharges: §c{0}§7, Max: §e2048
|
||||
AUTO_CHECKER_RESULT_WIND_CHARGES_DISPENSER = §7Werfer: §c[{0}, {1}, {2}]§7, Windcharges: §c{3}§7
|
||||
AUTO_CHECKER_RESULT_FORBIDDEN_ITEM = §7Verbotener gegenstand: [{0}, {1}, {2}] -> §c{3}
|
||||
AUTO_CHECKER_RESULT_MIXED_ITEMS = §7Gegenstände verschiedener Regeln in einem Inventar: [{0}, {1}, {2}] -> §c{3}
|
||||
AUTO_CHECKER_RESULT_DEFUNCT_NBT = §7Keine NBT-Daten: §c[{0}, {1}, {2}]
|
||||
AUTO_CHECKER_RESULT_DESIGN_BLOCK = §7{0} im Design: [{1}, {2}, {3}]
|
||||
AUTO_CHECKER_RESULT_RECORD = §7Schallplatte: §c[{0}, {1}, {2}]
|
||||
|
||||
@@ -25,6 +25,7 @@ import com.sk89q.worldedit.extent.clipboard.Clipboard;
|
||||
import com.sk89q.worldedit.math.BlockVector3;
|
||||
import com.sk89q.worldedit.world.block.BaseBlock;
|
||||
import de.steamwar.sql.GameModeConfig;
|
||||
import de.steamwar.sql.GameModeConfig.SchematicConfig.BlockRule;
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
import org.bukkit.Material;
|
||||
@@ -62,6 +63,7 @@ public class AutoChecker {
|
||||
|
||||
public AutoChecker.BlockScanResult scan(Clipboard clipboard, GameModeConfig<Material, String> type) {
|
||||
AutoChecker.BlockScanResult result = new AutoChecker.BlockScanResult();
|
||||
Map<BlockPos, List<BlockRule<Material>>> ruleCandidates = new HashMap<>();
|
||||
BlockVector3 min = clipboard.getMinimumPoint();
|
||||
BlockVector3 max = clipboard.getMaximumPoint();
|
||||
for (int x = min.x(); x <= max.x(); x++) {
|
||||
@@ -75,13 +77,13 @@ public class AutoChecker {
|
||||
|
||||
BlockPos pos = new BlockPos(x, y, z);
|
||||
|
||||
result.getBlockCounts().merge(material, 1, Integer::sum);
|
||||
if (type.Schematic.isInventory(material)) {
|
||||
checkInventory(result, block, material, pos, type);
|
||||
if (!result.getInventoryItemCounts().getOrDefault(pos, Collections.emptyMap()).isEmpty()) {
|
||||
result.getBlockCounts().merge(material, 1, Integer::sum);
|
||||
result.getInventoryBlockType().put(pos, material);
|
||||
List<BlockRule<Material>> candidates = checkInventory(result, block, material, pos, type);
|
||||
if (!candidates.isEmpty()) {
|
||||
ruleCandidates.put(pos, candidates);
|
||||
}
|
||||
} else {
|
||||
result.getBlockCounts().merge(material, 1, Integer::sum);
|
||||
}
|
||||
|
||||
if (x == min.x() || x == max.x() || y == max.y() || z == min.z() || z == max.z()) {
|
||||
@@ -90,28 +92,28 @@ public class AutoChecker {
|
||||
}
|
||||
}
|
||||
}
|
||||
assignRules(result, ruleCandidates);
|
||||
return result;
|
||||
}
|
||||
|
||||
private void checkInventory(AutoChecker.BlockScanResult result, BaseBlock block, Material material, BlockPos pos, GameModeConfig<Material, String> type) {
|
||||
/**
|
||||
* Scans the inventory and returns the rules this block may be counted towards.
|
||||
* An empty list means the block is either faulty (reported in the result) or unlimited.
|
||||
*/
|
||||
private List<BlockRule<Material>> checkInventory(AutoChecker.BlockScanResult result, BaseBlock block, Material material, BlockPos pos, GameModeConfig<Material, String> type) {
|
||||
CompoundTag nbt = block.getNbtData();
|
||||
if (nbt == null) {
|
||||
result.getDefunctNbt().add(pos);
|
||||
return;
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
|
||||
if (material == Material.JUKEBOX && nbt.getValue().containsKey("RecordItem")) {
|
||||
result.getRecords().add(pos);
|
||||
return;
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<CompoundTag> items = nbt.getList("Items", CompoundTag.class);
|
||||
if (items.isEmpty()) return; // Leeres Inventar
|
||||
|
||||
Map<Material, Integer> itemCounts = new EnumMap<>(Material.class);
|
||||
int windChargeCount = 0;
|
||||
for (CompoundTag item : items) {
|
||||
for (CompoundTag item : nbt.getList("Items", CompoundTag.class)) {
|
||||
if (!item.containsKey("id")) {
|
||||
result.getDefunctNbt().add(pos);
|
||||
continue;
|
||||
@@ -121,22 +123,69 @@ public class AutoChecker {
|
||||
if (itemType == null) // Leere Slots
|
||||
continue;
|
||||
|
||||
if (type.Schematic.Type.getName().equals("wargearseason26") && material == Material.DISPENSER && itemType == Material.WIND_CHARGE) {
|
||||
windChargeCount += item.getInt("count");
|
||||
} else if (type.Schematic.getItemAmount(material, itemType) == 0) {
|
||||
result.getForbiddenItems().computeIfAbsent(pos, blockVector3 -> new HashSet<>()).add(itemType);
|
||||
} else {
|
||||
itemCounts.merge(itemType, item.getInt("count"), Integer::sum);
|
||||
}
|
||||
if (item.containsKey("tag")) {
|
||||
result.getForbiddenNbt().computeIfAbsent(pos, blockVector3 -> new HashSet<>()).add(itemType);
|
||||
}
|
||||
}
|
||||
if (!itemCounts.isEmpty()) {
|
||||
result.getInventoryItemCounts().put(pos, itemCounts);
|
||||
result.getInventoryBlockType().put(pos, material);
|
||||
|
||||
List<BlockRule<Material>> rules = type.Schematic.getRules(material);
|
||||
if (itemCounts.isEmpty()) { // Leeres Inventar
|
||||
return rules.stream().filter(rule -> !rule.Content.loaded).collect(Collectors.toList());
|
||||
}
|
||||
result.getWindChargeCount().put(pos, windChargeCount);
|
||||
result.getInventoryItemCounts().put(pos, itemCounts);
|
||||
|
||||
List<BlockRule<Material>> contentRules = rules.stream().filter(rule -> rule.Content.loaded).collect(Collectors.toList());
|
||||
List<BlockRule<Material>> itemsFitting = contentRules.stream()
|
||||
.filter(rule -> rule.Content.Items.containsAll(itemCounts.keySet()))
|
||||
.collect(Collectors.toList());
|
||||
List<BlockRule<Material>> fitting = itemsFitting.stream()
|
||||
.filter(rule -> itemCounts.values().stream().allMatch(count -> count <= rule.Content.Amount))
|
||||
.collect(Collectors.toList());
|
||||
if (!fitting.isEmpty()) {
|
||||
return fitting;
|
||||
}
|
||||
|
||||
if (!itemsFitting.isEmpty()) {
|
||||
BlockRule<Material> best = Collections.max(itemsFitting, Comparator.comparingInt(rule -> rule.Content.Amount));
|
||||
itemCounts.forEach((itemType, count) -> {
|
||||
if (count > best.Content.Amount) {
|
||||
result.getTooManyItems().computeIfAbsent(pos, blockVector3 -> new EnumMap<>(Material.class)).put(itemType, best.Content.Amount);
|
||||
}
|
||||
});
|
||||
return Collections.singletonList(best);
|
||||
}
|
||||
|
||||
Set<Material> allowed = contentRules.stream().flatMap(rule -> rule.Content.Items.stream()).collect(Collectors.toSet());
|
||||
Set<Material> forbidden = itemCounts.keySet().stream().filter(itemType -> !allowed.contains(itemType)).collect(Collectors.toSet());
|
||||
if (!forbidden.isEmpty()) {
|
||||
result.getForbiddenItems().put(pos, forbidden);
|
||||
}
|
||||
|
||||
Set<Material> rest = itemCounts.keySet().stream().filter(allowed::contains).collect(Collectors.toSet());
|
||||
if (!rest.isEmpty()) {
|
||||
// Every remaining item is allowed on its own, but no single rule allows all of them together
|
||||
BlockRule<Material> best = Collections.max(contentRules, Comparator.comparingLong(rule -> rest.stream().filter(rule.Content.Items::contains).count()));
|
||||
Set<Material> mixed = rest.stream().filter(itemType -> !best.Content.Items.contains(itemType)).collect(Collectors.toSet());
|
||||
if (!mixed.isEmpty()) {
|
||||
result.getMixedItems().put(pos, mixed);
|
||||
}
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts every block towards one of its candidate rules, preferring the rule with the most capacity left.
|
||||
* Blocks with fewer candidates are assigned first.
|
||||
*/
|
||||
private void assignRules(AutoChecker.BlockScanResult result, Map<BlockPos, List<BlockRule<Material>>> ruleCandidates) {
|
||||
ruleCandidates.entrySet().stream()
|
||||
.sorted(Comparator.comparingInt(entry -> entry.getValue().size()))
|
||||
.forEach(entry -> {
|
||||
BlockRule<Material> rule = Collections.max(entry.getValue(), Comparator.comparingLong(candidate -> (long) candidate.Amount - result.getRuleCounts().getOrDefault(candidate, 0)));
|
||||
result.getRuleCounts().merge(rule, 1, Integer::sum);
|
||||
result.getRuleBlockTypes().computeIfAbsent(rule, r -> EnumSet.noneOf(Material.class)).add(result.getInventoryBlockType().get(entry.getKey()));
|
||||
});
|
||||
}
|
||||
|
||||
@Getter
|
||||
@@ -148,8 +197,11 @@ public class AutoChecker {
|
||||
private final Map<Material, List<BlockPos>> designBlocks = new EnumMap<>(Material.class);
|
||||
private final Map<BlockPos, Map<Material, Integer>> inventoryItemCounts = new HashMap<>();
|
||||
private final Map<BlockPos, Material> inventoryBlockType = new HashMap<>();
|
||||
private final Map<BlockPos, Integer> windChargeCount = new HashMap<>();
|
||||
private final Map<BlockRule<Material>, Integer> ruleCounts = new HashMap<>();
|
||||
private final Map<BlockRule<Material>, Set<Material>> ruleBlockTypes = new HashMap<>();
|
||||
private final Map<BlockPos, Map<Material, Integer>> tooManyItems = new HashMap<>();
|
||||
private final Map<BlockPos, Set<Material>> forbiddenItems = new HashMap<>();
|
||||
private final Map<BlockPos, Set<Material>> mixedItems = new HashMap<>();
|
||||
private final Map<BlockPos, Set<Material>> forbiddenNbt = new HashMap<>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import org.bukkit.entity.Player;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Builder
|
||||
@Getter
|
||||
@@ -46,12 +47,12 @@ public class AutoCheckerResult {
|
||||
public boolean isOk() {
|
||||
return blockScanResult.getRecords().isEmpty() &&
|
||||
blockScanResult.getForbiddenItems().isEmpty() &&
|
||||
blockScanResult.getMixedItems().isEmpty() &&
|
||||
blockScanResult.getForbiddenNbt().isEmpty() &&
|
||||
isSizeOk() &&
|
||||
isBlockCountOk() &&
|
||||
isLimitedBlocksOK() &&
|
||||
isInventoryItemsOK() &&
|
||||
isWindchargeCountOK() &&
|
||||
!type.isAfterDeadline() &&
|
||||
entities.isEmpty() &&
|
||||
isDesignBlastResistanceOK();
|
||||
@@ -62,23 +63,8 @@ public class AutoCheckerResult {
|
||||
!type.isAfterDeadline();
|
||||
}
|
||||
|
||||
public boolean isWindchargeCountOK() {
|
||||
if (type.Schematic.Type.getName().equals("wargearseason26")) {
|
||||
int windChargesCount = blockScanResult.getWindChargeCount().values().stream().reduce(Integer::sum).orElse(0);
|
||||
return windChargesCount <= 2048;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isInventoryItemsOK() {
|
||||
return blockScanResult.getInventoryItemCounts().entrySet().stream().allMatch(posEntry -> {
|
||||
Material inventory = blockScanResult.getInventoryBlockType().get(posEntry.getKey());
|
||||
return posEntry.getValue().entrySet().stream().allMatch(itemEntry -> {
|
||||
int cap = type.Schematic.getItemAmount(inventory, itemEntry.getKey());
|
||||
return itemEntry.getValue() <= cap;
|
||||
});
|
||||
});
|
||||
return blockScanResult.getTooManyItems().isEmpty();
|
||||
}
|
||||
|
||||
public boolean hasWarnings() {
|
||||
@@ -107,7 +93,10 @@ public class AutoCheckerResult {
|
||||
|
||||
public boolean isLimitedBlocksOK() {
|
||||
return blockScanResult.getBlockCounts().entrySet().stream()
|
||||
.allMatch(entry -> entry.getValue() <= type.Schematic.getMaxCount(entry.getKey()));
|
||||
.filter(entry -> !type.Schematic.isInventory(entry.getKey()))
|
||||
.allMatch(entry -> entry.getValue() <= type.Schematic.getMaxCount(entry.getKey())) &&
|
||||
blockScanResult.getRuleCounts().entrySet().stream()
|
||||
.allMatch(entry -> entry.getValue() <= entry.getKey().Amount);
|
||||
}
|
||||
|
||||
public boolean isDesignBlastResistanceOK() {
|
||||
@@ -132,6 +121,7 @@ public class AutoCheckerResult {
|
||||
}
|
||||
if (!isLimitedBlocksOK()) {
|
||||
blockScanResult.getBlockCounts().forEach((mat, count) -> {
|
||||
if (type.Schematic.isInventory(mat)) return;
|
||||
int maxCount = type.Schematic.getMaxCount(mat);
|
||||
if (count > maxCount) {
|
||||
if (maxCount == 0) {
|
||||
@@ -141,30 +131,25 @@ public class AutoCheckerResult {
|
||||
}
|
||||
}
|
||||
});
|
||||
blockScanResult.getRuleCounts().forEach((rule, count) -> {
|
||||
if (count <= rule.Amount) return;
|
||||
String blocks = blockScanResult.getRuleBlockTypes().get(rule).stream().map(Material::name).collect(Collectors.joining(", "));
|
||||
if (rule.Amount == 0) {
|
||||
SchematicSystem.MESSAGE.sendPrefixless("AUTO_CHECKER_RESULT_FORBIDDEN_BLOCK", p, blocks);
|
||||
} else {
|
||||
SchematicSystem.MESSAGE.sendPrefixless("AUTO_CHECKER_RESULT_TOO_MANY_BLOCK", p, blocks, count, rule.Amount);
|
||||
}
|
||||
|
||||
if (!isWindchargeCountOK()) {
|
||||
int windChargesCount = blockScanResult.getWindChargeCount().values().stream().reduce(Integer::sum).orElse(0);
|
||||
SchematicSystem.MESSAGE.sendPrefixless("AUTO_CHECKER_RESULT_WIND_CHARGES", p, windChargesCount, 2048);
|
||||
blockScanResult.getWindChargeCount().entrySet().stream().filter(blockVector3IntegerEntry -> blockVector3IntegerEntry.getValue() > 0).forEach(blockVector3IntegerEntry -> {
|
||||
SchematicSystem.MESSAGE.sendPrefixless("AUTO_CHECKER_RESULT_WIND_CHARGES_DISPENSER", p, SchematicSystem.MESSAGE.parse("AUTO_CHECKER_RESULT_TELEPORT_HERE", p), tpCommandTo(blockVector3IntegerEntry.getKey()),
|
||||
blockVector3IntegerEntry.getKey().getBlockX(),
|
||||
blockVector3IntegerEntry.getKey().getBlockY(),
|
||||
blockVector3IntegerEntry.getKey().getBlockZ(),
|
||||
blockVector3IntegerEntry.getValue());
|
||||
});
|
||||
}
|
||||
|
||||
blockScanResult.getInventoryItemCounts().forEach((pos, itemCounts) -> {
|
||||
blockScanResult.getTooManyItems().forEach((pos, caps) -> {
|
||||
Material inventory = blockScanResult.getInventoryBlockType().get(pos);
|
||||
itemCounts.forEach((itemType, count) -> {
|
||||
int cap = type.Schematic.getItemAmount(inventory, itemType);
|
||||
if (count <= cap) return;
|
||||
caps.forEach((itemType, cap) -> {
|
||||
SchematicSystem.MESSAGE.sendPrefixless("AUTO_CHECKER_RESULT_TOO_MANY_INVENTORY_ITEMS", p, SchematicSystem.MESSAGE.parse("AUTO_CHECKER_RESULT_TELEPORT_HERE", p), tpCommandTo(pos),
|
||||
pos.getBlockX(),
|
||||
pos.getBlockY(),
|
||||
pos.getBlockZ(),
|
||||
count,
|
||||
blockScanResult.getInventoryItemCounts().get(pos).get(itemType),
|
||||
cap,
|
||||
itemType.name(),
|
||||
inventory.name());
|
||||
@@ -177,6 +162,9 @@ public class AutoCheckerResult {
|
||||
blockScanResult.getForbiddenItems().forEach((blockVector3, materials) -> {
|
||||
SchematicSystem.MESSAGE.sendPrefixless("AUTO_CHECKER_RESULT_FORBIDDEN_ITEM", p, SchematicSystem.MESSAGE.parse("AUTO_CHECKER_RESULT_TELEPORT_HERE", p), tpCommandTo(blockVector3), blockVector3.getX(), blockVector3.getY(), blockVector3.getZ(), setToString(materials));
|
||||
});
|
||||
blockScanResult.getMixedItems().forEach((blockVector3, materials) -> {
|
||||
SchematicSystem.MESSAGE.sendPrefixless("AUTO_CHECKER_RESULT_MIXED_ITEMS", p, SchematicSystem.MESSAGE.parse("AUTO_CHECKER_RESULT_TELEPORT_HERE", p), tpCommandTo(blockVector3), blockVector3.getX(), blockVector3.getY(), blockVector3.getZ(), setToString(materials));
|
||||
});
|
||||
blockScanResult.getForbiddenNbt().forEach((blockVector3, materials) -> {
|
||||
SchematicSystem.MESSAGE.sendPrefixless("AUTO_CHECKER_RESULT_FORBIDDEN_ITEM_NBT", p, SchematicSystem.MESSAGE.parse("AUTO_CHECKER_RESULT_TELEPORT_HERE", p), tpCommandTo(blockVector3), blockVector3.getX(), blockVector3.getY(), blockVector3.getZ(), setToString(materials));
|
||||
});
|
||||
|
||||
+7
-9
@@ -48,6 +48,7 @@ import java.util.*;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static de.steamwar.schematicsystem.commands.schematiccommand.SchematicCommandHelp.*;
|
||||
import static de.steamwar.schematicsystem.commands.schematiccommand.SchematicCommandUtils.togglePublic;
|
||||
@@ -171,8 +172,8 @@ public class SchematicCommand extends SWCommand {
|
||||
|
||||
Map<BlockPos, Set<Material>> toBeCheckedInvs = new HashMap<>();
|
||||
|
||||
toBeCheckedInvs.putAll(result.getBlockScanResult().getForbiddenItems());
|
||||
toBeCheckedInvs.putAll(result.getBlockScanResult().getForbiddenNbt());
|
||||
Stream.of(result.getBlockScanResult().getForbiddenItems(), result.getBlockScanResult().getMixedItems(), result.getBlockScanResult().getForbiddenNbt())
|
||||
.forEach(invs -> invs.forEach((pos, materials) -> toBeCheckedInvs.computeIfAbsent(pos, p -> new HashSet<>()).addAll(materials)));
|
||||
|
||||
for (Map.Entry<BlockPos, Set<Material>> entry : toBeCheckedInvs.entrySet()) {
|
||||
BlockPos pos = entry.getKey();
|
||||
@@ -197,16 +198,13 @@ public class SchematicCommand extends SWCommand {
|
||||
clipboard.setBlock(vector, block.toBaseBlock(builder.build()));
|
||||
}
|
||||
|
||||
for (Map.Entry<BlockPos, Map<Material, Integer>> posEntry : result.getBlockScanResult().getInventoryItemCounts().entrySet()) {
|
||||
for (Map.Entry<BlockPos, Map<Material, Integer>> posEntry : result.getBlockScanResult().getTooManyItems().entrySet()) {
|
||||
BlockPos pos = posEntry.getKey();
|
||||
Material inventory = result.getBlockScanResult().getInventoryBlockType().get(pos);
|
||||
Map<Material, Integer> itemCounts = result.getBlockScanResult().getInventoryItemCounts().get(pos);
|
||||
|
||||
for (Map.Entry<Material, Integer> itemEntry : posEntry.getValue().entrySet()) {
|
||||
Material itemType = itemEntry.getKey();
|
||||
Integer cap = type.Schematic.getItemAmount(inventory, itemType);
|
||||
if (itemEntry.getValue() <= cap) {
|
||||
continue;
|
||||
}
|
||||
int cap = itemEntry.getValue();
|
||||
|
||||
BlockVector3 vector = BlockVector3.at(pos.getX(), pos.getY(), pos.getZ());
|
||||
BaseBlock block = clipboard.getFullBlock(vector);
|
||||
@@ -215,7 +213,7 @@ public class SchematicCommand extends SWCommand {
|
||||
List<CompoundTag> items = new ArrayList<>(tag.getList("Items", CompoundTag.class));
|
||||
Collections.reverse(items); // To let the first item be in the inventory
|
||||
List<CompoundTag> list = new ArrayList<>();
|
||||
int diff = itemEntry.getValue() - cap;
|
||||
int diff = itemCounts.get(itemType) - cap;
|
||||
for (CompoundTag item : items) {
|
||||
if (item == null) {
|
||||
continue;
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
# 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/>.
|
||||
#
|
||||
LOCAL_CHAT = §eLocal §r{0}§8»§7 {1}
|
||||
|
||||
COMMAND_SYSTEM_ERROR = §cError executing the command!
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
# 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/>.
|
||||
#
|
||||
LOCAL_CHAT = §eLokal §r{0}§8»§7 {1}
|
||||
|
||||
COMMAND_SYSTEM_ERROR = §cFehler beim Ausführen des Befehls!
|
||||
|
||||
|
||||
@@ -21,8 +21,8 @@ package de.steamwar.core;
|
||||
|
||||
import de.steamwar.message.Message;
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.experimental.Delegate;
|
||||
import net.md_5.bungee.api.ChatMessageType;
|
||||
import net.md_5.bungee.api.chat.ClickEvent;
|
||||
import org.bukkit.Bukkit;
|
||||
@@ -99,11 +99,11 @@ public class SWPlayer {
|
||||
|
||||
@Data
|
||||
public static final class SWPlayerWithComponent<T extends Component> {
|
||||
private final SWPlayer player;
|
||||
private final SWPlayer swPlayer;
|
||||
private final T component;
|
||||
}
|
||||
|
||||
@Delegate
|
||||
@Getter
|
||||
private final Player player;
|
||||
private final Map<Class<? extends Component>, Component> components = new HashMap<>();
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
package de.steamwar.core.worldedit;
|
||||
|
||||
import com.comphenix.tinyprotocol.TinyProtocol;
|
||||
import com.sk89q.worldedit.IncompleteRegionException;
|
||||
import com.sk89q.worldedit.LocalSession;
|
||||
import com.sk89q.worldedit.bukkit.BukkitAdapter;
|
||||
import com.sk89q.worldedit.bukkit.WorldEditPlugin;
|
||||
@@ -29,16 +30,13 @@ import com.sk89q.worldedit.regions.selector.limit.PermissiveSelectorLimits;
|
||||
import com.sk89q.worldedit.world.World;
|
||||
import de.steamwar.core.Core;
|
||||
import de.steamwar.core.SWPlayer;
|
||||
import de.steamwar.entity.CWireframe;
|
||||
import de.steamwar.entity.REntityServer;
|
||||
import de.steamwar.inventory.SWItem;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.*;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.FluidCollisionMode;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.attribute.Attribute;
|
||||
import org.bukkit.attribute.AttributeModifier;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
@@ -46,6 +44,7 @@ import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.inventory.InventoryType;
|
||||
import org.bukkit.event.player.PlayerInteractEvent;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
import org.bukkit.util.RayTraceResult;
|
||||
|
||||
@@ -64,6 +63,16 @@ public class SteamEditWand implements Listener {
|
||||
};
|
||||
public static final ItemStack ITEM = new SWItem(MATERIAL, NAME).setLore(LORE).getItemStack();
|
||||
|
||||
static {
|
||||
ItemMeta itemMeta = ITEM.getItemMeta();
|
||||
itemMeta.addAttributeModifier(Attribute.BLOCK_INTERACTION_RANGE, new AttributeModifier(
|
||||
NamespacedKey.fromString("range", Core.getInstance()),
|
||||
35.0,
|
||||
AttributeModifier.Operation.ADD_NUMBER
|
||||
));
|
||||
ITEM.setItemMeta(itemMeta);
|
||||
}
|
||||
|
||||
private boolean noWand(Player player) {
|
||||
return player.getInventory().getItemInMainHand().getType() != SteamEditWand.MATERIAL;
|
||||
}
|
||||
@@ -86,15 +95,6 @@ public class SteamEditWand implements Listener {
|
||||
TinyProtocol.instance.addFilter(ServerboundUseItemOnPacket.class, this::rightClick);
|
||||
TinyProtocol.instance.addFilter(ServerboundPickItemFromBlockPacket.class, this::middleClick);
|
||||
TinyProtocol.instance.addFilter(ServerboundPlayerActionPacket.class, this::swapHands);
|
||||
TinyProtocol.instance.addFilter(ServerboundMovePlayerPacket.Pos.class, this::movePlayerPacket);
|
||||
TinyProtocol.instance.addFilter(ServerboundMovePlayerPacket.Rot.class, this::movePlayerPacket);
|
||||
TinyProtocol.instance.addFilter(ServerboundMovePlayerPacket.PosRot.class, this::movePlayerPacket);
|
||||
|
||||
Bukkit.getScheduler().runTaskTimer(Core.getInstance(), () -> {
|
||||
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||
if (noWand(player)) SWPlayer.of(player).removeComponent(SWEditCursor.class);
|
||||
}
|
||||
}, 1, 1);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
@@ -103,35 +103,6 @@ public class SteamEditWand implements Listener {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
|
||||
public static class SWEditCursor implements SWPlayer.Component {
|
||||
private Player player;
|
||||
private REntityServer entityServer = new REntityServer();
|
||||
private CWireframe wireframe;
|
||||
|
||||
@Override
|
||||
public void onMount(SWPlayer player) {
|
||||
this.player = player.getPlayer();
|
||||
entityServer.addPlayer(player.getPlayer());
|
||||
wireframe = new CWireframe(entityServer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUnmount(SWPlayer player) {
|
||||
entityServer.close();
|
||||
}
|
||||
|
||||
public void setLocation(Location location) {
|
||||
float distance = (float) player.getLocation().distanceSquared(location);
|
||||
wireframe.setPos1And2(location, location);
|
||||
float width = distance / 1600;
|
||||
width = ((int) (width * 16)) / 16F;
|
||||
width = Math.max(width, 1 / 16F);
|
||||
width = Math.min(width, 1 / 2F);
|
||||
wireframe.setWidth(width);
|
||||
wireframe.setBlock(WorldEditRendererCUIEditor.Type.CURSOR.getMaterial(player).createBlockData());
|
||||
}
|
||||
}
|
||||
|
||||
private Packet<?> leftClick(Player player, Packet<?> packet) {
|
||||
if (noWand(player)) return packet;
|
||||
if (player.getOpenInventory().getType() != InventoryType.CREATIVE) return packet;
|
||||
@@ -338,9 +309,13 @@ public class SteamEditWand implements Listener {
|
||||
}
|
||||
|
||||
private void selectPos1(RegionSelector regionSelector, Player player, LocalSession session, BlockVector3 pos1) {
|
||||
try {
|
||||
if (!pos1.equals(regionSelector.getPrimaryPosition())) {
|
||||
SWPlayer.of(player).setComponent(new Pos1Message(regionSelector, player, session, pos1));
|
||||
}
|
||||
} catch (IncompleteRegionException e) {
|
||||
SWPlayer.of(player).setComponent(new Pos1Message(regionSelector, player, session, pos1));
|
||||
}
|
||||
regionSelector.selectPrimary(pos1, PermissiveSelectorLimits.getInstance());
|
||||
WorldEditRenderer.renderPlayer(player);
|
||||
}
|
||||
@@ -362,9 +337,13 @@ public class SteamEditWand implements Listener {
|
||||
}
|
||||
|
||||
private void selectPos2(RegionSelector regionSelector, Player player, LocalSession session, BlockVector3 pos2) {
|
||||
try {
|
||||
if (regionSelector.getVertices().size() == 2 && !pos2.equals(regionSelector.getVertices().get(1))) {
|
||||
SWPlayer.of(player).setComponent(new Pos2Message(regionSelector, player, session, pos2));
|
||||
}
|
||||
} catch (IncompleteRegionException e) {
|
||||
SWPlayer.of(player).setComponent(new Pos2Message(regionSelector, player, session, pos2));
|
||||
}
|
||||
regionSelector.selectSecondary(pos2, PermissiveSelectorLimits.getInstance());
|
||||
WorldEditRenderer.renderPlayer(player);
|
||||
}
|
||||
@@ -377,30 +356,4 @@ public class SteamEditWand implements Listener {
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
public ServerboundMovePlayerPacket movePlayerPacket(Player player, ServerboundMovePlayerPacket packet) {
|
||||
SWPlayer swPlayer = SWPlayer.of(player);
|
||||
|
||||
if (noWand(player)) {
|
||||
swPlayer.removeComponent(SWEditCursor.class);
|
||||
return packet;
|
||||
}
|
||||
|
||||
double distance = WorldEditRendererCUIEditor.Type.CURSOR.getWidth(player).blocks;
|
||||
RayTraceResult result = player.rayTraceBlocks(distance, FluidCollisionMode.NEVER);
|
||||
Block block = result == null ? null : result.getHitBlock();
|
||||
if (result == null || block == null) {
|
||||
swPlayer.removeComponent(SWEditCursor.class);
|
||||
return packet;
|
||||
}
|
||||
|
||||
if (!swPlayer.hasComponent(SWEditCursor.class)) {
|
||||
swPlayer.setComponent(new SWEditCursor());
|
||||
}
|
||||
|
||||
swPlayer.getComponent(SWEditCursor.class).ifPresent(cursor -> {
|
||||
cursor.setLocation(block.getLocation());
|
||||
});
|
||||
return packet;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,9 @@ public class SWScoreboard {
|
||||
}
|
||||
|
||||
public void removeScoreboard(Player player) {
|
||||
if (player.getScoreboard().getObjective(SIDEBAR) != null) {
|
||||
player.getScoreboard().getObjective(SIDEBAR).unregister();
|
||||
}
|
||||
playerBoards.remove(player);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,3 @@ DEALER_REDSTONE = §eRedstone
|
||||
DEALER_BLOCKS = §eBlocks
|
||||
DEALER_TOOLS = §eTools
|
||||
DEALER_ANGLES = §eAngle Blocks
|
||||
|
||||
PARTICIPANT_CHAT = {0} {1}§8» §7{2}
|
||||
SPECTATOR_CHAT = §7{0}§8» §7{1}
|
||||
@@ -22,7 +22,6 @@ package de.steamwar.tntleague.events
|
||||
import de.steamwar.tntleague.config.TNTLeagueWorldConfig
|
||||
import de.steamwar.tntleague.game.TNTLeagueGame
|
||||
import de.steamwar.tntleague.game.TNTLeagueTeam
|
||||
import de.steamwar.tntleague.message
|
||||
import de.steamwar.tntleague.plugin
|
||||
import org.bukkit.GameMode
|
||||
import org.bukkit.Material
|
||||
@@ -90,20 +89,4 @@ object GlobalListener : Listener {
|
||||
null -> e.respawnLocation = TNTLeagueWorldConfig.lobby
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@EventHandler
|
||||
fun handlePlayerChat(event: AsyncPlayerChatEvent) {
|
||||
val player = event.player
|
||||
val msg = event.message
|
||||
|
||||
val fightTeam = TNTLeagueGame.getTeam(player)
|
||||
if (fightTeam != null) {
|
||||
message.broadcastPrefixless("PARTICIPANT_CHAT", fightTeam.name, player.name, msg)
|
||||
} else {
|
||||
message.broadcastPrefixless("SPECTATOR_CHAT", player.name, msg)
|
||||
}
|
||||
|
||||
event.isCancelled = true
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,3 @@ GAME_TIME = §a{0}:{1}
|
||||
CATCH_UP_WARNING = §4!! §cYou should catch up §4!!
|
||||
|
||||
COMMAND_START = §aThe game will start soon§8!
|
||||
|
||||
PARTICIPANT_CHAT = {0} {1}§8» §7{2}
|
||||
SPECTATOR_CHAT = §7{0}§8» §7{1}
|
||||
@@ -20,19 +20,15 @@
|
||||
package de.steamwar.towerrun.listener;
|
||||
|
||||
import de.steamwar.linkage.Linked;
|
||||
import de.steamwar.towerrun.TowerRun;
|
||||
import de.steamwar.towerrun.config.WorldConfig;
|
||||
import de.steamwar.towerrun.game.TowerRunGame;
|
||||
import de.steamwar.towerrun.game.TowerRunPlayer;
|
||||
import de.steamwar.towerrun.state.GameState;
|
||||
import de.steamwar.towerrun.state.GameStateBukkitListener;
|
||||
import de.steamwar.towerrun.state.GameStates;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.GameMode;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.AsyncPlayerChatEvent;
|
||||
import org.bukkit.event.player.PlayerMoveEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
import org.bukkit.event.player.PlayerRespawnEvent;
|
||||
@@ -71,25 +67,4 @@ public class GlobalListener extends GameStateBukkitListener implements Listener
|
||||
public void onPlayerRespawn(PlayerRespawnEvent event) {
|
||||
event.setRespawnLocation(WorldConfig.SPAWN);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onAsyncPlayerChat(AsyncPlayerChatEvent event) {
|
||||
TowerRunPlayer player = TowerRunPlayer.get(event.getPlayer());
|
||||
if (GameState.getCurrentState() == GameStates.RUNNING && (TowerRunGame.isAlive(player) || TowerRunGame.isEscaped(player))) {
|
||||
String prefix;
|
||||
if (TowerRunGame.isAlive(player)) {
|
||||
if (event.getPlayer().getGameMode() == GameMode.SPECTATOR) {
|
||||
prefix = "§c☠";
|
||||
} else {
|
||||
prefix = "§6❤";
|
||||
}
|
||||
} else {
|
||||
prefix = "§a✔";
|
||||
}
|
||||
TowerRun.getMessage().broadcastPrefixless("PARTICIPANT_CHAT", prefix, event.getPlayer().getName(), event.getMessage());
|
||||
} else {
|
||||
TowerRun.getMessage().broadcastPrefixless("SPECTATOR_CHAT", event.getPlayer().getName(), event.getMessage());
|
||||
}
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,14 +138,16 @@ public class Subserver {
|
||||
writer.println(command);
|
||||
}
|
||||
|
||||
public void sleep() {
|
||||
try {
|
||||
long pid = process.pid();
|
||||
private ProcessHandle serverProcess() {
|
||||
if (checkpoint) {
|
||||
pid = process.children().findAny().map(ProcessHandle::pid).orElse(pid);
|
||||
return process.children().findAny().orElseGet(process::toHandle);
|
||||
}
|
||||
return process.toHandle();
|
||||
}
|
||||
|
||||
Runtime.getRuntime().exec(new String[]{"kill", "-SIGUSR1", Long.toString(pid)});
|
||||
public void sleep() {
|
||||
try {
|
||||
Runtime.getRuntime().exec(new String[]{"kill", "-SIGUSR1", Long.toString(serverProcess().pid())});
|
||||
} catch (IOException e) {
|
||||
logger.log(Level.SEVERE, "Failed to send SIGUSR1 to subserver.", e);
|
||||
}
|
||||
@@ -162,7 +164,7 @@ public class Subserver {
|
||||
|
||||
public void stop() {
|
||||
try {
|
||||
process.destroy();
|
||||
serverProcess().destroy();
|
||||
if (!process.waitFor(1, TimeUnit.MINUTES)) {
|
||||
forceStop();
|
||||
} else if (thread.isAlive()) {
|
||||
@@ -177,6 +179,7 @@ public class Subserver {
|
||||
public void forceStop() {
|
||||
try {
|
||||
logger.log(Level.SEVERE, () -> serverName + " did not stop correctly, forcibly stopping!");
|
||||
serverProcess().destroyForcibly();
|
||||
process.destroyForcibly();
|
||||
|
||||
if (thread.isAlive()) thread.join();
|
||||
|
||||
@@ -430,7 +430,6 @@ TEAM_HELP_NAME = §8/§7team changename §8- §7Change your team name.
|
||||
TEAM_HELP_COLOR = §8/§7team changecolor §8- §7Change your team color.
|
||||
TEAM_HELP_LEADER = §8/§7team promote §8- §7Promote someone to leader.
|
||||
TEAM_HELP_STEP_BACK = §8/§7team stepback §8- §7Demote yourself from leader.
|
||||
TEAM_HELP_SERVER = §8/§7team server §8[§eIP/address§8] §8(§7port§8) §8- §7Set the address for your teamserver.
|
||||
|
||||
#Team Create
|
||||
TEAM_CREATE_USAGE = §8/§7team create §8[§eteam shortcut§8] §8[§eteam name§8]
|
||||
@@ -574,12 +573,59 @@ CHAT_BC_USAGE = §8/§7bc §8[§emessage§8]
|
||||
CHAT_NO_RECEIVER = §cNobody receives your message
|
||||
CHAT_EMPTY = §cDon\'t write meaningless empty messages.
|
||||
|
||||
CHAT_SERVERTEAM = §8STC §e{0}§8» §f{2}
|
||||
CHAT_DISCORD_SERVERTEAM = §8STC §e{0}§8» §f{2}
|
||||
CHAT_GLOBAL = {3}{4}{5}{6}{0}§8» {7}{2}
|
||||
CHAT_DISCORD_GLOBAL = {3}{4}{5}{6}{0}§8» {7}{2}
|
||||
CHAT_TEAM = §8TC §e{0}§8» §f{2}
|
||||
CHAT_MSG = §e{0}§8»§e{1} §7{2}
|
||||
CHAT_SERVERTEAM_SENDER = §e{0}
|
||||
CHAT_SERVERTEAM = §8» §f{2}
|
||||
CHAT_GLOBAL_SENDER = {3}{4}{5}{6}{0}
|
||||
CHAT_GLOBAL = §8» {7}{2}
|
||||
CHAT_TEAM_SENDER = §e{0}
|
||||
CHAT_TEAM = §8» §f{2}
|
||||
CHAT_MSG_SENDER = §e{0}
|
||||
CHAT_MSG_SEPARATOR = §8»
|
||||
CHAT_MSG_RECEIVER = §e{0}
|
||||
CHAT_MSG = §7{0}
|
||||
CHAT_SPECIAL_ALERT = §e{0}§8»§e{1} §7{2}
|
||||
CHAT_FIGHT_PARTICIPANT_SENDER = {0}{1}
|
||||
CHAT_FIGHT_PARTICIPANT = §8» §7{0}
|
||||
CHAT_FIGHT_SPECTATOR_SENDER = §7{1}
|
||||
CHAT_FIGHT_SPECTATOR = §8» §7{0}
|
||||
CHAT_FIGHT_REFEREE_SENDER = §e{1}
|
||||
CHAT_FIGHT_REFEREE = §8» §e{0}
|
||||
CHAT_FIGHT_TEAM = §8» §f{0}
|
||||
CHAT_ICON = {0}{1}
|
||||
CHAT_HOVER_TEAM = §7Team: §e{0}\n§7Prefix: §r{1}{2}
|
||||
CHAT_HOVER_LOCAL = {0}\n§7Server: §e{1}
|
||||
CHAT_ICON_GLOBAL = 🌍
|
||||
CHAT_ICON_LOCAL = ⌂
|
||||
CHAT_ICON_FIGHT = ⚔
|
||||
CHAT_ICON_FIGHT_FOREIGN = ⚔ §8{0}
|
||||
CHAT_ICON_FIGHT_TEAM = {0}⚑
|
||||
CHAT_ICON_FIGHT_TEAM_FOREIGN = {0}⚑ §8{1}
|
||||
CHAT_ICON_TEAM = 👥
|
||||
CHAT_ICON_SERVERTEAM = ⚙
|
||||
CHAT_ICON_MSG = ✉
|
||||
CHAT_CHANNEL_GLOBAL = §eGlobal
|
||||
CHAT_CHANNEL_ARENA = §eFight
|
||||
CHAT_CHANNEL_FIGHT_TEAM = §e{0} {1}
|
||||
CHAT_CHANNEL_TEAM = §eTeamChat
|
||||
CHAT_CHANNEL_SERVERTEAM = §eServerTeamChat
|
||||
CHAT_CHANNEL_MSG = §eMSG §7{0}
|
||||
CHAT_CHANNEL_ALERT = §cAlert
|
||||
CHAT_CHANNEL_LOCAL = §eLocal
|
||||
CHAT_CHANNEL_DEFAULT = §ais your default channel
|
||||
CHAT_CHANNEL_NOT_SWITCHABLE = §cYou cannot write into this channel by default.
|
||||
CHAT_NO_WRITE = §cYou cannot write into this channel.
|
||||
CHAT_FIGHT_NO_TEAM = §cYou are not in a fight team.
|
||||
GLOBAL_USAGE = §8/§7global §8[§emessage§8]
|
||||
CHAT_USAGE = §8/§7chat §8[§echannel§8] [§ejoin§8|§eleave§8]
|
||||
CHAT_MSG_USAGE = §8/§7chat §8[§echannel§8] §7msg §8[§emessage§8]
|
||||
CHAT_ARENAS_USAGE = §8/§7chat §7arenas §8[§ejoin§8|§eleave§8]
|
||||
CHAT_ARENAS_JOINED = §7You now read the chats of all fights.
|
||||
CHAT_ARENAS_LEFT = §7You no longer read the chats of all fights.
|
||||
CHAT_GLOBAL_AFTER_WRITING = §7You read §e{0} §7on this server until you leave it.
|
||||
CHAT_CHANNEL_UNKNOWN = §cUnknown chat channel.
|
||||
CHAT_CHANNEL_JOINED = §7You are reading §e{0} §7again.
|
||||
CHAT_CHANNEL_LEFT = §7You no longer read §e{0}§7.
|
||||
CHAT_CHANNEL_LEAVE_DEFAULT = §cYou cannot leave the channel you are writing into. Switch to another channel first.
|
||||
|
||||
#CheckListner
|
||||
CHECK_UNCHECKED = §7You still have §e{0} §7unchecked schematic§8(§7s§8)!
|
||||
|
||||
@@ -399,7 +399,6 @@ TEAM_HELP_NAME = §8/§7team changename §8- §7Ändere deinen Teamnamen.
|
||||
TEAM_HELP_COLOR = §8/§7team changecolor §8- §7Ändere deine Teamfarbe.
|
||||
TEAM_HELP_LEADER = §8/§7team promote §8- §7Ernenne jemanden zum Teamleader.
|
||||
TEAM_HELP_STEP_BACK = §8/§7team stepback §8- §7Tritt als Leader zurück.
|
||||
TEAM_HELP_SERVER = §8/§7team server §8[§eIP/Adresse§8] §8(§7Port§8) §8- §7Setzt Adresse des Teamservers.
|
||||
|
||||
#Team Create
|
||||
TEAM_CREATE_USAGE = §8/§7team create §8[§eTeamkürzel§8] §8[§eTeamname§8]
|
||||
@@ -539,12 +538,59 @@ CHAT_BC_USAGE = §8/§7bc §8[§eNachricht§8]
|
||||
CHAT_NO_RECEIVER = §cNiemand empfängt deine Nachricht
|
||||
CHAT_EMPTY = §cSchreibe keine inhaltslosen Nachrichten.
|
||||
|
||||
CHAT_SERVERTEAM = §8STC §e{0}§8» §f{2}
|
||||
CHAT_DISCORD_SERVERTEAM = §8STC §e{0}§8» §f{2}
|
||||
CHAT_GLOBAL = {3}{4}{5}{6}{0}§8» {7}{2}
|
||||
CHAT_DISCORD_GLOBAL = {3}{4}{5}{6}{0}§8» {7}{2}
|
||||
CHAT_TEAM = §8TC §e{0}§8» §f{2}
|
||||
CHAT_MSG = §e{0}§8»§e{1} §7{2}
|
||||
CHAT_SERVERTEAM_SENDER = §e{0}
|
||||
CHAT_SERVERTEAM = §8» §f{2}
|
||||
CHAT_GLOBAL_SENDER = {3}{4}{5}{6}{0}
|
||||
CHAT_GLOBAL = §8» {7}{2}
|
||||
CHAT_TEAM_SENDER = §e{0}
|
||||
CHAT_TEAM = §8» §f{2}
|
||||
CHAT_MSG_SENDER = §e{0}
|
||||
CHAT_MSG_SEPARATOR = §8»
|
||||
CHAT_MSG_RECEIVER = §e{0}
|
||||
CHAT_MSG = §7{0}
|
||||
CHAT_SPECIAL_ALERT = §e{0}§8»§e{1} §7{2}
|
||||
CHAT_FIGHT_PARTICIPANT_SENDER = {0}{1}
|
||||
CHAT_FIGHT_PARTICIPANT = §8» §7{0}
|
||||
CHAT_FIGHT_SPECTATOR_SENDER = §7{1}
|
||||
CHAT_FIGHT_SPECTATOR = §8» §7{0}
|
||||
CHAT_FIGHT_REFEREE_SENDER = §e{1}
|
||||
CHAT_FIGHT_REFEREE = §8» §e{0}
|
||||
CHAT_FIGHT_TEAM = §8» §f{0}
|
||||
CHAT_ICON = {0}{1}
|
||||
CHAT_HOVER_TEAM = §7Team: §e{0}\n§7Kürzel: §r{1}{2}
|
||||
CHAT_HOVER_LOCAL = {0}\n§7Server: §e{1}
|
||||
CHAT_ICON_GLOBAL = 🌍
|
||||
CHAT_ICON_LOCAL = ⌂
|
||||
CHAT_ICON_FIGHT = ⚔
|
||||
CHAT_ICON_FIGHT_FOREIGN = ⚔ §8{0}
|
||||
CHAT_ICON_FIGHT_TEAM = {0}⚑
|
||||
CHAT_ICON_FIGHT_TEAM_FOREIGN = {0}⚑ §8{1}
|
||||
CHAT_ICON_TEAM = 👥
|
||||
CHAT_ICON_SERVERTEAM = ⚙
|
||||
CHAT_ICON_MSG = ✉
|
||||
CHAT_CHANNEL_GLOBAL = §eGlobal
|
||||
CHAT_CHANNEL_ARENA = §eFight
|
||||
CHAT_CHANNEL_FIGHT_TEAM = §e{0} {1}
|
||||
CHAT_CHANNEL_TEAM = §eTeamChat
|
||||
CHAT_CHANNEL_SERVERTEAM = §eServerTeamChat
|
||||
CHAT_CHANNEL_MSG = §eMSG §7{0}
|
||||
CHAT_CHANNEL_ALERT = §cAlert
|
||||
CHAT_CHANNEL_LOCAL = §eLokal
|
||||
CHAT_CHANNEL_DEFAULT = §aist dein Standardkanal
|
||||
CHAT_CHANNEL_NOT_SWITCHABLE = §cIn diesen Kanal kannst du nicht standardmäßig schreiben.
|
||||
CHAT_NO_WRITE = §cDu kannst nicht in diesen Kanal schreiben.
|
||||
CHAT_FIGHT_NO_TEAM = §cDu bist in keinem Kampfteam.
|
||||
GLOBAL_USAGE = §8/§7global §8[§eNachricht§8]
|
||||
CHAT_USAGE = §8/§7chat §8[§eKanal§8] [§ejoin§8|§eleave§8]
|
||||
CHAT_MSG_USAGE = §8/§7chat §8[§eKanal§8] §7msg §8[§eNachricht§8]
|
||||
CHAT_ARENAS_USAGE = §8/§7chat §7arenas §8[§ejoin§8|§eleave§8]
|
||||
CHAT_ARENAS_JOINED = §7Du liest jetzt die Chats aller Fights mit.
|
||||
CHAT_ARENAS_LEFT = §7Du liest die Chats aller Fights nicht mehr mit.
|
||||
CHAT_GLOBAL_AFTER_WRITING = §7Du liest §e{0} §7auf diesem Server mit, bis du ihn verlässt.
|
||||
CHAT_CHANNEL_UNKNOWN = §cUnbekannter Chatkanal.
|
||||
CHAT_CHANNEL_JOINED = §7Du liest §e{0} §7wieder mit.
|
||||
CHAT_CHANNEL_LEFT = §7Du liest §e{0} §7nicht mehr mit.
|
||||
CHAT_CHANNEL_LEAVE_DEFAULT = §cDu kannst den Kanal, in den du schreibst, nicht verlassen. Wechsle zuerst in einen anderen Kanal.
|
||||
|
||||
#CheckListner
|
||||
CHECK_UNCHECKED = §7Du hast noch §e{0} §7ungeprüfte Schematic§8(§7s§8)!
|
||||
|
||||
@@ -23,8 +23,6 @@ import com.velocitypowered.api.command.CommandSource;
|
||||
import com.velocitypowered.api.event.ResultedEvent;
|
||||
import com.velocitypowered.api.event.connection.LoginEvent;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.proxy.ServerConnection;
|
||||
import de.steamwar.persistent.Subserver;
|
||||
import de.steamwar.sql.SteamwarUser;
|
||||
import de.steamwar.sql.UserPerm;
|
||||
import de.steamwar.velocitycore.VelocityCore;
|
||||
@@ -61,13 +59,6 @@ public interface Chatter {
|
||||
return new ChatterGroup(allStream());
|
||||
}
|
||||
|
||||
static ChatterGroup globalChat() {
|
||||
return new ChatterGroup(Stream.concat(Stream.of(Chatter.console()), allPlayers().filter(player -> {
|
||||
Subserver subserver = Subserver.getSubserver(player);
|
||||
return !(Subserver.isArena(subserver) && subserver.getServer() == player.getCurrentServer().map(ServerConnection::getServerInfo).orElse(null));
|
||||
}).map(Chatter::of)));
|
||||
}
|
||||
|
||||
static ChatterGroup serverteam() {
|
||||
return new ChatterGroup(allStream()
|
||||
.filter(player -> player.user().hasPerm(UserPerm.TEAM))
|
||||
|
||||
@@ -31,8 +31,10 @@ import net.kyori.adventure.text.event.ClickEvent;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static de.steamwar.persistent.Storage.eventServer;
|
||||
@@ -43,8 +45,15 @@ public class EventStarter {
|
||||
@Getter
|
||||
private static final Map<Integer, String> spectatePorts = new HashMap<>();
|
||||
|
||||
public static void addSpectateServer(int port, String command) {
|
||||
private static final Set<String> spectateServers = new HashSet<>();
|
||||
|
||||
public static void addSpectateServer(int port, String command, String serverName) {
|
||||
spectatePorts.put(port, command);
|
||||
spectateServers.add(serverName);
|
||||
}
|
||||
|
||||
public static boolean isSpectateServer(String serverName) {
|
||||
return spectateServers.contains(serverName);
|
||||
}
|
||||
|
||||
public EventStarter() {
|
||||
|
||||
@@ -236,7 +236,7 @@ public class VelocityCore implements ReloadablePlugin {
|
||||
String cmd = cmds.remove(0);
|
||||
|
||||
if (server.getSpectatePort() != 0) {
|
||||
EventStarter.addSpectateServer(server.getSpectatePort(), cmd);
|
||||
EventStarter.addSpectateServer(server.getSpectatePort(), cmd, entry.getKey());
|
||||
}
|
||||
|
||||
new ServerSwitchCommand(cmd, entry.getKey(), server.getSpectatePort() != 0, cmds.toArray(new String[0]));
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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.velocitycore.chat;
|
||||
|
||||
import de.steamwar.messages.Chatter;
|
||||
import de.steamwar.messages.Message;
|
||||
import de.steamwar.sql.UserPerm;
|
||||
import net.kyori.adventure.text.Component;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class AlertChannel extends ChatChannel {
|
||||
|
||||
@Override
|
||||
public boolean switchable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message tag(Chatter receiver) {
|
||||
return new Message("CHAT_CHANNEL_ALERT");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<Chatter> readers() {
|
||||
return Arrays.stream(Chatter.broadcast().getChatters());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canWrite(Chatter sender) {
|
||||
if (!sender.user().hasPerm(UserPerm.MODERATION)) {
|
||||
sender.system("CHAT_NO_WRITE");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Component format(Chatter receiver, Chatter sender, String message) {
|
||||
return receiver.parse(true, new Message("ALERT", message.replace('&', '§')));
|
||||
}
|
||||
|
||||
/**
|
||||
* Alerts are system messages: they are shown even with hidden chat and bypass the chat filter.
|
||||
*/
|
||||
@Override
|
||||
public void send(Chatter sender, String message, boolean mirror) {
|
||||
if (!canWrite(sender)) return;
|
||||
|
||||
readers().forEach(reader -> reader.sendMessage(format(reader, sender, message)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* 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.velocitycore.chat;
|
||||
|
||||
import de.steamwar.messages.Chatter;
|
||||
import de.steamwar.messages.Message;
|
||||
import de.steamwar.sql.AuditLog;
|
||||
import de.steamwar.sql.SteamwarUser;
|
||||
import de.steamwar.sql.UserPerm;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.event.ClickEvent;
|
||||
import net.kyori.adventure.text.event.HoverEvent;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public abstract class ChatChannel {
|
||||
|
||||
/**
|
||||
* @return the id under which this channel is stored as default channel of a player, null if it cannot be stored
|
||||
*/
|
||||
public String persistId() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean switchable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the channel name shown in channel related system messages
|
||||
*/
|
||||
public abstract Message tag(Chatter receiver);
|
||||
|
||||
/**
|
||||
* @return the text shown when hovering the channel icon
|
||||
*/
|
||||
public Message hover(Chatter receiver) {
|
||||
return tag(receiver);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the icon always shown in front of messages of this channel, null if the message format already shows the channel.
|
||||
* The icon is highlighted if this is the default channel of the receiver.
|
||||
*/
|
||||
protected Message icon(Chatter receiver) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the text put into the chat input when clicking the channel icon to write a single message, null if there is none
|
||||
*/
|
||||
protected String suggestion(Chatter receiver) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the channel icon with its name on hover and its write command on click
|
||||
*/
|
||||
Component iconComponent(Chatter receiver, String color) {
|
||||
Message icon = icon(receiver);
|
||||
Component component = icon == null ? receiver.parse(tag(receiver)) : receiver.parse(new Message("CHAT_ICON", color, icon));
|
||||
component = component.hoverEvent(HoverEvent.showText(receiver.parse(hover(receiver))));
|
||||
|
||||
String suggestion = suggestion(receiver);
|
||||
if (suggestion != null) {
|
||||
component = component.clickEvent(ClickEvent.suggestCommand(suggestion));
|
||||
}
|
||||
return component;
|
||||
}
|
||||
|
||||
public abstract Stream<Chatter> readers();
|
||||
|
||||
/**
|
||||
* Checks if the sender is allowed to write into this channel and informs the sender otherwise.
|
||||
*/
|
||||
public abstract boolean canWrite(Chatter sender);
|
||||
|
||||
protected abstract Component format(Chatter receiver, Chatter sender, String message);
|
||||
|
||||
/**
|
||||
* Called after a message was delivered to all readers.
|
||||
*
|
||||
* @param mirror false if the message originates from the mirror of this channel and should not be sent back
|
||||
* @return true if the message was delivered to a receiver outside the game
|
||||
*/
|
||||
protected boolean delivered(Chatter sender, String message, boolean mirror) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void audit(SteamwarUser sender, String message) {
|
||||
AuditLog.createChat(AuditLog.SERVER_NAME_VELOCITY, null, sender, message);
|
||||
}
|
||||
|
||||
public void send(Chatter sender, String message) {
|
||||
send(sender, message, true);
|
||||
}
|
||||
|
||||
public void send(Chatter sender, String message, boolean mirror) {
|
||||
if (!canWrite(sender)) return;
|
||||
|
||||
SteamwarUser user = sender.user();
|
||||
String coloredMessage = user.hasPerm(UserPerm.COLOR_CHAT) ? message.replace('&', '§') : message;
|
||||
if (ChatFilter.filtered(sender, coloredMessage)) return;
|
||||
|
||||
Map<Integer, Chatter> receivers = new LinkedHashMap<>();
|
||||
readers().forEach(reader -> receivers.putIfAbsent(reader.user().getId(), reader));
|
||||
receivers.putIfAbsent(user.getId(), sender);
|
||||
|
||||
boolean noReceiver = true;
|
||||
for (Chatter receiver : receivers.values()) {
|
||||
if (!receiver.chatShown()) continue;
|
||||
|
||||
Component msg = ChatFormat.withMentionHovers(receiver, format(receiver, sender, coloredMessage));
|
||||
if (icon(receiver) != null) {
|
||||
Component icon = iconComponent(receiver, ChatManager.getDefault(receiver) == this ? "§e" : "§7");
|
||||
msg = Component.text().append(icon).append(Component.text(" ")).append(msg).build();
|
||||
}
|
||||
receiver.sendMessage(msg);
|
||||
|
||||
if (receiver.user().getId() != user.getId()) noReceiver = false;
|
||||
}
|
||||
|
||||
if (!delivered(sender, coloredMessage, mirror) && noReceiver) {
|
||||
sender.system("CHAT_NO_RECEIVER");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.velocitycore.chat;
|
||||
|
||||
import de.steamwar.messages.Chatter;
|
||||
import de.steamwar.messages.Message;
|
||||
import de.steamwar.sql.Punishment;
|
||||
import de.steamwar.sql.SteamwarUser;
|
||||
import de.steamwar.sql.UserPerm;
|
||||
import de.steamwar.velocitycore.VelocityCore;
|
||||
import de.steamwar.velocitycore.commands.PunishmentCommand;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class ChatFilter {
|
||||
|
||||
private ChatFilter() {}
|
||||
|
||||
public static boolean filtered(Chatter sender, String message) {
|
||||
if (!sender.chatShown()) {
|
||||
sender.system("CHAT_RECEIVE");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (message.replace("§[a-f0-9klmno]", "").trim().isEmpty()) {
|
||||
sender.system("CHAT_EMPTY");
|
||||
return true;
|
||||
}
|
||||
|
||||
SteamwarUser user = sender.user();
|
||||
if (!user.hasPerm(UserPerm.TEAM) && (message.contains("http:") || message.contains("https:") || message.contains("www."))) {
|
||||
sender.system("CHAT_NO_LINKS");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (PunishmentCommand.isPunishedWithMessage(sender, Punishment.PunishmentType.Mute)) return true;
|
||||
|
||||
if (message.contains("LIXFEL")) {
|
||||
specialAlert(sender, "Lixfel", "CHAT_LIXFEL_", 3, 6, 11, 12, 15);
|
||||
}
|
||||
if (message.contains("YOYONOW")) {
|
||||
specialAlert(sender, "YoyoNow", "CHAT_YOYONOW_", 3, 6, 11, 12);
|
||||
}
|
||||
if (message.contains("CHAOSCAOT")) {
|
||||
specialAlert(sender, "Chaoscaot", "CHAT_CHAOSCAOT_", 3, 6, 11, 12, 15, 17);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void specialAlert(Chatter sender, String name, String baseMessage, int... delay) {
|
||||
sender.system("CHAT_LIXFEL_ACTION_BAR");
|
||||
for (int i = 0; i < delay.length; i++) {
|
||||
int finalI = i;
|
||||
VelocityCore.schedule(() -> sender.prefixless("CHAT_SPECIAL_ALERT", name, sender.user(), new Message(baseMessage + (finalI + 1)))).delay(delay[i], TimeUnit.SECONDS).schedule();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* 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.velocitycore.chat;
|
||||
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import de.steamwar.messages.Chatter;
|
||||
import de.steamwar.messages.Message;
|
||||
import de.steamwar.network.packets.server.PingPacket;
|
||||
import de.steamwar.sql.SteamwarUser;
|
||||
import de.steamwar.sql.Team;
|
||||
import de.steamwar.sql.UserPerm;
|
||||
import de.steamwar.velocitycore.network.NetworkSender;
|
||||
import de.steamwar.velocitycore.util.SteamwarPrefix;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.TextReplacementConfig;
|
||||
import net.kyori.adventure.text.event.HoverEvent;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ChatFormat {
|
||||
|
||||
private static final Pattern MENTION = Pattern.compile("@(\\w{1,16})");
|
||||
|
||||
private ChatFormat() {}
|
||||
|
||||
/**
|
||||
* Formats a message with the rank and team prefixes of the sender. The sender part ({@code <format>_SENDER})
|
||||
* shows the team of the sender on hover.
|
||||
*/
|
||||
public static Component player(String format, Chatter receiver, Chatter sender, Chatter msgReceiver, String message) {
|
||||
SteamwarUser user = sender.user();
|
||||
UserPerm.Prefix prefix = user.prefix();
|
||||
|
||||
String teamPrefix = "";
|
||||
if (user.hasPerm(UserPerm.TEAM) && useSwPrefix(user)) {
|
||||
teamPrefix = "§eS§8W ";
|
||||
} else if (user.getTeam() != 0) {
|
||||
Team team = Team.byId(user.getTeam());
|
||||
teamPrefix = "§" + team.getTeamColor() + team.getTeamKuerzel() + " ";
|
||||
}
|
||||
|
||||
String chatColorCode = user.hasPerm(UserPerm.TEAM) ? "§f" : "§7";
|
||||
Object[] params = {
|
||||
user,
|
||||
msgReceiver == null ? receiver : msgReceiver,
|
||||
highlightMentions(message, chatColorCode, receiver),
|
||||
teamPrefix,
|
||||
"",
|
||||
prefix == UserPerm.emptyPrefix ? "§f" : prefix.getColorCode(),
|
||||
prefix.getChatPrefix().length() == 0 ? "§f" : prefix.getChatPrefix() + " ",
|
||||
chatColorCode};
|
||||
|
||||
Component senderPart = withTeamHover(receiver, user, receiver.parse(new Message(format + "_SENDER", params)));
|
||||
return Component.text().append(senderPart).append(receiver.parse(new Message(format, params))).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a fight chat message. The fight team name is only hoverable in event fights, where it is the actual team.
|
||||
*
|
||||
* @param coloredTeamName the fight team of the sender, null if the sender is not in a fight team
|
||||
*/
|
||||
public static Component fight(Chatter receiver, RegisteredServer server, Chatter sender, String coloredTeamName, String senderFormat, String format, String message) {
|
||||
TextComponent.Builder line = Component.text();
|
||||
String nameColor = "";
|
||||
if (coloredTeamName != null) {
|
||||
Component team = Chatter.SERIALIZER.deserialize(coloredTeamName);
|
||||
Team eventTeam = ChatManager.eventTeam(server, coloredTeamName);
|
||||
if (eventTeam != null) {
|
||||
team = team.hoverEvent(HoverEvent.showText(teamHover(receiver, eventTeam)));
|
||||
}
|
||||
line.append(team).append(Component.text(" "));
|
||||
nameColor = coloredTeamName.length() >= 2 && coloredTeamName.charAt(0) == '§' ? coloredTeamName.substring(0, 2) : "";
|
||||
}
|
||||
|
||||
line.append(withTeamHover(receiver, sender.user(), receiver.parse(new Message(senderFormat, nameColor, sender.user()))));
|
||||
line.append(receiver.parse(new Message(format, message)));
|
||||
return line.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the team of the user when hovering the component, the SteamWar team for server team members using the SW prefix.
|
||||
*/
|
||||
public static Component withTeamHover(Chatter receiver, SteamwarUser user, Component component) {
|
||||
if (useSwPrefix(user)) {
|
||||
return component.hoverEvent(HoverEvent.showText(receiver.parse(new Message("CHAT_HOVER_TEAM", "SteamWar", "§e", "S§8W"))));
|
||||
}
|
||||
if (user.getTeam() == 0) return component;
|
||||
|
||||
return component.hoverEvent(HoverEvent.showText(teamHover(receiver, Team.byId(user.getTeam()))));
|
||||
}
|
||||
|
||||
private static Component teamHover(Chatter receiver, Team team) {
|
||||
return receiver.parse(new Message("CHAT_HOVER_TEAM", team.getTeamName(), "§" + team.getTeamColor(), team.getTeamKuerzel()));
|
||||
}
|
||||
|
||||
private static boolean useSwPrefix(SteamwarUser user) {
|
||||
if (!user.hasPerm(UserPerm.TEAM)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
UserPerm.Prefix prefix = user.prefix();
|
||||
|
||||
boolean hasTeamPrefix = user.getTeam() != 0;
|
||||
boolean hasSwPrefix = prefix != UserPerm.emptyPrefix;
|
||||
|
||||
if (!hasSwPrefix) return false;
|
||||
if (!hasTeamPrefix) return true;
|
||||
|
||||
return SteamwarPrefix.usesSWPrefix(user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the team of every mentioned player ({@code @name}) on hover, like at the sender name.
|
||||
*/
|
||||
public static Component withMentionHovers(Chatter receiver, Component component) {
|
||||
return component.replaceText(TextReplacementConfig.builder()
|
||||
.match(MENTION)
|
||||
.replacement((match, builder) -> {
|
||||
SteamwarUser user = SteamwarUser.get(match.group(1));
|
||||
return user == null ? builder : withTeamHover(receiver, user, builder.build());
|
||||
})
|
||||
.build());
|
||||
}
|
||||
|
||||
public static String highlightMentions(String message, String returnColor, Chatter player) {
|
||||
if (!message.contains("@")) return message;
|
||||
|
||||
String mark = "@" + player.user().getUserName();
|
||||
return Arrays.stream(message.split(" ")).map(cur -> {
|
||||
if (cur.equalsIgnoreCase(mark) && player.getPlayer() != null) {
|
||||
NetworkSender.send(player.getPlayer(), new PingPacket(player.user().getId()));
|
||||
return "§e" + cur + returnColor;
|
||||
}
|
||||
return cur;
|
||||
}).collect(Collectors.joining(" "));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,514 @@
|
||||
/*
|
||||
* 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.velocitycore.chat;
|
||||
|
||||
import com.velocitypowered.api.event.Subscribe;
|
||||
import com.velocitypowered.api.event.connection.DisconnectEvent;
|
||||
import com.velocitypowered.api.event.player.ServerPostConnectEvent;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.proxy.ServerConnection;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import de.steamwar.linkage.Linked;
|
||||
import de.steamwar.messages.Chatter;
|
||||
import de.steamwar.messages.Message;
|
||||
import de.steamwar.messages.PlayerChatter;
|
||||
import de.steamwar.network.packets.common.FightInfoPacket;
|
||||
import de.steamwar.persistent.Subserver;
|
||||
import de.steamwar.sql.Event;
|
||||
import de.steamwar.sql.Referee;
|
||||
import de.steamwar.sql.Team;
|
||||
import de.steamwar.sql.SteamwarUser;
|
||||
import de.steamwar.sql.UserConfig;
|
||||
import de.steamwar.sql.UserPerm;
|
||||
import de.steamwar.velocitycore.EventStarter;
|
||||
import de.steamwar.velocitycore.VelocityCore;
|
||||
import de.steamwar.velocitycore.listeners.BasicListener;
|
||||
import de.steamwar.velocitycore.tablist.TablistManager;
|
||||
import lombok.Getter;
|
||||
import net.kyori.adventure.text.Component;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Keeps track of all chat channels and the default channel every player writes into.
|
||||
*/
|
||||
@Linked
|
||||
public class ChatManager extends BasicListener {
|
||||
|
||||
private static final String CONFIG_KEY = "chat.channel";
|
||||
private static final String HIDDEN_KEY = "chat.hidden";
|
||||
private static final String ARENAS_KEY = "chat.arenas";
|
||||
private static final long REFEREE_CACHE_MILLIS = 60_000;
|
||||
|
||||
@Getter
|
||||
private static final GlobalChannel global = new GlobalChannel();
|
||||
@Getter
|
||||
private static final ServerTeamChannel serverTeam = new ServerTeamChannel();
|
||||
@Getter
|
||||
private static final AlertChannel alert = new AlertChannel();
|
||||
|
||||
private static final Map<RegisteredServer, LocalChannel> locals = new ConcurrentHashMap<>();
|
||||
private static final Map<RegisteredServer, FightTeamChannel[]> fightTeams = new ConcurrentHashMap<>();
|
||||
private static final Map<Integer, TeamChannel> teams = new ConcurrentHashMap<>();
|
||||
private static final Set<DirectChannel> directs = ConcurrentHashMap.newKeySet();
|
||||
|
||||
private static final Map<UUID, ChatChannel> defaults = new ConcurrentHashMap<>();
|
||||
private static final Map<UUID, String> persisted = new ConcurrentHashMap<>();
|
||||
private static final Set<UUID> globalOnArena = ConcurrentHashMap.newKeySet();
|
||||
// Players reading the global chat on their current fight server until they leave it (after /global <text> or /chat global join)
|
||||
private static final Set<UUID> globalOnServer = ConcurrentHashMap.newKeySet();
|
||||
private static final Set<UUID> announced = ConcurrentHashMap.newKeySet();
|
||||
private static final Map<UUID, Set<RegisteredServer>> spies = new ConcurrentHashMap<>();
|
||||
private static final Map<UUID, Boolean> allArenas = new ConcurrentHashMap<>();
|
||||
private static final Map<UUID, Set<RegisteredServer>> leftArenas = new ConcurrentHashMap<>();
|
||||
private static final Map<UUID, Set<String>> hidden = new ConcurrentHashMap<>();
|
||||
|
||||
private static int refereeEvent = -1;
|
||||
private static long refereesLoaded = 0;
|
||||
private static Set<Integer> referees = Collections.emptySet();
|
||||
|
||||
public static LocalChannel local(RegisteredServer server) {
|
||||
locals.keySet().removeIf(ChatManager::unregistered);
|
||||
return locals.computeIfAbsent(server, LocalChannel::new);
|
||||
}
|
||||
|
||||
public static FightTeamChannel fightTeam(RegisteredServer server, boolean blue) {
|
||||
fightTeams.keySet().removeIf(ChatManager::unregistered);
|
||||
return fightTeams.computeIfAbsent(server, s -> new FightTeamChannel[]{new FightTeamChannel(s, true), new FightTeamChannel(s, false)})[blue ? 0 : 1];
|
||||
}
|
||||
|
||||
public static TeamChannel team(int team) {
|
||||
return teams.computeIfAbsent(team, TeamChannel::new);
|
||||
}
|
||||
|
||||
public static DirectChannel direct(UUID first, UUID second) {
|
||||
for (DirectChannel channel : directs) {
|
||||
if (channel.contains(first) && channel.partner(first).equals(second)) {
|
||||
return channel;
|
||||
}
|
||||
}
|
||||
|
||||
DirectChannel channel = new DirectChannel(first, second);
|
||||
directs.add(channel);
|
||||
return channel;
|
||||
}
|
||||
|
||||
private static boolean unregistered(RegisteredServer server) {
|
||||
return VelocityCore.getProxy().getServer(server.getServerInfo().getName()).orElse(null) != server;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true for arenas and event spectate servers, which use a fight chat instead of the global chat
|
||||
*/
|
||||
public static boolean isArena(RegisteredServer server) {
|
||||
return Subserver.isArena(Subserver.getSubserver(server.getServerInfo())) || EventStarter.isSpectateServer(server.getServerInfo().getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return all servers using a fight chat (arenas and event spectate servers)
|
||||
*/
|
||||
public static Stream<RegisteredServer> fightServers() {
|
||||
return VelocityCore.getProxy().getAllServers().stream().filter(ChatManager::isArena);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the local channel of the server the player is on, null while the player is on no server
|
||||
*/
|
||||
public static LocalChannel currentLocal(Player player) {
|
||||
return currentServer(player).map(ChatManager::local).orElse(null);
|
||||
}
|
||||
|
||||
static boolean onServer(Chatter chatter, RegisteredServer server) {
|
||||
Player player = chatter.getPlayer();
|
||||
return player != null && server.getPlayersConnected().contains(player);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the message into the channel or makes it the default channel of the player if there is no message.
|
||||
*/
|
||||
public static void sendOrSwitch(Chatter sender, ChatChannel channel, String[] message, String usage) {
|
||||
if (message.length > 0) {
|
||||
channel.send(sender, String.join(" ", message));
|
||||
} else if (sender instanceof PlayerChatter player) {
|
||||
switchTo(player, channel);
|
||||
} else {
|
||||
sender.system(usage);
|
||||
}
|
||||
}
|
||||
|
||||
private static Optional<RegisteredServer> currentServer(Player player) {
|
||||
return player.getCurrentServer().map(ServerConnection::getServer);
|
||||
}
|
||||
|
||||
static boolean readsGlobal(Player player) {
|
||||
UUID uuid = player.getUniqueId();
|
||||
return !currentServer(player).map(ChatManager::isArena).orElse(false) || globalOnArena.contains(uuid) || globalOnServer.contains(uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the global chat to a player who wrote into it on a fight server until they leave the server.
|
||||
*/
|
||||
public static void readGlobalAfterWriting(PlayerChatter player) {
|
||||
if (!currentServer(player.getPlayer()).map(ChatManager::isArena).orElse(false)) return;
|
||||
if (!reads(player, global.persistId())) return;
|
||||
|
||||
if (globalOnServer.add(player.user().getUUID())) {
|
||||
player.system("CHAT_GLOBAL_AFTER_WRITING", global.tag(player));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the moderators reading the fight chats of the server without being on it
|
||||
*/
|
||||
static Stream<Chatter> spies(RegisteredServer server) {
|
||||
return Chatter.allPlayers()
|
||||
.map(Chatter::of)
|
||||
.filter(chatter -> !onServer(chatter, server))
|
||||
.filter(chatter -> chatter.user().hasPerm(UserPerm.MODERATION))
|
||||
.filter(chatter -> readsArena(chatter.user().getUUID(), server))
|
||||
.map(Chatter.class::cast);
|
||||
}
|
||||
|
||||
private static boolean readsArena(UUID uuid, RegisteredServer server) {
|
||||
Set<RegisteredServer> joined = spies.get(uuid);
|
||||
if (joined != null && joined.contains(server)) return true;
|
||||
|
||||
Set<RegisteredServer> left = leftArenas.get(uuid);
|
||||
return readsAllArenas(uuid) && (left == null || !left.contains(server));
|
||||
}
|
||||
|
||||
private static boolean readsAllArenas(UUID uuid) {
|
||||
return allArenas.computeIfAbsent(uuid, u -> "true".equals(UserConfig.getConfig(u, ARENAS_KEY)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Lets a moderator read the fight chats of every fight server automatically, saved across relogs.
|
||||
*/
|
||||
public static void setReadAllArenas(PlayerChatter player, boolean read) {
|
||||
UUID uuid = player.user().getUUID();
|
||||
allArenas.put(uuid, read);
|
||||
leftArenas.remove(uuid);
|
||||
UserConfig.updatePlayerConfig(uuid, ARENAS_KEY, read ? "true" : null);
|
||||
player.system(read ? "CHAT_ARENAS_JOINED" : "CHAT_ARENAS_LEFT");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the user is a referee of the event fight running on the server
|
||||
*/
|
||||
static boolean isReferee(RegisteredServer server, int userId) {
|
||||
Subserver subserver = Subserver.getSubserver(server.getServerInfo());
|
||||
if (subserver == null || !EventStarter.getEventServer().containsValue(subserver)) return false;
|
||||
|
||||
Event event = Event.get();
|
||||
return event != null && referees(event.getEventID()).contains(userId);
|
||||
}
|
||||
|
||||
private static synchronized Set<Integer> referees(int eventId) {
|
||||
if (eventId != refereeEvent || System.currentTimeMillis() - refereesLoaded > REFEREE_CACHE_MILLIS) {
|
||||
referees = Referee.get(eventId);
|
||||
refereeEvent = eventId;
|
||||
refereesLoaded = System.currentTimeMillis();
|
||||
}
|
||||
return referees;
|
||||
}
|
||||
|
||||
private static Set<String> hidden(UUID uuid) {
|
||||
return hidden.computeIfAbsent(uuid, u -> {
|
||||
Set<String> channels = ConcurrentHashMap.newKeySet();
|
||||
String config = UserConfig.getConfig(u, HIDDEN_KEY);
|
||||
if (config != null && !config.isEmpty()) {
|
||||
channels.addAll(Arrays.asList(config.split(",")));
|
||||
}
|
||||
return channels;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @return false if the player left the channel with the given persist id as reader
|
||||
*/
|
||||
static boolean reads(Chatter chatter, String persistId) {
|
||||
Player player = chatter.getPlayer();
|
||||
return player == null || !hidden(player.getUniqueId()).contains(persistId);
|
||||
}
|
||||
|
||||
private static void saveHidden(UUID uuid) {
|
||||
Set<String> channels = hidden(uuid);
|
||||
UserConfig.updatePlayerConfig(uuid, HIDDEN_KEY, channels.isEmpty() ? null : String.join(",", channels));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the channel is the fight chat of an arena the player is not on, which moderators can read without joining the arena
|
||||
*/
|
||||
private static boolean isForeignArena(PlayerChatter player, ChatChannel channel) {
|
||||
return channel instanceof LocalChannel local && local.isArena() && !onServer(player, local.getServer());
|
||||
}
|
||||
|
||||
public static void join(PlayerChatter player, ChatChannel channel) {
|
||||
UUID uuid = player.user().getUUID();
|
||||
if (isForeignArena(player, channel)) {
|
||||
RegisteredServer server = ((LocalChannel) channel).getServer();
|
||||
spies.computeIfAbsent(uuid, u -> ConcurrentHashMap.newKeySet()).add(server);
|
||||
Set<RegisteredServer> left = leftArenas.get(uuid);
|
||||
if (left != null) left.remove(server);
|
||||
player.system("CHAT_CHANNEL_JOINED", channel.tag(player));
|
||||
return;
|
||||
}
|
||||
|
||||
if (hidden(uuid).remove(channel.persistId())) {
|
||||
saveHidden(uuid);
|
||||
}
|
||||
if (channel == global && currentServer(player.getPlayer()).map(ChatManager::isArena).orElse(false)) {
|
||||
globalOnServer.add(uuid);
|
||||
}
|
||||
player.system("CHAT_CHANNEL_JOINED", channel.tag(player));
|
||||
}
|
||||
|
||||
public static void leave(PlayerChatter player, ChatChannel channel) {
|
||||
UUID uuid = player.user().getUUID();
|
||||
if (isForeignArena(player, channel)) {
|
||||
RegisteredServer server = ((LocalChannel) channel).getServer();
|
||||
Set<RegisteredServer> servers = spies.get(uuid);
|
||||
if (servers != null) servers.remove(server);
|
||||
// Excludes the arena for this session while reading all arenas
|
||||
leftArenas.computeIfAbsent(uuid, u -> ConcurrentHashMap.newKeySet()).add(server);
|
||||
player.system("CHAT_CHANNEL_LEFT", channel.tag(player));
|
||||
return;
|
||||
}
|
||||
|
||||
String id = channel.persistId();
|
||||
if (id.equals(getDefault(player).persistId())) {
|
||||
player.system("CHAT_CHANNEL_LEAVE_DEFAULT");
|
||||
return;
|
||||
}
|
||||
|
||||
// On fight servers leaving only stops reading the global chat on this server, it does not leave the global chat everywhere
|
||||
if (channel == global && globalOnServer.remove(uuid)) {
|
||||
player.system("CHAT_CHANNEL_LEFT", channel.tag(player));
|
||||
return;
|
||||
}
|
||||
|
||||
if (hidden(uuid).add(id)) {
|
||||
saveHidden(uuid);
|
||||
}
|
||||
player.system("CHAT_CHANNEL_LEFT", channel.tag(player));
|
||||
}
|
||||
|
||||
public static FightInfoPacket getFightInfo(RegisteredServer server) {
|
||||
return TablistManager.getFightInfo(server);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the team of an event fight on the server whose colored name is shown, null if the server runs no event fight
|
||||
*/
|
||||
static Team eventTeam(RegisteredServer server, String coloredTeamName) {
|
||||
Subserver subserver = Subserver.getSubserver(server.getServerInfo());
|
||||
if (subserver == null) return null;
|
||||
|
||||
String kuerzel = coloredTeamName.replaceAll("§.", "");
|
||||
for (Map.Entry<Integer, Subserver> entry : new ArrayList<>(EventStarter.getEventServer().entrySet())) {
|
||||
if (entry.getValue() != subserver) continue;
|
||||
|
||||
Team team = Team.byId(entry.getKey());
|
||||
if (team != null && team.getTeamKuerzel().equals(kuerzel)) return team;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the fight teams of an arena. Players who just joined a fight team write into the fight chat by default.
|
||||
*/
|
||||
public static void updateFightInfo(RegisteredServer server, FightInfoPacket previous, FightInfoPacket packet) {
|
||||
|
||||
Set<Integer> joined = new HashSet<>(packet.getBluePlayers());
|
||||
joined.addAll(packet.getRedPlayers());
|
||||
if (previous != null) {
|
||||
previous.getBluePlayers().forEach(joined::remove);
|
||||
previous.getRedPlayers().forEach(joined::remove);
|
||||
}
|
||||
|
||||
for (Player player : server.getPlayersConnected()) {
|
||||
if (joined.contains(SteamwarUser.get(player.getUniqueId()).getId())) {
|
||||
setDefault(Chatter.of(player), local(server));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the channel the receiver writes into by default; non-player chatters (console, Discord) use the global chat
|
||||
*/
|
||||
public static ChatChannel getDefault(Chatter chatter) {
|
||||
Player player = chatter.getPlayer();
|
||||
if (player == null) return global;
|
||||
|
||||
return defaults.computeIfAbsent(player.getUniqueId(), uuid -> initialDefault(player, currentServer(player).orElse(null)));
|
||||
}
|
||||
|
||||
private static ChatChannel initialDefault(Player player, RegisteredServer server) {
|
||||
if (server != null && isArena(server)) return local(server);
|
||||
return resolvePersisted(player, server);
|
||||
}
|
||||
|
||||
private static ChatChannel resolvePersisted(Player player, RegisteredServer server) {
|
||||
String id = persisted.computeIfAbsent(player.getUniqueId(), uuid -> Optional.ofNullable(UserConfig.getConfig(uuid, CONFIG_KEY)).orElse(global.persistId()));
|
||||
SteamwarUser user = SteamwarUser.get(player.getUniqueId());
|
||||
return switch (id) {
|
||||
case "local" -> server != null && !isArena(server) ? local(server) : global;
|
||||
case "team" -> user.getTeam() != 0 ? team(user.getTeam()) : global;
|
||||
case "serverteam" -> user.hasPerm(UserPerm.TEAM) ? serverTeam : global;
|
||||
default -> global;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the channel the default channel of the player because the player chose it.
|
||||
*/
|
||||
public static void switchTo(PlayerChatter player, ChatChannel channel) {
|
||||
if (!channel.switchable() || isForeignArena(player, channel)) {
|
||||
player.system("CHAT_CHANNEL_NOT_SWITCHABLE");
|
||||
return;
|
||||
}
|
||||
if (!channel.canWrite(player)) return;
|
||||
|
||||
UUID uuid = player.user().getUUID();
|
||||
if (channel == global && currentServer(player.getPlayer()).map(ChatManager::isArena).orElse(false)) {
|
||||
globalOnArena.add(uuid);
|
||||
} else if (channel != global) {
|
||||
globalOnArena.remove(uuid);
|
||||
}
|
||||
|
||||
String id = channel.persistId();
|
||||
if (id != null && !id.equals(persisted.get(uuid))) {
|
||||
persisted.put(uuid, id);
|
||||
UserConfig.updatePlayerConfig(uuid, CONFIG_KEY, id);
|
||||
}
|
||||
if (id != null && hidden(uuid).remove(id)) {
|
||||
saveHidden(uuid);
|
||||
}
|
||||
|
||||
defaults.put(uuid, channel);
|
||||
notifyDefault(player, channel);
|
||||
}
|
||||
|
||||
private static void setDefault(PlayerChatter player, ChatChannel channel) {
|
||||
UUID uuid = player.user().getUUID();
|
||||
if (channel != global) globalOnArena.remove(uuid);
|
||||
|
||||
ChatChannel previous = defaults.put(uuid, channel);
|
||||
if (announced.add(uuid)) {
|
||||
notifyDefault(player, channel);
|
||||
} else if (previous != channel && !player.parseToPlain(previous.tag(player)).equals(player.parseToPlain(channel.tag(player)))) {
|
||||
notifyDefault(player, channel);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells the player which channel they write into, showing the channel icon with its name on hover.
|
||||
*/
|
||||
public static void notifyDefault(PlayerChatter player, ChatChannel channel) {
|
||||
player.sendMessage(Component.text()
|
||||
.append(channel.iconComponent(player, "§a"))
|
||||
.append(Component.text(" "))
|
||||
.append(player.parse(new Message("CHAT_CHANNEL_DEFAULT")))
|
||||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a chat message the player typed without a command.
|
||||
*/
|
||||
public static void chat(PlayerChatter sender, String message) {
|
||||
ChatChannel channel = getDefault(sender);
|
||||
if (channel instanceof TeamChannel team && team.getTeam() != sender.user().getTeam()) {
|
||||
channel = resolvePersisted(sender.getPlayer(), currentServer(sender.getPlayer()).orElse(null));
|
||||
setDefault(sender, channel);
|
||||
}
|
||||
|
||||
if (message.startsWith("+")) {
|
||||
channel = quickChannel(sender, channel);
|
||||
message = message.substring(1);
|
||||
}
|
||||
|
||||
channel.send(sender, message);
|
||||
channel.audit(sender.user(), message);
|
||||
}
|
||||
|
||||
/**
|
||||
* The channel a message prefixed with '+' is sent to: the local chat when writing into the global chat,
|
||||
* the fight team chat when writing into the local/fight chat.
|
||||
*/
|
||||
private static ChatChannel quickChannel(PlayerChatter sender, ChatChannel current) {
|
||||
RegisteredServer server = currentServer(sender.getPlayer()).orElse(null);
|
||||
if (server == null) return current;
|
||||
|
||||
if (current instanceof LocalChannel local) {
|
||||
FightTeamChannel blue = fightTeam(server, true);
|
||||
if (blue.isMember(sender)) return blue;
|
||||
|
||||
FightTeamChannel red = fightTeam(server, false);
|
||||
if (red.isMember(sender)) return red;
|
||||
|
||||
// Spectators on fight servers stay in the fight chat
|
||||
return local.isArena() ? local : global;
|
||||
}
|
||||
|
||||
return local(server);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onServerPostConnect(ServerPostConnectEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
RegisteredServer server = currentServer(player).orElse(null);
|
||||
if (server == null) return;
|
||||
|
||||
UUID uuid = player.getUniqueId();
|
||||
globalOnArena.remove(uuid);
|
||||
globalOnServer.remove(uuid);
|
||||
|
||||
ChatChannel current = defaults.get(uuid);
|
||||
ChatChannel next;
|
||||
if (isArena(server)) {
|
||||
next = local(server);
|
||||
} else if (!announced.contains(uuid) || current instanceof LocalChannel || (event.getPreviousServer() != null && isArena(event.getPreviousServer()))) {
|
||||
next = resolvePersisted(player, server);
|
||||
} else {
|
||||
next = current;
|
||||
}
|
||||
|
||||
setDefault(Chatter.of(player), next);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onDisconnect(DisconnectEvent event) {
|
||||
UUID uuid = event.getPlayer().getUniqueId();
|
||||
defaults.remove(uuid);
|
||||
persisted.remove(uuid);
|
||||
globalOnArena.remove(uuid);
|
||||
globalOnServer.remove(uuid);
|
||||
announced.remove(uuid);
|
||||
spies.remove(uuid);
|
||||
allArenas.remove(uuid);
|
||||
leftArenas.remove(uuid);
|
||||
hidden.remove(uuid);
|
||||
|
||||
directs.removeIf(channel -> channel.contains(uuid));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* 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.velocitycore.chat;
|
||||
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import de.steamwar.messages.Chatter;
|
||||
import de.steamwar.messages.Message;
|
||||
import de.steamwar.sql.AuditLog;
|
||||
import de.steamwar.sql.IgnoreSystem;
|
||||
import de.steamwar.sql.SteamwarUser;
|
||||
import de.steamwar.velocitycore.VelocityCore;
|
||||
import net.kyori.adventure.text.Component;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static de.steamwar.persistent.Storage.lastChats;
|
||||
|
||||
/**
|
||||
* A temporary channel between two players, closed as soon as one of them goes offline.
|
||||
*/
|
||||
public class DirectChannel extends ChatChannel {
|
||||
|
||||
private final UUID first;
|
||||
private final UUID second;
|
||||
|
||||
DirectChannel(UUID first, UUID second) {
|
||||
this.first = first;
|
||||
this.second = second;
|
||||
}
|
||||
|
||||
public boolean contains(UUID uuid) {
|
||||
return first.equals(uuid) || second.equals(uuid);
|
||||
}
|
||||
|
||||
public UUID partner(UUID uuid) {
|
||||
return first.equals(uuid) ? second : first;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message tag(Chatter receiver) {
|
||||
return new Message("CHAT_CHANNEL_MSG", SteamwarUser.get(partner(receiver.user().getUUID())));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean switchable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Message icon(Chatter receiver) {
|
||||
return new Message("CHAT_ICON_MSG");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String suggestion(Chatter receiver) {
|
||||
return "/msg " + SteamwarUser.get(partner(receiver.user().getUUID())).getUserName() + " ";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<Chatter> readers() {
|
||||
return Stream.of(first, second)
|
||||
.map(uuid -> VelocityCore.getProxy().getPlayer(uuid).orElse(null))
|
||||
.filter(player -> player != null)
|
||||
.map(Chatter::of);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canWrite(Chatter sender) {
|
||||
UUID partner = partner(sender.user().getUUID());
|
||||
if (VelocityCore.getProxy().getPlayer(partner).isEmpty()) {
|
||||
sender.system("MSG_OFFLINE");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IgnoreSystem.isIgnored(partner, sender.user().getUUID())) {
|
||||
sender.system("MSG_IGNORED");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Component format(Chatter receiver, Chatter sender, String message) {
|
||||
SteamwarUser partner = SteamwarUser.get(partner(sender.user().getUUID()));
|
||||
return Component.text()
|
||||
.append(ChatFormat.withTeamHover(receiver, sender.user(), receiver.parse(new Message("CHAT_MSG_SENDER", sender.user()))))
|
||||
.append(receiver.parse(new Message("CHAT_MSG_SEPARATOR")))
|
||||
.append(ChatFormat.withTeamHover(receiver, partner, receiver.parse(new Message("CHAT_MSG_RECEIVER", partner))))
|
||||
.append(Component.text(" "))
|
||||
.append(receiver.parse(new Message("CHAT_MSG", ChatFormat.highlightMentions(message, "§7", receiver))))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean delivered(Chatter sender, String message, boolean mirror) {
|
||||
Player senderPlayer = VelocityCore.getProxy().getPlayer(sender.user().getUUID()).orElse(null);
|
||||
Player partnerPlayer = VelocityCore.getProxy().getPlayer(partner(sender.user().getUUID())).orElse(null);
|
||||
if (senderPlayer != null && partnerPlayer != null) {
|
||||
lastChats.put(senderPlayer, partnerPlayer);
|
||||
lastChats.put(partnerPlayer, senderPlayer);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void audit(SteamwarUser sender, String message) {
|
||||
AuditLog.createSensitiveCommand(AuditLog.SERVER_NAME_VELOCITY, null, sender, "/msg " + SteamwarUser.get(partner(sender.getUUID())).getUserName() + " " + message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* 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.velocitycore.chat;
|
||||
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import de.steamwar.messages.Chatter;
|
||||
import de.steamwar.messages.Message;
|
||||
import de.steamwar.network.packets.common.FightInfoPacket;
|
||||
import de.steamwar.sql.AuditLog;
|
||||
import de.steamwar.sql.SteamwarUser;
|
||||
import lombok.Getter;
|
||||
import net.kyori.adventure.text.Component;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* The team chat of the blue or red team of a fight.
|
||||
*/
|
||||
public class FightTeamChannel extends ChatChannel {
|
||||
|
||||
@Getter
|
||||
private final RegisteredServer server;
|
||||
private final boolean blue;
|
||||
|
||||
FightTeamChannel(RegisteredServer server, boolean blue) {
|
||||
this.server = server;
|
||||
this.blue = blue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean switchable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
private List<Integer> members() {
|
||||
FightInfoPacket info = ChatManager.getFightInfo(server);
|
||||
if (info == null) return Collections.emptyList();
|
||||
return blue ? info.getBluePlayers() : info.getRedPlayers();
|
||||
}
|
||||
|
||||
private String teamName() {
|
||||
FightInfoPacket info = ChatManager.getFightInfo(server);
|
||||
if (info == null) return "";
|
||||
return blue ? info.getBlueName() : info.getRedName();
|
||||
}
|
||||
|
||||
public boolean isMember(Chatter chatter) {
|
||||
return members().contains(chatter.user().getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message tag(Chatter receiver) {
|
||||
return new Message("CHAT_CHANNEL_FIGHT_TEAM", server.getServerInfo().getName(), teamName());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Message icon(Chatter receiver) {
|
||||
String teamName = teamName();
|
||||
String color = teamName.length() >= 2 && teamName.charAt(0) == '§' ? teamName.substring(0, 2) : "§7";
|
||||
if (ChatManager.onServer(receiver, server)) {
|
||||
return new Message("CHAT_ICON_FIGHT_TEAM", color);
|
||||
}
|
||||
return new Message("CHAT_ICON_FIGHT_TEAM_FOREIGN", color, server.getServerInfo().getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String suggestion(Chatter receiver) {
|
||||
// Messages starting with '+' go into the fight team chat while writing into the fight chat
|
||||
return ChatManager.onServer(receiver, server) ? "+" : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<Chatter> readers() {
|
||||
List<Integer> members = members();
|
||||
return Stream.concat(
|
||||
server.getPlayersConnected().stream().map(Chatter::of).filter(chatter -> members.contains(chatter.user().getId())),
|
||||
ChatManager.spies(server));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canWrite(Chatter sender) {
|
||||
if (!isMember(sender) || !ChatManager.onServer(sender, server)) {
|
||||
sender.system("CHAT_FIGHT_NO_TEAM");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Component format(Chatter receiver, Chatter sender, String message) {
|
||||
return ChatFormat.fight(receiver, server, sender, teamName(), "CHAT_FIGHT_PARTICIPANT_SENDER", "CHAT_FIGHT_TEAM", ChatFormat.highlightMentions(message, "§f", receiver));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void audit(SteamwarUser sender, String message) {
|
||||
AuditLog.createChat(server.getServerInfo().getName(), null, sender, "+" + message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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.velocitycore.chat;
|
||||
|
||||
import de.steamwar.messages.Chatter;
|
||||
import de.steamwar.messages.Message;
|
||||
import de.steamwar.velocitycore.discord.DiscordBot;
|
||||
import net.kyori.adventure.text.Component;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class GlobalChannel extends ChatChannel {
|
||||
|
||||
@Override
|
||||
public String persistId() {
|
||||
return "global";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message tag(Chatter receiver) {
|
||||
return new Message("CHAT_CHANNEL_GLOBAL");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Message icon(Chatter receiver) {
|
||||
return new Message("CHAT_ICON_GLOBAL");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String suggestion(Chatter receiver) {
|
||||
return "/global ";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<Chatter> readers() {
|
||||
return Stream.concat(Stream.of(Chatter.console()), Chatter.allPlayers().filter(ChatManager::readsGlobal).map(Chatter::of).filter(chatter -> ChatManager.reads(chatter, persistId())));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canWrite(Chatter sender) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Component format(Chatter receiver, Chatter sender, String message) {
|
||||
return ChatFormat.player("CHAT_GLOBAL", receiver, sender, null, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean delivered(Chatter sender, String message, boolean mirror) {
|
||||
if (mirror) {
|
||||
DiscordBot.withBot(bot -> bot.getIngameChat().sendMessage(format(bot.getIngameChat(), sender, message)));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* 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.velocitycore.chat;
|
||||
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import de.steamwar.messages.Chatter;
|
||||
import de.steamwar.messages.Message;
|
||||
import de.steamwar.network.packets.common.FightInfoPacket;
|
||||
import de.steamwar.network.packets.server.ChatRecordPacket;
|
||||
import de.steamwar.sql.AuditLog;
|
||||
import de.steamwar.sql.SteamwarUser;
|
||||
import de.steamwar.sql.UserPerm;
|
||||
import de.steamwar.velocitycore.network.NetworkSender;
|
||||
import lombok.Getter;
|
||||
import net.kyori.adventure.text.Component;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* The chat of all players on one server. On arenas this is the fight chat.
|
||||
*/
|
||||
public class LocalChannel extends ChatChannel {
|
||||
|
||||
@Getter
|
||||
private final RegisteredServer server;
|
||||
|
||||
LocalChannel(RegisteredServer server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
public boolean isArena() {
|
||||
return ChatManager.isArena(server);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String persistId() {
|
||||
return "local";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message tag(Chatter receiver) {
|
||||
if (isArena()) {
|
||||
return new Message("CHAT_CHANNEL_ARENA");
|
||||
}
|
||||
return new Message("CHAT_CHANNEL_LOCAL");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message hover(Chatter receiver) {
|
||||
return new Message("CHAT_HOVER_LOCAL", tag(receiver), server.getServerInfo().getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Message icon(Chatter receiver) {
|
||||
if (!isArena()) return new Message("CHAT_ICON_LOCAL");
|
||||
|
||||
// Moderators reading an arena they are not on need to know which arena it is
|
||||
if (ChatManager.onServer(receiver, server)) {
|
||||
return new Message("CHAT_ICON_FIGHT");
|
||||
}
|
||||
return new Message("CHAT_ICON_FIGHT_FOREIGN", server.getServerInfo().getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String suggestion(Chatter receiver) {
|
||||
if (isArena() && !ChatManager.onServer(receiver, server)) {
|
||||
return "/chat " + server.getServerInfo().getName() + " msg ";
|
||||
}
|
||||
return "/local ";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<Chatter> readers() {
|
||||
Stream<Chatter> players = server.getPlayersConnected().stream().map(Chatter::of).filter(chatter -> ChatManager.reads(chatter, persistId())).map(Chatter.class::cast);
|
||||
if (isArena()) {
|
||||
players = Stream.concat(players, ChatManager.spies(server));
|
||||
}
|
||||
return players;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canWrite(Chatter sender) {
|
||||
// Moderators can write into fight chats they are not on (/chat <arena> msg)
|
||||
if (!ChatManager.onServer(sender, server) && !(isArena() && sender.user().hasPerm(UserPerm.MODERATION))) {
|
||||
sender.system("CHAT_NO_WRITE");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Component format(Chatter receiver, Chatter sender, String message) {
|
||||
if (!isArena()) {
|
||||
return ChatFormat.player("CHAT_GLOBAL", receiver, sender, null, message);
|
||||
}
|
||||
|
||||
String highlighted = ChatFormat.highlightMentions(message, "§7", receiver);
|
||||
FightInfoPacket info = ChatManager.getFightInfo(server);
|
||||
int id = sender.user().getId();
|
||||
if (info != null && info.getBluePlayers().contains(id)) {
|
||||
return ChatFormat.fight(receiver, server, sender, info.getBlueName(), "CHAT_FIGHT_PARTICIPANT_SENDER", "CHAT_FIGHT_PARTICIPANT", highlighted);
|
||||
} else if (info != null && info.getRedPlayers().contains(id)) {
|
||||
return ChatFormat.fight(receiver, server, sender, info.getRedName(), "CHAT_FIGHT_PARTICIPANT_SENDER", "CHAT_FIGHT_PARTICIPANT", highlighted);
|
||||
} else if (ChatManager.isReferee(server, id)) {
|
||||
return ChatFormat.fight(receiver, server, sender, null, "CHAT_FIGHT_REFEREE_SENDER", "CHAT_FIGHT_REFEREE", highlighted);
|
||||
}
|
||||
return ChatFormat.fight(receiver, server, sender, null, "CHAT_FIGHT_SPECTATOR_SENDER", "CHAT_FIGHT_SPECTATOR", highlighted);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean delivered(Chatter sender, String message, boolean mirror) {
|
||||
if (isArena()) {
|
||||
// Arenas never display chat from Velocity, they only record it for replays
|
||||
NetworkSender.send(server, new ChatRecordPacket(sender.user().getId(), message));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void audit(SteamwarUser sender, String message) {
|
||||
AuditLog.createChat(server.getServerInfo().getName(), null, sender, message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* 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.velocitycore.chat;
|
||||
|
||||
import de.steamwar.messages.Chatter;
|
||||
import de.steamwar.messages.Message;
|
||||
import de.steamwar.sql.AuditLog;
|
||||
import de.steamwar.sql.SteamwarUser;
|
||||
import de.steamwar.sql.UserPerm;
|
||||
import de.steamwar.velocitycore.discord.DiscordBot;
|
||||
import net.kyori.adventure.text.Component;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class ServerTeamChannel extends ChatChannel {
|
||||
|
||||
@Override
|
||||
public String persistId() {
|
||||
return "serverteam";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message tag(Chatter receiver) {
|
||||
return new Message("CHAT_CHANNEL_SERVERTEAM");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Message icon(Chatter receiver) {
|
||||
return new Message("CHAT_ICON_SERVERTEAM");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String suggestion(Chatter receiver) {
|
||||
return "/stc ";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<Chatter> readers() {
|
||||
return Arrays.stream(Chatter.serverteam().getChatters()).filter(chatter -> ChatManager.reads(chatter, persistId()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canWrite(Chatter sender) {
|
||||
if (!sender.user().hasPerm(UserPerm.TEAM)) {
|
||||
sender.system("CHAT_NO_WRITE");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Component format(Chatter receiver, Chatter sender, String message) {
|
||||
return ChatFormat.player("CHAT_SERVERTEAM", receiver, sender, null, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean delivered(Chatter sender, String message, boolean mirror) {
|
||||
if (mirror) {
|
||||
DiscordBot.withBot(bot -> bot.getServerTeamChat().sendMessage(ChatFormat.player("CHAT_GLOBAL", bot.getServerTeamChat(), sender, null, message)));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void audit(SteamwarUser sender, String message) {
|
||||
AuditLog.createCommand(AuditLog.SERVER_NAME_VELOCITY, null, sender, "/stc " + message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* 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.velocitycore.chat;
|
||||
|
||||
import de.steamwar.messages.Chatter;
|
||||
import de.steamwar.messages.Message;
|
||||
import de.steamwar.sql.AuditLog;
|
||||
import de.steamwar.sql.SteamwarUser;
|
||||
import de.steamwar.velocitycore.commands.StreamingCommand;
|
||||
import lombok.Getter;
|
||||
import net.kyori.adventure.text.Component;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class TeamChannel extends ChatChannel {
|
||||
|
||||
@Getter
|
||||
private final int team;
|
||||
|
||||
TeamChannel(int team) {
|
||||
this.team = team;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String persistId() {
|
||||
return "team";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message tag(Chatter receiver) {
|
||||
return new Message("CHAT_CHANNEL_TEAM");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Message icon(Chatter receiver) {
|
||||
return new Message("CHAT_ICON_TEAM");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String suggestion(Chatter receiver) {
|
||||
return "/tc ";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<Chatter> readers() {
|
||||
return Chatter.allPlayers()
|
||||
.map(Chatter::of)
|
||||
.filter(chatter -> chatter.user().getTeam() == team)
|
||||
.filter(StreamingCommand::isNotStreaming)
|
||||
.filter(chatter -> ChatManager.reads(chatter, persistId()))
|
||||
.map(Chatter.class::cast);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canWrite(Chatter sender) {
|
||||
if (sender.user().getTeam() != team) {
|
||||
sender.system("TC_NO_TEAM");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Component format(Chatter receiver, Chatter sender, String message) {
|
||||
return ChatFormat.player("CHAT_TEAM", receiver, sender, null, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void audit(SteamwarUser sender, String message) {
|
||||
AuditLog.createSensitiveCommand(AuditLog.SERVER_NAME_VELOCITY, null, sender, "/tc " + message);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user