diff --git a/pswgcommon b/pswgcommon index 44dc9210..fff4daa9 160000 --- a/pswgcommon +++ b/pswgcommon @@ -1 +1 @@ -Subproject commit 44dc921082dc6a2a9e63941a0130033fcc81945c +Subproject commit fff4daa98e4270aa45e06c94c3ff542fac5224ab diff --git a/src/main/java/com/projectswg/holocore/ProjectSWG.java b/src/main/java/com/projectswg/holocore/ProjectSWG.java index 8a929029..05e71aea 100644 --- a/src/main/java/com/projectswg/holocore/ProjectSWG.java +++ b/src/main/java/com/projectswg/holocore/ProjectSWG.java @@ -32,7 +32,6 @@ import com.projectswg.common.data.encodables.galaxy.Galaxy.GalaxyStatus; import com.projectswg.holocore.intents.support.data.control.ServerStatusIntent; import com.projectswg.holocore.resources.support.data.client_info.ServerFactory; import com.projectswg.holocore.resources.support.data.control.ServerStatus; -import com.projectswg.holocore.resources.support.data.server_info.DataManager; import com.projectswg.holocore.resources.support.data.server_info.mongodb.PswgDatabase; import com.projectswg.holocore.services.gameplay.GameplayManager; import com.projectswg.holocore.services.support.SupportManager; @@ -111,7 +110,6 @@ public class ProjectSWG { return -1; } setupDatabase(arguments); - DataManager.initialize(); Thread.currentThread().setPriority(10); initializeServerFactory(); setupGalaxy(arguments); @@ -145,7 +143,6 @@ public class ProjectSWG { } private static void shutdownStaticClasses() { - DataManager.terminate(); ScheduledUtilities.shutdown(); } @@ -177,16 +174,16 @@ public class ProjectSWG { private static void setupGalaxy(CommandLine arguments) { GALAXY.setId(1); - GALAXY.setName(PswgDatabase.config().getString(ProjectSWG.class, "galaxyName", "Holocore")); + GALAXY.setName(PswgDatabase.INSTANCE.getConfig().getString(ProjectSWG.class, "galaxyName", "Holocore")); GALAXY.setAddress(""); GALAXY.setPopulation(0); GALAXY.setZoneOffset(OffsetTime.now().getOffset()); GALAXY.setZonePort(0); GALAXY.setPingPort(0); GALAXY.setStatus(GalaxyStatus.DOWN); - GALAXY.setMaxCharacters(PswgDatabase.config().getInt(ProjectSWG.class, "galaxyMaxCharacters", 2)); - GALAXY.setOnlinePlayerLimit(PswgDatabase.config().getInt(ProjectSWG.class, "galaxyMaxOnline", 3000)); - GALAXY.setOnlineFreeTrialLimit(PswgDatabase.config().getInt(ProjectSWG.class, "galaxyMaxOnline", 3000)); + GALAXY.setMaxCharacters(PswgDatabase.INSTANCE.getConfig().getInt(ProjectSWG.class, "galaxyMaxCharacters", 2)); + GALAXY.setOnlinePlayerLimit(PswgDatabase.INSTANCE.getConfig().getInt(ProjectSWG.class, "galaxyMaxOnline", 3000)); + GALAXY.setOnlineFreeTrialLimit(PswgDatabase.INSTANCE.getConfig().getInt(ProjectSWG.class, "galaxyMaxOnline", 3000)); GALAXY.setRecommended(true); try { GALAXY.setAdminServerPort(Integer.parseInt(arguments.getOptionValue("admin-port", "-1"))); @@ -199,9 +196,9 @@ public class ProjectSWG { private static void setupDatabase(CommandLine arguments) { String dbStr = arguments.getOptionValue("database", "mongodb://localhost"); - String db = arguments.getOptionValue("database", "nge"); + String db = arguments.getOptionValue("dbName", "nge"); - PswgDatabase.initialize(dbStr, db); + PswgDatabase.INSTANCE.initialize(dbStr, db); } private static Options createArgumentOptions() { diff --git a/src/main/java/com/projectswg/holocore/resources/gameplay/crafting/resource/galactic/GalacticResourceSpawn.java b/src/main/java/com/projectswg/holocore/resources/gameplay/crafting/resource/galactic/GalacticResourceSpawn.java index 667de0e6..2c8b2ab0 100644 --- a/src/main/java/com/projectswg/holocore/resources/gameplay/crafting/resource/galactic/GalacticResourceSpawn.java +++ b/src/main/java/com/projectswg/holocore/resources/gameplay/crafting/resource/galactic/GalacticResourceSpawn.java @@ -31,8 +31,6 @@ import com.projectswg.common.data.encodables.mongo.MongoPersistable; import com.projectswg.common.data.location.Terrain; import com.projectswg.common.network.NetBufferStream; import com.projectswg.common.persistable.Persistable; -import com.projectswg.holocore.resources.support.data.config.ConfigFile; -import com.projectswg.holocore.resources.support.data.server_info.DataManager; import com.projectswg.holocore.resources.support.data.server_info.mongodb.PswgDatabase; import java.time.Instant; @@ -145,19 +143,19 @@ public class GalacticResourceSpawn implements Persistable, MongoPersistable { } private int getMinSpawnTime() { - return PswgDatabase.config().getInt(this, "resourceMinSpawnTime", 7); + return PswgDatabase.INSTANCE.getConfig().getInt(this, "resourceMinSpawnTime", 7); } private int getMaxSpawnTime() { - return PswgDatabase.config().getInt(this, "resourceMaxSpawnTime", 21); + return PswgDatabase.INSTANCE.getConfig().getInt(this, "resourceMaxSpawnTime", 21); } private int getMinRadius() { - return PswgDatabase.config().getInt(this, "resourceMinSpawnRadius", 200); + return PswgDatabase.INSTANCE.getConfig().getInt(this, "resourceMinSpawnRadius", 200); } private int getMaxRadius() { - return PswgDatabase.config().getInt(this, "resourceMaxSpawnRadius", 500); + return PswgDatabase.INSTANCE.getConfig().getInt(this, "resourceMaxSpawnRadius", 500); } private int calculateRandomMaxConcentration(Random random, int min) { diff --git a/src/main/java/com/projectswg/holocore/resources/gameplay/crafting/resource/galactic/GalacticResourceSpawner.java b/src/main/java/com/projectswg/holocore/resources/gameplay/crafting/resource/galactic/GalacticResourceSpawner.java index 1f817404..229356ae 100644 --- a/src/main/java/com/projectswg/holocore/resources/gameplay/crafting/resource/galactic/GalacticResourceSpawner.java +++ b/src/main/java/com/projectswg/holocore/resources/gameplay/crafting/resource/galactic/GalacticResourceSpawner.java @@ -29,13 +29,9 @@ package com.projectswg.holocore.resources.gameplay.crafting.resource.galactic; import com.projectswg.common.data.location.Terrain; import com.projectswg.holocore.resources.gameplay.crafting.resource.galactic.storage.GalacticResourceContainer; import com.projectswg.holocore.resources.gameplay.crafting.resource.raw.RawResource; -import com.projectswg.holocore.resources.gameplay.crafting.resource.raw.RawResourceContainer; import com.projectswg.holocore.resources.support.data.namegen.SWGNameGenerator; -import com.projectswg.holocore.resources.support.data.server_info.DataManager; import com.projectswg.holocore.resources.support.data.server_info.StandardLog; import com.projectswg.holocore.resources.support.data.server_info.mongodb.PswgDatabase; -import me.joshlarson.jlcommon.log.Log; -import me.joshlarson.jlcommon.log.log_wrapper.ConsoleLogWrapper; import java.util.List; import java.util.Random; @@ -69,20 +65,6 @@ public class GalacticResourceSpawner { this.resourceIdMax = new AtomicLong(0); } - public static void main(String [] args) { - Log.addWrapper(new ConsoleLogWrapper()); - DataManager.initialize(); - RawResourceContainer container = new RawResourceContainer(); - container.loadResources(); - for (RawResource rawResource : container.getResources()) { - GalacticResourceContainer.getContainer().addRawResource(rawResource); - } - GalacticResourceSpawner spawner = new GalacticResourceSpawner(); - spawner.initialize(); - spawner.terminate(); - DataManager.terminate(); - } - public void initialize() { loadResources(); updateAllResources(); @@ -100,7 +82,7 @@ public class GalacticResourceSpawner { private void loadResources() { long startTime = StandardLog.onStartLoad("galactic resources"); int resourceCount = 0; - for (GalacticResource resource : PswgDatabase.resources().getResources()) { + for (GalacticResource resource : PswgDatabase.INSTANCE.getResources().getResources()) { GalacticResourceContainer.getContainer().addGalacticResource(resource); if (resource.getId() > resourceIdMax.get()) resourceIdMax.set(resource.getId()); @@ -112,7 +94,7 @@ public class GalacticResourceSpawner { private void saveResources() { GalacticResourceLoader loader = new GalacticResourceLoader(); loader.saveResources(GalacticResourceContainer.getContainer().getAllResources()); - PswgDatabase.resources().addResources(GalacticResourceContainer.getContainer().getAllResources()); + PswgDatabase.INSTANCE.getResources().setResources(GalacticResourceContainer.getContainer().getAllResources()); } private void updateUnusedPools() { diff --git a/src/main/java/com/projectswg/holocore/resources/gameplay/world/travel/TravelHelper.java b/src/main/java/com/projectswg/holocore/resources/gameplay/world/travel/TravelHelper.java index ddca572c..6fdc243e 100644 --- a/src/main/java/com/projectswg/holocore/resources/gameplay/world/travel/TravelHelper.java +++ b/src/main/java/com/projectswg/holocore/resources/gameplay/world/travel/TravelHelper.java @@ -186,8 +186,8 @@ public class TravelHelper { } private void createGalaxyTravels() { - long groundTime = PswgDatabase.config().getInt(this, "shuttleGroundTime", 120); - long airTime = PswgDatabase.config().getInt(this, "shuttleAirTime", 60); + long groundTime = PswgDatabase.INSTANCE.getConfig().getInt(this, "shuttleGroundTime", 120); + long airTime = PswgDatabase.INSTANCE.getConfig().getInt(this, "shuttleAirTime", 60); createGalaxyTravel(SpecificObject.SO_TRANSPORT_SHUTTLE.getTemplate(), 17, groundTime, airTime); createGalaxyTravel(SpecificObject.SO_TRANSPORT_STARPORT.getTemplate(), 21, groundTime, airTime); diff --git a/src/main/java/com/projectswg/holocore/resources/support/data/server_info/ConfigWatcher.java b/src/main/java/com/projectswg/holocore/resources/support/data/server_info/ConfigWatcher.java deleted file mode 100644 index ca092576..00000000 --- a/src/main/java/com/projectswg/holocore/resources/support/data/server_info/ConfigWatcher.java +++ /dev/null @@ -1,127 +0,0 @@ -/*********************************************************************************** - * Copyright (c) 2018 /// Project SWG /// www.projectswg.com * - * * - * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * - * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * - * Our goal is to create an emulator which will provide a server for players to * - * continue playing a game similar to the one they used to play. We are basing * - * it on the final publish of the game prior to end-game events. * - * * - * This file is part of Holocore. * - * * - * --------------------------------------------------------------------------------* - * * - * Holocore 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. * - * * - * Holocore 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 Holocore. If not, see . * - ***********************************************************************************/ -package com.projectswg.holocore.resources.support.data.server_info; - -import com.projectswg.common.data.info.Config; -import com.projectswg.holocore.intents.support.data.config.ConfigChangedIntent; -import com.projectswg.holocore.resources.support.data.config.ConfigFile; -import me.joshlarson.jlcommon.concurrency.BasicThread; -import me.joshlarson.jlcommon.log.Log; - -import java.io.IOException; -import java.nio.file.*; -import java.util.Map; -import java.util.Map.Entry; -import java.util.concurrent.atomic.AtomicBoolean; - -public final class ConfigWatcher { - - private static final String CFGPATH = "cfg/"; - - private final Map configMap; - private final BasicThread watcherThread; - private final AtomicBoolean running; - - private WatchService watcher; - - public ConfigWatcher(Map configMap) { - this.configMap = configMap; - this.watcherThread = new BasicThread("config-watcher", this::watch); - this.running = new AtomicBoolean(false); - - try { - this.watcher = FileSystems.getDefault().newWatchService(); - Paths.get(CFGPATH).register(watcher, StandardWatchEventKinds.ENTRY_MODIFY); - } catch (IOException e) { - this.watcher = null; - } - } - - public void start() { - running.set(true); - watcherThread.start(); - } - - public void stop() { - running.set(false); - watcherThread.stop(true); - watcherThread.awaitTermination(1000); - } - - private void watch() { - if (watcher == null) { - Log.e("WatcherService is null!"); - return; - } - WatchKey key; - - Log.i("ConfigWatcher started"); - try { - while (running.get()) { - key = watcher.take(); // We're stuck here until a change is made. - if (key == null) - break; - - for (WatchEvent event : key.pollEvents()) { - processEvents(event); - key.reset(); - } - } - } catch (Exception e) { - // Ignored - } finally { - try { - watcher.close(); - } catch (Exception e) { - Log.e(e); - } - } - Log.i("ConfigWatcher shut down"); - } - - private void processEvents(WatchEvent event) { - if (event.kind() == StandardWatchEventKinds.OVERFLOW) - return; - - @SuppressWarnings("unchecked") - Path cfgPath = ((WatchEvent) event).context(); - ConfigFile cfgFile = ConfigFile.configFileForName(CFGPATH + cfgPath); - if (cfgFile == null) { - Log.w("Unknown config file: %s", cfgPath); - return; - } - Config cfg = configMap.get(cfgFile); - if (cfg == null) { - Log.w("Unknown config type: %s", cfgFile); - return; - } - - for (Entry entry : cfg.load().entrySet()) { - new ConfigChangedIntent(cfgFile, entry.getKey(), entry.getValue(), cfg.getString(entry.getKey(), null)).broadcast(); - } - } -} diff --git a/src/main/java/com/projectswg/holocore/resources/support/data/server_info/DataManager.java b/src/main/java/com/projectswg/holocore/resources/support/data/server_info/DataManager.java deleted file mode 100644 index 4883c772..00000000 --- a/src/main/java/com/projectswg/holocore/resources/support/data/server_info/DataManager.java +++ /dev/null @@ -1,139 +0,0 @@ -/*********************************************************************************** - * Copyright (c) 2018 /// Project SWG /// www.projectswg.com * - * * - * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * - * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * - * Our goal is to create an emulator which will provide a server for players to * - * continue playing a game similar to the one they used to play. We are basing * - * it on the final publish of the game prior to end-game events. * - * * - * This file is part of Holocore. * - * * - * --------------------------------------------------------------------------------* - * * - * Holocore 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. * - * * - * Holocore 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 Holocore. If not, see . * - ***********************************************************************************/ -package com.projectswg.holocore.resources.support.data.server_info; - -import com.projectswg.common.data.info.Config; -import com.projectswg.common.data.info.RelationalServerFactory; -import com.projectswg.holocore.resources.support.data.config.ConfigFile; -import me.joshlarson.jlcommon.log.Log; - -import java.io.File; -import java.io.IOException; -import java.util.EnumMap; -import java.util.Map; -import java.util.concurrent.atomic.AtomicBoolean; - -public class DataManager { - - private static final Object instanceLock = new Object(); - private static DataManager instance = null; - - private final Map configs; - private final ConfigWatcher watcher; - private final AtomicBoolean initialized; - - private DataManager() { - this.configs = new EnumMap<>(ConfigFile.class); - this.watcher = new ConfigWatcher(configs); - this.initialized = new AtomicBoolean(false); - } - - private void initializeImpl() { - if (initialized.getAndSet(true)) - return; - initializeConfig(); - RelationalServerFactory.setBasePath("serverdata/"); - } - - private void terminateImpl() { - if (!initialized.getAndSet(false)) - return; - watcher.stop(); - configs.clear(); - } - - private Config getConfigImpl(ConfigFile file) { - return configs.get(file); - } - - private void initializeConfig() { - assert configs.isEmpty() : "double initialize"; - for (ConfigFile file : ConfigFile.values()) { - File f = new File(file.getFilename()); - if (!createConfig(f)) { - Log.w("ConfigFile could not be loaded! " + file.getFilename()); - } else { - configs.put(file, new Config(f)); - } - } - watcher.start(); - } - - private boolean createConfig(File file) { - if (file.exists()) - return file.isFile(); - try { - File parent = file.getParentFile(); - if (parent != null && !parent.exists() && !parent.mkdirs()) { - Log.e("Failed to create parent directories for config: " + file.getCanonicalPath()); - return false; - } - if (!file.createNewFile()) { - Log.e("Failed to create new config: " + file.getCanonicalPath()); - return false; - } - } catch (IOException e) { - Log.e(e); - } - return file.exists(); - } - - /** - * Gets the config object associated with a certain file, or NULL if the file failed to load on startup - * - * @param file the file to get the config for - * @return the config object associated with the file, or NULL if the config failed to load - */ - public static Config getConfig(ConfigFile file) { - return getInstance().getConfigImpl(file); - } - - public static void initialize() { - synchronized (instanceLock) { - if (instance != null) - return; - instance = new DataManager(); - instance.initializeImpl(); - } - } - - public static void terminate() { - synchronized (instanceLock) { - if (instance == null) - return; - instance.terminateImpl(); - instance = null; - } - } - - private static DataManager getInstance() { - synchronized (instanceLock) { - return instance; - } - } - -} diff --git a/src/main/java/com/projectswg/holocore/resources/support/data/server_info/mongodb/PswgConfigDatabase.java b/src/main/java/com/projectswg/holocore/resources/support/data/server_info/mongodb/PswgConfigDatabase.java deleted file mode 100644 index bec66d75..00000000 --- a/src/main/java/com/projectswg/holocore/resources/support/data/server_info/mongodb/PswgConfigDatabase.java +++ /dev/null @@ -1,130 +0,0 @@ -/*********************************************************************************** - * Copyright (c) 2019 /// Project SWG /// www.projectswg.com * - * * - * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * - * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * - * Our goal is to create an emulator which will provide a server for players to * - * continue playing a game similar to the one they used to play. We are basing * - * it on the final publish of the game prior to end-game events. * - * * - * This file is part of Holocore. * - * * - * --------------------------------------------------------------------------------* - * * - * Holocore 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. * - * * - * Holocore 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 Holocore. If not, see . * - ***********************************************************************************/ - -package com.projectswg.holocore.resources.support.data.server_info.mongodb; - -import com.mongodb.client.MongoCollection; -import com.mongodb.client.model.Filters; -import com.mongodb.client.model.IndexOptions; -import com.mongodb.client.model.Indexes; -import me.joshlarson.jlcommon.log.Log; -import org.bson.Document; -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -public class PswgConfigDatabase implements PswgDatabase { - - private MongoCollection collection; - - PswgConfigDatabase() { - this.collection = null; - } - - @Override - public void open(MongoCollection collection) { - this.collection = collection; - collection.createIndex(Indexes.ascending("package"), new IndexOptions().unique(true)); - } - - @NotNull - public MongoCollection getCollection() { - return collection; - } - - public String getString(Object o, String key, String def) { - for (Document config : getConfigurations(o)) { - if (config.containsKey(key)) - return config.getString(key); - } - return def; - } - - public boolean getBoolean(Object o, String key, boolean def) { - for (Document config : getConfigurations(o)) { - if (config.containsKey(key)) - return config.getBoolean(key); - } - return def; - } - - public int getInt(Object o, String key, int def) { - for (Document config : getConfigurations(o)) { - if (config.containsKey(key)) - return config.getInteger(key); - } - return def; - } - - public double getDouble(Object o, String key, double def) { - for (Document config : getConfigurations(o)) { - if (config.containsKey(key)) - return config.getDouble(key); - } - return def; - } - - public double getLong(Object o, String key, long def) { - for (Document config : getConfigurations(o)) { - if (config.containsKey(key)) - return config.getLong(key); - } - return def; - } - - private List getConfigurations(Object o) { - String packageKey = o instanceof Class ? ((Class) o).getPackageName() : Objects.requireNonNull(o).getClass().getPackageName(); - if (packageKey.startsWith("com.projectswg.holocore.")) - packageKey = packageKey.substring(24); - else if (packageKey.startsWith("com.projectswg.holocore")) - packageKey = packageKey.substring(23); - else - throw new IllegalArgumentException("package lookup object does not belong to holocore"); - - if (packageKey.startsWith("intents.")) - throw new IllegalArgumentException("intents should not be querying configs"); - - if (packageKey.startsWith("resources.") || packageKey.startsWith("services.")) - packageKey = packageKey.substring(packageKey.indexOf('.')+1); - - List configs = new ArrayList<>(); - if (collection == null) - return configs; - while (!packageKey.isEmpty()) { - Document doc = collection.find(Filters.eq("package", packageKey)).first(); - if (doc != null) - configs.add(doc); - - int lastDot = packageKey.lastIndexOf('.'); - packageKey = lastDot == -1 ? "" : packageKey.substring(0, lastDot); - } - return configs; - } - -} diff --git a/src/main/java/com/projectswg/holocore/resources/support/data/server_info/mongodb/PswgConfigDatabase.kt b/src/main/java/com/projectswg/holocore/resources/support/data/server_info/mongodb/PswgConfigDatabase.kt new file mode 100644 index 00000000..4cee894c --- /dev/null +++ b/src/main/java/com/projectswg/holocore/resources/support/data/server_info/mongodb/PswgConfigDatabase.kt @@ -0,0 +1,110 @@ +/*********************************************************************************** + * Copyright (c) 2019 /// Project SWG /// www.projectswg.com * + * * + * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * + * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * + * Our goal is to create an emulator which will provide a server for players to * + * continue playing a game similar to the one they used to play. We are basing * + * it on the final publish of the game prior to end-game events. * + * * + * This file is part of Holocore. * + * * + * --------------------------------------------------------------------------------* + * * + * Holocore 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. * + * * + * Holocore 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 Holocore. If not, see //www.gnu.org/licenses/>. * + */ + +package com.projectswg.holocore.resources.support.data.server_info.mongodb + +import com.mongodb.client.MongoCollection +import com.mongodb.client.model.Filters +import com.mongodb.client.model.IndexOptions +import com.mongodb.client.model.Indexes +import org.bson.Document +import java.util.* + +class PswgConfigDatabase(private val collection: MongoCollection){ + + init { + collection.createIndex(Indexes.ascending("package"), IndexOptions().unique(true)) + } + + fun getString(o: Any, key: String, def: String): String { + for (config in getConfigurations(o)) { + if (config.containsKey(key)) + return config.getString(key) + } + return def + } + + fun getBoolean(o: Any, key: String, def: Boolean): Boolean { + for (config in getConfigurations(o)) { + if (config.containsKey(key)) + return config.getBoolean(key)!! + } + return def + } + + fun getInt(o: Any, key: String, def: Int): Int { + for (config in getConfigurations(o)) { + if (config.containsKey(key)) + return config.getInteger(key)!! + } + return def + } + + fun getDouble(o: Any, key: String, def: Double): Double { + for (config in getConfigurations(o)) { + if (config.containsKey(key)) + return config.getDouble(key)!! + } + return def + } + + fun getLong(o: Any, key: String, def: Long): Double { + for (config in getConfigurations(o)) { + if (config.containsKey(key)) + return config.getLong(key)!!.toDouble() + } + return def.toDouble() + } + + private fun getConfigurations(o: Any): List { + var packageKey = if (o is Class<*>) o.packageName else o.javaClass.packageName + require(packageKey.startsWith("com.projectswg.holocore")) { "packageKey must be a part of holocore, was: $packageKey" } + + packageKey = packageKey.removePrefix("com.projectswg.holocore") + packageKey = packageKey.removePrefix(".") + + if (packageKey.startsWith("intents.")) + throw IllegalArgumentException("intents should not be querying configs") + + if (packageKey.startsWith("resources.") || packageKey.startsWith("services.")) + packageKey = packageKey.substringAfter('.') + + val configs = ArrayList() + + while (packageKey.isNotEmpty()) { + val doc = collection.find(Filters.eq("package", packageKey)).first() + if (doc != null) + configs.add(doc) + + if (!packageKey.contains('.')) + break + packageKey = packageKey.substringBeforeLast('.') + } + return configs + } + +} diff --git a/src/main/java/com/projectswg/holocore/resources/support/data/server_info/mongodb/PswgDatabase.java b/src/main/java/com/projectswg/holocore/resources/support/data/server_info/mongodb/PswgDatabase.java deleted file mode 100644 index 16e3ecdf..00000000 --- a/src/main/java/com/projectswg/holocore/resources/support/data/server_info/mongodb/PswgDatabase.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.projectswg.holocore.resources.support.data.server_info.mongodb; - -import com.mongodb.client.MongoClient; -import com.mongodb.client.MongoClients; -import com.mongodb.client.MongoCollection; -import com.mongodb.client.MongoDatabase; -import me.joshlarson.jlcommon.log.Log; -import org.bson.Document; - -import java.util.logging.Handler; -import java.util.logging.Level; -import java.util.logging.LogRecord; -import java.util.logging.Logger; - -public interface PswgDatabase { - - default void open(MongoCollection collection) { - - } - - static void initialize(String connectionString, String databaseName) { - setupMongoLogging(); - MongoClient client = MongoClients.create(connectionString); - MongoDatabase database = client.getDatabase(databaseName); - for (PswgDatabaseImpl db : PswgDatabaseImpl.values()) { - db.open(database); - } - } - - static PswgConfigDatabase config() { - return (PswgConfigDatabase) PswgDatabaseImpl.CONFIG.getImplementation(); - } - - static PswgUserDatabase users() { - return (PswgUserDatabase) PswgDatabaseImpl.USERS.getImplementation(); - } - - static PswgObjectDatabase objects() { - return (PswgObjectDatabase) PswgDatabaseImpl.OBJECTS.getImplementation(); - } - - static PswgResourceDatabase resources() { - return (PswgResourceDatabase) PswgDatabaseImpl.RESOURCES.getImplementation(); - } - - private static void setupMongoLogging() { - Logger mongoLogger = Logger.getLogger("com.mongodb"); - while (mongoLogger != null) { - for (Handler handler : mongoLogger.getHandlers()) { - mongoLogger.removeHandler(handler); - } - if (mongoLogger.getParent() != null) - mongoLogger = mongoLogger.getParent(); - else - break; - } - if (mongoLogger != null) { - mongoLogger.addHandler(new Handler() { - public void publish(LogRecord record) { - Level level = record.getLevel(); - if (level.equals(Level.INFO)) - Log.i("MongoDB: %s", record.getMessage()); - else if (level.equals(Level.WARNING)) - Log.w("MongoDB: %s", record.getMessage()); - else if (level.equals(Level.SEVERE)) - Log.e("MongoDB: %s", record.getMessage()); - else - Log.t("MongoDB: %s", record.getMessage()); - } - public void flush() { } - public void close() throws SecurityException { } - }); - } - } - -} diff --git a/src/main/java/com/projectswg/holocore/resources/support/data/server_info/mongodb/PswgDatabase.kt b/src/main/java/com/projectswg/holocore/resources/support/data/server_info/mongodb/PswgDatabase.kt new file mode 100644 index 00000000..2c2e2793 --- /dev/null +++ b/src/main/java/com/projectswg/holocore/resources/support/data/server_info/mongodb/PswgDatabase.kt @@ -0,0 +1,64 @@ +package com.projectswg.holocore.resources.support.data.server_info.mongodb + +import com.mongodb.WriteConcern +import com.mongodb.client.MongoClients +import me.joshlarson.jlcommon.log.Log +import org.jetbrains.annotations.NotNull +import java.util.logging.Handler +import java.util.logging.Level +import java.util.logging.LogRecord +import java.util.logging.Logger + +object PswgDatabase { + + var config: PswgConfigDatabase? = null + @NotNull get() = field!! + private set + var users: PswgUserDatabase? = null + @NotNull get() = field!! + private set + var objects: PswgObjectDatabase? = null + @NotNull get() = field!! + private set + var resources: PswgResourceDatabase? = null + @NotNull get() = field!! + private set + + fun initialize(connectionString: String, databaseName: String) { + setupMongoLogging() + val client = MongoClients.create(connectionString) + val database = client.getDatabase(databaseName) + + config = PswgConfigDatabase(database.getCollection("config").withWriteConcern(WriteConcern.ACKNOWLEDGED)) + users = PswgUserDatabase(database.getCollection("users").withWriteConcern(WriteConcern.ACKNOWLEDGED)) + objects = PswgObjectDatabase(database.getCollection("objects").withWriteConcern(WriteConcern.JOURNALED)) + resources = PswgResourceDatabase(database.getCollection("resources").withWriteConcern(WriteConcern.ACKNOWLEDGED)) + } + + private fun setupMongoLogging() { + var mongoLogger: Logger? = Logger.getLogger("com.mongodb") + while (mongoLogger != null) { + for (handler in mongoLogger.handlers) { + mongoLogger.removeHandler(handler) + } + if (mongoLogger.parent != null) + mongoLogger = mongoLogger.parent + else + break + } + mongoLogger?.addHandler(object : Handler() { + override fun publish(record: LogRecord) { + when (record.level) { + Level.INFO -> Log.i("MongoDB: %s", record.message) + Level.WARNING -> Log.w("MongoDB: %s", record.message) + Level.SEVERE -> Log.e("MongoDB: %s", record.message) + else -> Log.t("MongoDB: %s", record.message) + } + } + + override fun flush() {} + override fun close() {} + }) + } + +} diff --git a/src/main/java/com/projectswg/holocore/resources/support/data/server_info/mongodb/PswgDatabaseImpl.java b/src/main/java/com/projectswg/holocore/resources/support/data/server_info/mongodb/PswgDatabaseImpl.java deleted file mode 100644 index 5b3e181c..00000000 --- a/src/main/java/com/projectswg/holocore/resources/support/data/server_info/mongodb/PswgDatabaseImpl.java +++ /dev/null @@ -1,60 +0,0 @@ -/*********************************************************************************** - * Copyright (c) 2019 /// Project SWG /// www.projectswg.com * - * * - * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * - * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * - * Our goal is to create an emulator which will provide a server for players to * - * continue playing a game similar to the one they used to play. We are basing * - * it on the final publish of the game prior to end-game events. * - * * - * This file is part of Holocore. * - * * - * --------------------------------------------------------------------------------* - * * - * Holocore 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. * - * * - * Holocore 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 Holocore. If not, see . * - ***********************************************************************************/ - -package com.projectswg.holocore.resources.support.data.server_info.mongodb; - -import com.mongodb.WriteConcern; -import com.mongodb.client.MongoCollection; -import com.mongodb.client.MongoDatabase; -import org.bson.Document; - -enum PswgDatabaseImpl { - CONFIG ("config", WriteConcern.ACKNOWLEDGED, new PswgConfigDatabase()), - USERS ("users", WriteConcern.ACKNOWLEDGED, new PswgUserDatabase()), - OBJECTS ("objects", WriteConcern.JOURNALED, new PswgObjectDatabase()), - RESOURCES ("resources", WriteConcern.ACKNOWLEDGED, new PswgResourceDatabase()); - - private final String collectionName; - private final WriteConcern writeConcern; - private final PswgDatabase implementation; - - PswgDatabaseImpl(String collectionName, WriteConcern writeConcern, PswgDatabase implementation) { - this.collectionName = collectionName; - this.writeConcern = writeConcern; - this.implementation = implementation; - } - - void open(MongoDatabase database) { - MongoCollection collection = database.getCollection(collectionName).withWriteConcern(writeConcern); - implementation.open(collection); - } - - PswgDatabase getImplementation() { - return implementation; - } - -} diff --git a/src/main/java/com/projectswg/holocore/resources/support/data/server_info/mongodb/PswgObjectDatabase.java b/src/main/java/com/projectswg/holocore/resources/support/data/server_info/mongodb/PswgObjectDatabase.java deleted file mode 100644 index fa536a48..00000000 --- a/src/main/java/com/projectswg/holocore/resources/support/data/server_info/mongodb/PswgObjectDatabase.java +++ /dev/null @@ -1,102 +0,0 @@ -/*********************************************************************************** - * Copyright (c) 2019 /// Project SWG /// www.projectswg.com * - * * - * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * - * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * - * Our goal is to create an emulator which will provide a server for players to * - * continue playing a game similar to the one they used to play. We are basing * - * it on the final publish of the game prior to end-game events. * - * * - * This file is part of Holocore. * - * * - * --------------------------------------------------------------------------------* - * * - * Holocore 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. * - * * - * Holocore 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 Holocore. If not, see . * - ***********************************************************************************/ - -package com.projectswg.holocore.resources.support.data.server_info.mongodb; - -import com.mongodb.client.MongoCollection; -import com.mongodb.client.model.*; -import com.projectswg.common.data.encodables.mongo.MongoData; -import com.projectswg.holocore.resources.support.data.persistable.SWGObjectFactory; -import com.projectswg.holocore.resources.support.objects.swg.SWGObject; -import org.bson.Document; -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - -public class PswgObjectDatabase implements PswgDatabase { - - private MongoCollection collection; - - PswgObjectDatabase() { - this.collection = null; - } - - @Override - public void open(MongoCollection collection) { - this.collection = collection; - collection.createIndex(Indexes.ascending("id"), new IndexOptions().unique(true)); - } - - @NotNull - public MongoCollection getCollection() { - return collection; - } - - public void addObject(@NotNull SWGObject obj) { - collection.replaceOne(Filters.eq("id", obj.getObjectId()), SWGObjectFactory.save(obj, new MongoData()).toDocument(), new ReplaceOptions().upsert(true)); - } - - public void addObjects(@NotNull Collection objects) { - collection.bulkWrite(objects.stream() - .map(obj -> new ReplaceOneModel<>( - Filters.eq("id", obj.getObjectId()), - SWGObjectFactory.save(obj).toDocument(), - new ReplaceOptions().upsert(true) - )) - .collect(Collectors.toList()), - new BulkWriteOptions().ordered(false)); - } - - public boolean removeObject(long id) { - return collection.deleteOne(Filters.eq("id", id)).getDeletedCount() > 0; - } - - public int getCharacterCount(String account) { - return (int) collection.countDocuments(Filters.eq("account", account)); - } - - public boolean isCharacter(String firstName) { - return collection.countDocuments(Filters.and( - Filters.regex("template", "object/creature/player/shared_.+\\.iff"), - Filters.regex("base3.objectName", Pattern.compile(Pattern.quote(firstName) + "( .+|$)", Pattern.CASE_INSENSITIVE)) - )) > 0; - } - - public long clearObjects() { - return collection.deleteMany(Filters.exists("_id")).getDeletedCount(); - } - - @NotNull - public List getObjects() { - return collection.find().map(MongoData::new).into(new ArrayList<>()); - } - -} diff --git a/src/main/java/com/projectswg/holocore/resources/support/data/server_info/mongodb/PswgObjectDatabase.kt b/src/main/java/com/projectswg/holocore/resources/support/data/server_info/mongodb/PswgObjectDatabase.kt new file mode 100644 index 00000000..5b7be130 --- /dev/null +++ b/src/main/java/com/projectswg/holocore/resources/support/data/server_info/mongodb/PswgObjectDatabase.kt @@ -0,0 +1,87 @@ +/*********************************************************************************** + * Copyright (c) 2019 /// Project SWG /// www.projectswg.com * + * * + * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * + * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * + * Our goal is to create an emulator which will provide a server for players to * + * continue playing a game similar to the one they used to play. We are basing * + * it on the final publish of the game prior to end-game events. * + * * + * This file is part of Holocore. * + * * + * --------------------------------------------------------------------------------* + * * + * Holocore 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. * + * * + * Holocore 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 Holocore. If not, see //www.gnu.org/licenses/>. * + */ + +package com.projectswg.holocore.resources.support.data.server_info.mongodb + +import com.mongodb.client.MongoCollection +import com.mongodb.client.model.* +import com.projectswg.common.data.encodables.mongo.MongoData +import com.projectswg.holocore.resources.support.data.persistable.SWGObjectFactory +import com.projectswg.holocore.resources.support.objects.swg.SWGObject +import org.bson.Document +import java.util.* +import java.util.regex.Pattern +import java.util.stream.Collectors.toList + +class PswgObjectDatabase(private val collection: MongoCollection) { + + val objects: List + get() = collection.find().map { MongoData(it) }.into(ArrayList()) + + init { + collection.createIndex(Indexes.ascending("id"), IndexOptions().unique(true)) + } + + fun addObject(obj: SWGObject) { + collection.replaceOne(Filters.eq("id", obj.objectId), SWGObjectFactory.save(obj, MongoData()).toDocument(), ReplaceOptions().upsert(true)) + } + + fun addObjects(objects: Collection) { + if (objects.isEmpty()) + return + collection.bulkWrite(objects.stream() + .map { obj -> + ReplaceOneModel( + Filters.eq("id", obj.objectId), + SWGObjectFactory.save(obj).toDocument(), + ReplaceOptions().upsert(true) + ) + } + .collect(toList()), + BulkWriteOptions().ordered(false)) + } + + fun removeObject(id: Long): Boolean { + return collection.deleteOne(Filters.eq("id", id)).deletedCount > 0 + } + + fun getCharacterCount(account: String): Int { + return collection.countDocuments(Filters.eq("account", account)).toInt() + } + + fun isCharacter(firstName: String): Boolean { + return collection.countDocuments(Filters.and( + Filters.regex("template", "object/creature/player/shared_.+\\.iff"), + Filters.regex("base3.objectName", Pattern.compile(Pattern.quote(firstName) + "( .+|$)", Pattern.CASE_INSENSITIVE)) + )) > 0 + } + + fun clearObjects(): Long { + return collection.deleteMany(Filters.exists("_id")).deletedCount + } + +} diff --git a/src/main/java/com/projectswg/holocore/resources/support/data/server_info/mongodb/PswgResourceDatabase.java b/src/main/java/com/projectswg/holocore/resources/support/data/server_info/mongodb/PswgResourceDatabase.java deleted file mode 100644 index 33af0187..00000000 --- a/src/main/java/com/projectswg/holocore/resources/support/data/server_info/mongodb/PswgResourceDatabase.java +++ /dev/null @@ -1,77 +0,0 @@ -/*********************************************************************************** - * Copyright (c) 2019 /// Project SWG /// www.projectswg.com * - * * - * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * - * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * - * Our goal is to create an emulator which will provide a server for players to * - * continue playing a game similar to the one they used to play. We are basing * - * it on the final publish of the game prior to end-game events. * - * * - * This file is part of Holocore. * - * * - * --------------------------------------------------------------------------------* - * * - * Holocore 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. * - * * - * Holocore 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 Holocore. If not, see . * - ***********************************************************************************/ - -package com.projectswg.holocore.resources.support.data.server_info.mongodb; - -import com.mongodb.client.MongoCollection; -import com.mongodb.client.model.*; -import com.projectswg.common.data.encodables.mongo.MongoData; -import com.projectswg.holocore.resources.gameplay.crafting.resource.galactic.GalacticResource; -import org.bson.Document; -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -import static java.util.stream.Collectors.toList; - -public class PswgResourceDatabase implements PswgDatabase { - - private MongoCollection collection; - - PswgResourceDatabase() { - this.collection = null; - } - - @Override - public void open(MongoCollection collection) { - this.collection = collection; - collection.createIndex(Indexes.ascending("id"), new IndexOptions().unique(true)); - } - - public void addResource(@NotNull GalacticResource resource) { - collection.replaceOne(Filters.eq("id", resource.getId()), MongoData.store(resource).toDocument(), new ReplaceOptions().upsert(true)); - } - - public void addResources(@NotNull Collection resources) { - collection.bulkWrite(resources.stream() - .map(resource -> new ReplaceOneModel<>( - Filters.eq("id", resource.getId()), - MongoData.store(resource).toDocument(), - new ReplaceOptions().upsert(true) - )) - .collect(toList()), - new BulkWriteOptions().ordered(false)); - } - - @NotNull - public List getResources() { - return collection.find().map(MongoData::new).map(data -> MongoData.create(data, GalacticResource::new)).into(new ArrayList<>()); - } - -} diff --git a/src/main/java/com/projectswg/holocore/resources/support/data/server_info/mongodb/PswgResourceDatabase.kt b/src/main/java/com/projectswg/holocore/resources/support/data/server_info/mongodb/PswgResourceDatabase.kt new file mode 100644 index 00000000..1afbf8b4 --- /dev/null +++ b/src/main/java/com/projectswg/holocore/resources/support/data/server_info/mongodb/PswgResourceDatabase.kt @@ -0,0 +1,59 @@ +/*********************************************************************************** + * Copyright (c) 2019 /// Project SWG /// www.projectswg.com * + * * + * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * + * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * + * Our goal is to create an emulator which will provide a server for players to * + * continue playing a game similar to the one they used to play. We are basing * + * it on the final publish of the game prior to end-game events. * + * * + * This file is part of Holocore. * + * * + * --------------------------------------------------------------------------------* + * * + * Holocore 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. * + * * + * Holocore 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 Holocore. If not, see //www.gnu.org/licenses/>. * + */ + +package com.projectswg.holocore.resources.support.data.server_info.mongodb + +import com.mongodb.client.MongoCollection +import com.mongodb.client.model.* +import com.projectswg.common.data.encodables.mongo.MongoData +import com.projectswg.holocore.resources.gameplay.crafting.resource.galactic.GalacticResource +import org.bson.Document +import java.util.* +import java.util.stream.Collectors.toList + +class PswgResourceDatabase(private val collection: MongoCollection) { + + var resources: List + get() = collection.find().map { MongoData.create(it) { GalacticResource() } }.into(ArrayList()) + set(value) { + collection.bulkWrite(value.stream() + .map { resource -> + ReplaceOneModel( + Filters.eq("id", resource.id), // match the resource id + MongoData.store(resource).toDocument(), // store the resource into a mongodb document + ReplaceOptions().upsert(true) // replace any matches with the new resource + ) + } + .collect(toList()), + BulkWriteOptions().ordered(false)) + } + + init { + collection.createIndex(Indexes.ascending("id"), IndexOptions().unique(true)) + } + +} diff --git a/src/main/java/com/projectswg/holocore/resources/support/data/server_info/mongodb/PswgUserDatabase.java b/src/main/java/com/projectswg/holocore/resources/support/data/server_info/mongodb/PswgUserDatabase.java deleted file mode 100644 index 5d1a72ae..00000000 --- a/src/main/java/com/projectswg/holocore/resources/support/data/server_info/mongodb/PswgUserDatabase.java +++ /dev/null @@ -1,95 +0,0 @@ -/*********************************************************************************** - * Copyright (c) 2019 /// Project SWG /// www.projectswg.com * - * * - * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * - * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * - * Our goal is to create an emulator which will provide a server for players to * - * continue playing a game similar to the one they used to play. We are basing * - * it on the final publish of the game prior to end-game events. * - * * - * This file is part of Holocore. * - * * - * --------------------------------------------------------------------------------* - * * - * Holocore 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. * - * * - * Holocore 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 Holocore. If not, see . * - ***********************************************************************************/ - -package com.projectswg.holocore.resources.support.data.server_info.mongodb; - -import com.mongodb.client.MongoCollection; -import com.mongodb.client.model.Filters; -import com.mongodb.client.model.IndexOptions; -import com.mongodb.client.model.Indexes; -import org.bson.Document; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public class PswgUserDatabase implements PswgDatabase { - - private MongoCollection collection; - - PswgUserDatabase() { - this.collection = null; - } - - @Override - public void open(MongoCollection collection) { - this.collection = collection; - collection.createIndex(Indexes.ascending("username"), new IndexOptions().unique(true)); - } - - @Nullable - public UserMetadata getUser(@NotNull String username) { - return collection.find(Filters.eq("username", username)).map(UserMetadata::new).first(); - } - - public static class UserMetadata { - - private final String accountId; - private final String username; - private final String password; - private final String accessLevel; - private final boolean banned; - - public UserMetadata(Document doc) { - this.accountId = doc.getObjectId("_id").toHexString(); - this.username = doc.getString("username"); - this.password = doc.getString("password"); - this.accessLevel = doc.getString("accessLevel"); - this.banned = doc.getBoolean("banned"); - } - - public String getAccountId() { - return accountId; - } - - public String getUsername() { - return username; - } - - public String getPassword() { - return password; - } - - public String getAccessLevel() { - return accessLevel; - } - - public boolean isBanned() { - return banned; - } - - } - -} diff --git a/src/main/java/com/projectswg/holocore/resources/support/global/network/AdminNetworkClient.java b/src/main/java/com/projectswg/holocore/resources/support/data/server_info/mongodb/PswgUserDatabase.kt similarity index 50% rename from src/main/java/com/projectswg/holocore/resources/support/global/network/AdminNetworkClient.java rename to src/main/java/com/projectswg/holocore/resources/support/data/server_info/mongodb/PswgUserDatabase.kt index 3187a054..498ea366 100644 --- a/src/main/java/com/projectswg/holocore/resources/support/global/network/AdminNetworkClient.java +++ b/src/main/java/com/projectswg/holocore/resources/support/data/server_info/mongodb/PswgUserDatabase.kt @@ -1,52 +1,63 @@ /*********************************************************************************** - * Copyright (c) 2018 /// Project SWG /// www.projectswg.com * - * * + * Copyright (c) 2019 /// Project SWG /// www.projectswg.com * + * * * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * * Our goal is to create an emulator which will provide a server for players to * * continue playing a game similar to the one they used to play. We are basing * * it on the final publish of the game prior to end-game events. * - * * + * * * This file is part of Holocore. * - * * + * * * --------------------------------------------------------------------------------* - * * + * * * Holocore 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. * - * * + * * * Holocore 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 Holocore. If not, see . * - ***********************************************************************************/ + * along with Holocore. If not, see //www.gnu.org/licenses/>. * + */ -package com.projectswg.holocore.resources.support.global.network; +package com.projectswg.holocore.resources.support.data.server_info.mongodb -import com.projectswg.common.network.packets.SWGPacket; -import me.joshlarson.jlcommon.concurrency.ThreadPool; +import com.mongodb.client.MongoCollection +import com.mongodb.client.model.Filters +import com.mongodb.client.model.IndexOptions +import com.mongodb.client.model.Indexes +import org.bson.Document -import javax.net.ssl.SSLContext; -import java.nio.channels.SocketChannel; -import java.util.Collection; - -public class AdminNetworkClient extends NetworkClient { +class PswgUserDatabase(private val collection: MongoCollection) { - public AdminNetworkClient(SocketChannel socket, SSLContext sslContext, ThreadPool securityExecutor, Collection flushList) { - super(socket, sslContext, securityExecutor, flushList); + init { + collection.createIndex(Indexes.ascending("username"), IndexOptions().unique(true)) } - @Override - protected boolean allowInbound(SWGPacket packet) { - return true; + fun getUser(username: String): UserMetadata? { + return collection + .find(Filters.eq("username", username)) + .map { UserMetadata(it) } + .first() } - @Override - protected boolean allowOutbound(SWGPacket packet) { - return true; - } +} + +class UserMetadata(doc: Document) { + + val accountId: String = doc.getObjectId("_id").toHexString() + val username: String = doc.getString("username") + val password: String = doc.getString("password") + val accessLevel: String = doc.getString("accessLevel") + val isBanned: Boolean = doc.getBoolean("banned") + + override fun toString(): String = "UserMetadata[username=$username accessLevel=$accessLevel banned=$isBanned]" + override fun equals(other: Any?): Boolean = if (other is UserMetadata) accountId == other.accountId else false + override fun hashCode(): Int = accountId.hashCode() + } diff --git a/src/main/java/com/projectswg/holocore/resources/support/global/network/NetworkClient.java b/src/main/java/com/projectswg/holocore/resources/support/global/network/NetworkClient.java deleted file mode 100644 index 71b695d6..00000000 --- a/src/main/java/com/projectswg/holocore/resources/support/global/network/NetworkClient.java +++ /dev/null @@ -1,326 +0,0 @@ -/*********************************************************************************** - * Copyright (c) 2018 /// Project SWG /// www.projectswg.com * - * * - * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * - * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * - * Our goal is to create an emulator which will provide a server for players to * - * continue playing a game similar to the one they used to play. We are basing * - * it on the final publish of the game prior to end-game events. * - * * - * This file is part of Holocore. * - * * - * --------------------------------------------------------------------------------* - * * - * Holocore 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. * - * * - * Holocore 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 Holocore. If not, see . * - ***********************************************************************************/ -package com.projectswg.holocore.resources.support.global.network; - -import com.projectswg.common.network.NetBuffer; -import com.projectswg.common.network.NetworkProtocol; -import com.projectswg.common.network.packets.SWGPacket; -import com.projectswg.common.network.packets.swg.ErrorMessage; -import com.projectswg.common.network.packets.swg.admin.AdminPacket; -import com.projectswg.common.network.packets.swg.holo.HoloConnectionStarted; -import com.projectswg.common.network.packets.swg.holo.HoloConnectionStopped; -import com.projectswg.common.network.packets.swg.holo.HoloConnectionStopped.ConnectionStoppedReason; -import com.projectswg.common.network.packets.swg.holo.HoloSetProtocolVersion; -import com.projectswg.holocore.intents.support.global.network.ConnectionClosedIntent; -import com.projectswg.holocore.intents.support.global.network.ConnectionOpenedIntent; -import com.projectswg.holocore.intents.support.global.network.InboundPacketIntent; -import com.projectswg.holocore.resources.support.data.server_info.StandardLog; -import com.projectswg.holocore.resources.support.global.player.Player; -import me.joshlarson.jlcommon.concurrency.ThreadPool; -import me.joshlarson.jlcommon.control.IntentChain; -import me.joshlarson.jlcommon.log.Log; -import me.joshlarson.jlcommon.network.SSLEngineWrapper.SSLClosedException; -import me.joshlarson.jlcommon.network.TCPServer.SecureTCPSession; -import org.jetbrains.annotations.NotNull; - -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLEngine; -import java.io.IOException; -import java.net.InetSocketAddress; -import java.nio.BufferOverflowException; -import java.nio.ByteBuffer; -import java.nio.channels.ClosedChannelException; -import java.nio.channels.SocketChannel; -import java.util.Collection; -import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; - -public class NetworkClient extends SecureTCPSession { - - private static final String[] ENABLED_CIPHERS = new String[] { - "TLS_RSA_WITH_AES_256_GCM_SHA384", - "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384" - }; - private static final int INBOUND_BUFFER = 1024; - private static final int OUTBOUND_BUFFER = 8192; - - private final Collection flushList; - private final Queue outboundPackets; - private final IntentChain intentChain; - private final AtomicBoolean connected; - private final AtomicReference status; - private final ByteBuffer outboundBuffer; - private final NetBuffer buffer; - private final Player player; - - public NetworkClient(SocketChannel socket, SSLContext sslContext, ThreadPool securityExecutor, Collection flushList) { - super(socket, createEngine(sslContext), 1024, securityExecutor::execute); - //noinspection AssignmentOrReturnOfFieldWithMutableType - this.flushList = flushList; - this.outboundPackets = new ConcurrentLinkedQueue<>(); - - this.intentChain = new IntentChain(); - this.connected = new AtomicBoolean(true); - this.status = new AtomicReference<>(SessionStatus.DISCONNECTED); - this.player = new Player(getSessionId(), (InetSocketAddress) getRemoteAddress(), this::addToOutbound); - this.outboundBuffer = ByteBuffer.allocate(OUTBOUND_BUFFER); - this.buffer = NetBuffer.allocateDirect(INBOUND_BUFFER); - } - - @Override - public void close() { - if (connected.getAndSet(false)) { - addToOutbound(new HoloConnectionStopped(ConnectionStoppedReason.OTHER_SIDE_TERMINATED)); - try { - flushPipeline(); - } catch (Throwable t) { - // Super must be called - this was just a best effort to flush - } - super.close(); - } - } - - public void close(ConnectionStoppedReason reason) { - if (connected.getAndSet(false)) { - addToOutbound(new HoloConnectionStopped(reason)); - try { - flushPipeline(); - } catch (Throwable t) { - // Super must be called - this was just a best effort to flush - } - super.close(); - } - } - - public void addToOutbound(SWGPacket p) { - if (allowOutbound(p)) { - outboundPackets.add(NetworkProtocol.encode(p).getBuffer()); - if (outboundPackets.size() >= 128) - flush(); - } - } - - public void flush() { - if (outboundPackets.isEmpty()) - return; - if (!connected.get()) - return; - try { - flushPipeline(); - } catch (SSLClosedException | ClosedChannelException e) { - close(); - } catch (NetworkClientException e) { - StandardLog.onPlayerError(this, player, e.getMessage()); - close(ConnectionStoppedReason.NETWORK); - } catch (IOException e) { - StandardLog.onPlayerError(this, player, "lost connection. %s: %s", e.getClass().getName(), e.getMessage()); - close(); - } catch (Throwable t) { - StandardLog.onPlayerError(this, player, "encountered a network exception. %s: %s", t.getClass().getName(), t.getMessage()); - close(); - } - } - - private void flushPipeline() throws IOException { - synchronized (outboundBuffer) { - flushToBuffer(); - flushToSecurity(); - } - } - - private void flushToBuffer() throws IOException { - ByteBuffer packet; - while ((packet = outboundPackets.poll()) != null) { - if (packet.remaining() > outboundBuffer.remaining()) - flushToSecurity(); - - if (packet.remaining() > outboundBuffer.remaining()) { - if (outboundBuffer.position() > 0) { - // Failed to flush earlier and there's nowhere else to put the data - throw new NetworkClientException("failed to flush data to network security"); - } - int packetSize = packet.remaining(); - writeToChannel(packet); - if (packet.hasRemaining()) - throw new NetworkClientException("failed to flush data to network security - packet too big: " + packetSize); - } else { - outboundBuffer.put(packet); - } - } - } - - private void flushToSecurity() throws IOException { - if (outboundBuffer.position() > 0) { - outboundBuffer.flip(); - try { - writeToChannel(outboundBuffer); - } catch (BufferOverflowException e) { - throw new NetworkClientException("failed to flush data to network security - buffer overflow"); - } - outboundBuffer.compact(); - assert outboundBuffer.position() == 0; - } - } - - @Override - public String toString() { - return "NetworkClient[" + getRemoteAddress() + ']'; - } - - @Override - protected void onConnected() { - StandardLog.onPlayerTrace(this, player, "connected"); - flushList.add(this); - status.set(SessionStatus.CONNECTING); - intentChain.broadcastAfter(new ConnectionOpenedIntent(player)); - } - - @Override - protected void onDisconnected() { - StandardLog.onPlayerTrace(this, player, "disconnected"); - flushList.remove(this); - intentChain.broadcastAfter(new ConnectionClosedIntent(player, ConnectionStoppedReason.OTHER_SIDE_TERMINATED)); - } - - @Override - protected void onIncomingData(@NotNull ByteBuffer data) { - synchronized (buffer) { - if (data.remaining() > buffer.remaining()) { - StandardLog.onPlayerError(this, player, "Possible hack attempt detected with buffer overflow. Closing connection to %s", getRemoteAddress()); - close(ConnectionStoppedReason.APPLICATION); - return; - } - try { - buffer.add(data); - buffer.flip(); - while (NetworkProtocol.canDecode(buffer)) { - SWGPacket p = NetworkProtocol.decode(buffer); - if (p == null || !allowInbound(p)) - continue; - p.setSocketAddress(getRemoteAddress()); - processPacket(p); - intentChain.broadcastAfter(new InboundPacketIntent(player, p)); - } - buffer.compact(); - } catch (HolocoreSessionException e) { - onSessionError(e); - } catch (IOException e) { - Log.w("Failed to process inbound packets. IOException: %s", e.getMessage()); - close(); - } - } - } - - @Override - protected void onError(Throwable t) { - StandardLog.onPlayerError(this, player, "encountered exception in networking. %s: %s", t.getClass().getName(), t.getMessage()); - } - - protected boolean allowInbound(SWGPacket packet) { - return !(packet instanceof AdminPacket); - } - - protected boolean allowOutbound(SWGPacket packet) { - return !(packet instanceof AdminPacket); - } - - private void onSessionError(HolocoreSessionException e) { - switch (e.getReason()) { - case NO_PROTOCOL: - addToOutbound(new ErrorMessage("Network Manager", "Upgrade your launcher!", false)); - addToOutbound(new HoloConnectionStopped(ConnectionStoppedReason.INVALID_PROTOCOL)); - break; - case PROTOCOL_INVALID: - addToOutbound(new HoloConnectionStopped(ConnectionStoppedReason.INVALID_PROTOCOL)); - break; - } - } - - private void processPacket(SWGPacket p) throws HolocoreSessionException { - switch (p.getPacketType()) { - case HOLO_SET_PROTOCOL_VERSION: - if (!((HoloSetProtocolVersion) p).getProtocol().equals(NetworkProtocol.VERSION)) - throw new HolocoreSessionException(SessionExceptionReason.PROTOCOL_INVALID); - - status.set(SessionStatus.CONNECTED); - addToOutbound(new HoloConnectionStarted()); - break; - case HOLO_CONNECTION_STOPPED: - close(ConnectionStoppedReason.OTHER_SIDE_TERMINATED); - break; - default: - if (status.get() != SessionStatus.CONNECTED) - throw new HolocoreSessionException(SessionExceptionReason.NO_PROTOCOL); - break; - } - } - - private static SSLEngine createEngine(SSLContext sslContext) { - SSLEngine engine = sslContext.createSSLEngine(); - engine.setUseClientMode(false); - engine.setNeedClientAuth(false); - engine.setEnabledCipherSuites(ENABLED_CIPHERS); - return engine; - } - - private enum SessionStatus { - DISCONNECTED, - CONNECTING, - CONNECTED - - } - - private enum SessionExceptionReason { - NO_PROTOCOL, - PROTOCOL_INVALID - } - - private static class HolocoreSessionException extends Exception { - - private final SessionExceptionReason reason; - - public HolocoreSessionException(SessionExceptionReason reason) { - this.reason = reason; - } - - public SessionExceptionReason getReason() { - return reason; - } - - } - - private static class NetworkClientException extends IOException { - - public NetworkClientException(String message) { - super(message); - } - - } - -} diff --git a/src/main/java/com/projectswg/holocore/resources/support/global/network/NetworkClient.kt b/src/main/java/com/projectswg/holocore/resources/support/global/network/NetworkClient.kt new file mode 100644 index 00000000..ca18b1a7 --- /dev/null +++ b/src/main/java/com/projectswg/holocore/resources/support/global/network/NetworkClient.kt @@ -0,0 +1,220 @@ +/*********************************************************************************** + * Copyright (c) 2018 /// Project SWG /// www.projectswg.com * + * * + * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * + * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * + * Our goal is to create an emulator which will provide a server for players to * + * continue playing a game similar to the one they used to play. We are basing * + * it on the final publish of the game prior to end-game events. * + * * + * This file is part of Holocore. * + * * + * --------------------------------------------------------------------------------* + * * + * Holocore 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. * + * * + * Holocore 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 Holocore. If not, see //www.gnu.org/licenses/>. * + */ +package com.projectswg.holocore.resources.support.global.network + +import com.projectswg.common.network.NetBuffer +import com.projectswg.common.network.NetworkProtocol +import com.projectswg.common.network.packets.SWGPacket +import com.projectswg.common.network.packets.swg.ErrorMessage +import com.projectswg.common.network.packets.swg.admin.AdminPacket +import com.projectswg.common.network.packets.swg.holo.HoloConnectionStarted +import com.projectswg.common.network.packets.swg.holo.HoloConnectionStopped +import com.projectswg.common.network.packets.swg.holo.HoloConnectionStopped.ConnectionStoppedReason +import com.projectswg.common.network.packets.swg.holo.HoloSetProtocolVersion +import com.projectswg.holocore.intents.support.global.network.ConnectionClosedIntent +import com.projectswg.holocore.intents.support.global.network.ConnectionOpenedIntent +import com.projectswg.holocore.intents.support.global.network.InboundPacketIntent +import com.projectswg.holocore.resources.support.data.server_info.StandardLog +import com.projectswg.holocore.resources.support.global.player.AccessLevel +import com.projectswg.holocore.resources.support.global.player.Player +import me.joshlarson.jlcommon.concurrency.Delay +import me.joshlarson.jlcommon.control.IntentChain +import me.joshlarson.jlcommon.log.Log +import java.io.IOException +import java.net.InetSocketAddress +import java.net.SocketAddress +import java.nio.ByteBuffer +import java.nio.channels.SocketChannel +import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicLong +import java.util.concurrent.atomic.AtomicReference +import java.util.function.Consumer + +class NetworkClient(private val socket: SocketChannel) { + + private val remoteAddress: SocketAddress? = try { socket.remoteAddress } catch (e: IOException) { null } + private val inboundBuffer: NetBuffer + private val intentChain: IntentChain + private val connected: AtomicBoolean + private val status: AtomicReference + val player: Player + + val id: Long + get() = player.networkId + + init { + this.inboundBuffer = NetBuffer.allocate(INBOUND_BUFFER_SIZE) + + this.intentChain = IntentChain() + this.connected = AtomicBoolean(true) + this.status = AtomicReference(SessionStatus.DISCONNECTED) + this.player = Player(SESSION_ID.getAndIncrement(), remoteAddress as InetSocketAddress?, Consumer { this.addToOutbound(it) }) + + onConnecting() + } + + @JvmOverloads + fun close(reason: ConnectionStoppedReason = ConnectionStoppedReason.OTHER_SIDE_TERMINATED) { + if (connected.getAndSet(false)) { + try { + socket.write(NetworkProtocol.encode(HoloConnectionStopped(reason)).buffer) + } catch (e: IOException) { + // Ignored - just give it a best effort + } + try { + socket.close() + } catch (e: IOException) { + // Ignored + } + + onDisconnected() + } + } + + override fun toString(): String { + return "NetworkClient[$remoteAddress]" + } + + fun addToInbound(data: ByteBuffer) { + synchronized(inboundBuffer) { + if (data.remaining() > inboundBuffer.remaining()) { + StandardLog.onPlayerError(this, player, "Possible hack attempt detected with buffer overflow. Closing connection to $remoteAddress") + close(ConnectionStoppedReason.APPLICATION) + return + } + try { + inboundBuffer.add(data) + inboundBuffer.flip() + while (NetworkProtocol.canDecode(inboundBuffer)) { + val p = NetworkProtocol.decode(inboundBuffer) + if (p == null || !allowInbound(p)) + continue + p.socketAddress = remoteAddress + processPacket(p) + intentChain.broadcastAfter(InboundPacketIntent(player, p)) + } + inboundBuffer.compact() + } catch (e: HolocoreSessionException) { + onSessionError(e) + } catch (e: IOException) { + Log.w("Failed to process inbound packets. IOException: %s", e.message) + close() + } + + } + } + + private fun addToOutbound(p: SWGPacket) { + if (allowOutbound(p) && connected.get()) { + synchronized (socket) { + try { + val buffer = NetworkProtocol.encode(p).buffer + while (connected.get() && buffer.hasRemaining()) { + socket.write(buffer) + if (buffer.hasRemaining()) + Delay.sleepMilli(1) + } + } catch (e: IOException) { + StandardLog.onPlayerError(this, player, "failed to write network data. ${e.javaClass.name}: ${e.message}") + close() + } + } + } + } + + private fun onConnecting() { + StandardLog.onPlayerTrace(this, player, "connecting") + status.set(SessionStatus.CONNECTING) + intentChain.broadcastAfter(ConnectionOpenedIntent(player)) + } + + private fun onConnected() { + StandardLog.onPlayerTrace(this, player, "connected") + status.set(SessionStatus.CONNECTED) + addToOutbound(HoloConnectionStarted()) + } + + private fun onDisconnected() { + StandardLog.onPlayerTrace(this, player, "disconnected") + intentChain.broadcastAfter(ConnectionClosedIntent(player, ConnectionStoppedReason.OTHER_SIDE_TERMINATED)) + } + + private fun allowInbound(packet: SWGPacket): Boolean { + return packet !is AdminPacket || player.accessLevel > AccessLevel.WARDEN + } + + private fun allowOutbound(packet: SWGPacket): Boolean { + return packet !is AdminPacket || player.accessLevel > AccessLevel.WARDEN + } + + private fun onSessionError(e: HolocoreSessionException) { + when (e.reason) { + SessionExceptionReason.NO_PROTOCOL -> { + addToOutbound(ErrorMessage("Network Manager", "Upgrade your launcher!", false)) + addToOutbound(HoloConnectionStopped(ConnectionStoppedReason.INVALID_PROTOCOL)) + } + SessionExceptionReason.PROTOCOL_INVALID -> addToOutbound(HoloConnectionStopped(ConnectionStoppedReason.INVALID_PROTOCOL)) + } + } + + @Throws(HolocoreSessionException::class) + private fun processPacket(p: SWGPacket) { + when (p) { + is HoloSetProtocolVersion -> { + if (p.protocol == NetworkProtocol.VERSION) + onConnected() + else + throw HolocoreSessionException(SessionExceptionReason.PROTOCOL_INVALID) + } + is HoloConnectionStopped -> close(ConnectionStoppedReason.OTHER_SIDE_TERMINATED) + else -> { + if (status.get() != SessionStatus.CONNECTED) + throw HolocoreSessionException(SessionExceptionReason.NO_PROTOCOL) + } + } + } + + private enum class SessionStatus { + DISCONNECTED, + CONNECTING, + CONNECTED + + } + + private enum class SessionExceptionReason { + NO_PROTOCOL, + PROTOCOL_INVALID + } + + private class HolocoreSessionException(val reason: SessionExceptionReason) : Exception() + + companion object { + private val SESSION_ID = AtomicLong(1) + private const val INBOUND_BUFFER_SIZE = 4096 + } + +} diff --git a/src/main/java/com/projectswg/holocore/resources/support/global/zone/ZoneRequester.java b/src/main/java/com/projectswg/holocore/resources/support/global/zone/ZoneRequester.java index b5835998..ce366256 100644 --- a/src/main/java/com/projectswg/holocore/resources/support/global/zone/ZoneRequester.java +++ b/src/main/java/com/projectswg/holocore/resources/support/global/zone/ZoneRequester.java @@ -87,7 +87,7 @@ public class ZoneRequester { } private boolean isSafeZone() { - return PswgDatabase.config().getBoolean(this, "safeZoneIn", false); + return PswgDatabase.INSTANCE.getConfig().getBoolean(this, "safeZoneIn", false); } private void sendClientFatal(Player player, String message) { diff --git a/src/main/java/com/projectswg/holocore/resources/support/npc/ai/NpcCombatMode.java b/src/main/java/com/projectswg/holocore/resources/support/npc/ai/NpcCombatMode.java index c6a7e22e..18c4a355 100644 --- a/src/main/java/com/projectswg/holocore/resources/support/npc/ai/NpcCombatMode.java +++ b/src/main/java/com/projectswg/holocore/resources/support/npc/ai/NpcCombatMode.java @@ -37,7 +37,7 @@ public class NpcCombatMode extends NpcMode { this.returnLocation = new AtomicReference<>(null); this.targets = new CopyOnWriteArraySet<>(); this.iteration = new AtomicLong(0); - this.runSpeed = PswgDatabase.config().getDouble(this, "npcRunSpeed", 9); + this.runSpeed = PswgDatabase.INSTANCE.getConfig().getDouble(this, "npcRunSpeed", 9); } @Override diff --git a/src/main/java/com/projectswg/holocore/services/gameplay/combat/loot/GrantLootService.java b/src/main/java/com/projectswg/holocore/services/gameplay/combat/loot/GrantLootService.java index 2321af8e..791d6bb6 100644 --- a/src/main/java/com/projectswg/holocore/services/gameplay/combat/loot/GrantLootService.java +++ b/src/main/java/com/projectswg/holocore/services/gameplay/combat/loot/GrantLootService.java @@ -70,7 +70,7 @@ public final class GrantLootService extends Service { public GrantLootService() { this.lootRestrictions = new ConcurrentHashMap<>(); this.executor = new ScheduledThreadPool(1, "grant-loot-service"); - this.lootRange = PswgDatabase.config().getInt(this, "lootRange", 64); + this.lootRange = PswgDatabase.INSTANCE.getConfig().getInt(this, "lootRange", 64); } @Override diff --git a/src/main/java/com/projectswg/holocore/services/gameplay/combat/loot/LootGenerationService.java b/src/main/java/com/projectswg/holocore/services/gameplay/combat/loot/LootGenerationService.java index 01afa8c5..fcee61fc 100644 --- a/src/main/java/com/projectswg/holocore/services/gameplay/combat/loot/LootGenerationService.java +++ b/src/main/java/com/projectswg/holocore/services/gameplay/combat/loot/LootGenerationService.java @@ -88,9 +88,9 @@ public final class LootGenerationService extends Service { boolean cashGenerated = false; boolean lootGenerated = false; - if (PswgDatabase.config().getBoolean(this, "cashLoot", true)) + if (PswgDatabase.INSTANCE.getConfig().getBoolean(this, "cashLoot", true)) cashGenerated = generateCreditChip(loot, lootInventory, corpse.getDifficulty(), corpse.getLevel()); - if (PswgDatabase.config().getBoolean(this, "itemLoot", true)) + if (PswgDatabase.INSTANCE.getConfig().getBoolean(this, "itemLoot", true)) lootGenerated = generateLoot(loot, killer, lootInventory); if (!cashGenerated && !lootGenerated) @@ -209,25 +209,25 @@ public final class LootGenerationService extends Service { ThreadLocalRandom random = ThreadLocalRandom.current(); - double range = PswgDatabase.config().getDouble(this, "lootCashHumanRange", 0.05); + double range = PswgDatabase.INSTANCE.getConfig().getDouble(this, "lootCashHumanRange", 0.05); double cashLootRoll = random.nextDouble(); int credits; switch (difficulty) { default: case NORMAL: - if (cashLootRoll > PswgDatabase.config().getDouble(this, "lootCashHumanNormalChance", 0.60)) + if (cashLootRoll > PswgDatabase.INSTANCE.getConfig().getDouble(this, "lootCashHumanNormalChance", 0.60)) return false; - credits = PswgDatabase.config().getInt(this, "lootCashHumanNormal", 2); + credits = PswgDatabase.INSTANCE.getConfig().getInt(this, "lootCashHumanNormal", 2); break; case ELITE: - if (cashLootRoll > PswgDatabase.config().getDouble(this, "lootCashHumanElitechance", 0.80)) + if (cashLootRoll > PswgDatabase.INSTANCE.getConfig().getDouble(this, "lootCashHumanElitechance", 0.80)) return false; - credits = PswgDatabase.config().getInt(this, "lootCashHumanElite", 5); + credits = PswgDatabase.INSTANCE.getConfig().getInt(this, "lootCashHumanElite", 5); break; case BOSS: // bosses always drop cash loot, so no need to check - credits = PswgDatabase.config().getInt(this, "lootCashHumanBoss", 9); + credits = PswgDatabase.INSTANCE.getConfig().getInt(this, "lootCashHumanBoss", 9); break; } credits *= combatLevel; diff --git a/src/main/java/com/projectswg/holocore/services/gameplay/player/experience/ExperienceLevelService.java b/src/main/java/com/projectswg/holocore/services/gameplay/player/experience/ExperienceLevelService.java index ae384183..5f448bae 100644 --- a/src/main/java/com/projectswg/holocore/services/gameplay/player/experience/ExperienceLevelService.java +++ b/src/main/java/com/projectswg/holocore/services/gameplay/player/experience/ExperienceLevelService.java @@ -24,7 +24,7 @@ public class ExperienceLevelService extends Service { private final double xpMultiplier; public ExperienceLevelService() { - xpMultiplier = PswgDatabase.config().getDouble(this, "xpMultiplier", 1); + xpMultiplier = PswgDatabase.INSTANCE.getConfig().getDouble(this, "xpMultiplier", 1); } @IntentHandler diff --git a/src/main/java/com/projectswg/holocore/services/gameplay/world/travel/PlayerMountService.java b/src/main/java/com/projectswg/holocore/services/gameplay/world/travel/PlayerMountService.java index 1b63bd2f..91cbbb2f 100644 --- a/src/main/java/com/projectswg/holocore/services/gameplay/world/travel/PlayerMountService.java +++ b/src/main/java/com/projectswg/holocore/services/gameplay/world/travel/PlayerMountService.java @@ -1,475 +1,475 @@ -/*********************************************************************************** - * Copyright (c) 2018 /// Project SWG /// www.projectswg.com * - * * - * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * - * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * - * Our goal is to create an emulator which will provide a server for players to * - * continue playing a game similar to the one they used to play. We are basing * - * it on the final publish of the game prior to end-game events. * - * * - * This file is part of PSWGCommon. * - * * - * --------------------------------------------------------------------------------* - * * - * PSWGCommon 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. * - * * - * PSWGCommon 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 PSWGCommon. If not, see . * - ***********************************************************************************/ -package com.projectswg.holocore.services.gameplay.world.travel; - -import com.projectswg.common.data.encodables.tangible.Posture; -import com.projectswg.common.network.packets.swg.zone.PlayClientEffectObjectMessage; -import com.projectswg.holocore.intents.gameplay.combat.CreatureIncapacitatedIntent; -import com.projectswg.holocore.intents.gameplay.combat.CreatureKilledIntent; -import com.projectswg.holocore.intents.gameplay.combat.buffs.BuffIntent; -import com.projectswg.holocore.intents.gameplay.world.travel.pet.*; -import com.projectswg.holocore.intents.support.global.chat.SystemMessageIntent; -import com.projectswg.holocore.intents.support.global.command.ExecuteCommandIntent; -import com.projectswg.holocore.intents.support.global.network.CloseConnectionIntent; -import com.projectswg.holocore.intents.support.global.zone.PlayerEventIntent; -import com.projectswg.holocore.intents.support.global.zone.PlayerTransformedIntent; -import com.projectswg.holocore.intents.support.objects.swg.DestroyObjectIntent; -import com.projectswg.holocore.intents.support.objects.swg.ObjectCreatedIntent; -import com.projectswg.holocore.resources.support.data.server_info.StandardLog; -import com.projectswg.holocore.resources.support.data.server_info.loader.DataLoader; -import com.projectswg.holocore.resources.support.data.server_info.loader.VehicleLoader.VehicleInfo; -import com.projectswg.holocore.resources.support.data.server_info.mongodb.PswgDatabase; -import com.projectswg.holocore.resources.support.global.network.DisconnectReason; -import com.projectswg.holocore.resources.support.objects.ObjectCreator; -import com.projectswg.holocore.resources.support.objects.swg.SWGObject; -import com.projectswg.holocore.resources.support.objects.swg.ServerAttribute; -import com.projectswg.holocore.resources.support.objects.swg.creature.CreatureObject; -import com.projectswg.holocore.resources.support.objects.swg.creature.CreatureState; -import com.projectswg.holocore.resources.support.objects.swg.group.GroupObject; -import com.projectswg.holocore.resources.support.objects.swg.intangible.IntangibleObject; -import com.projectswg.holocore.resources.support.objects.swg.tangible.OptionFlag; -import com.projectswg.holocore.services.support.objects.ObjectStorageService.ObjectLookup; -import me.joshlarson.jlcommon.control.IntentHandler; -import me.joshlarson.jlcommon.control.Service; -import org.jetbrains.annotations.NotNull; - -import java.util.Collection; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CopyOnWriteArraySet; - -public class PlayerMountService extends Service { - - // TODO no-vehicle zones (does not affect creature mounts) - - private final Map> calledMounts; - - public PlayerMountService() { - this.calledMounts = new ConcurrentHashMap<>(); - } - - @Override - public boolean stop() { - globallyStorePets(); // Don't want mounts out and about when server starts up again - - return true; - } - - String mobileForVehicleDeed(String deedTemplate) { - assert deedTemplate.startsWith("object/tangible/deed/") : "invalid vehicle deed"; - return deedTemplate.replace("_deed", "").replace("tangible/deed", "mobile"); - } - - String pcdForVehicleDeed(String deedTemplate) { - assert deedTemplate.startsWith("object/tangible/deed/") : "invalid vehicle deed"; - return deedTemplate.replace("tangible/deed/vehicle_deed", "intangible/vehicle").replace("deed", "pcd"); - } - - @IntentHandler - private void handleCreatureIncapacitatedIntent(CreatureIncapacitatedIntent cii) { - CreatureObject player = cii.getIncappee(); - if (player.isPlayer()) - exitMount(player); - } - - @IntentHandler - private void handleCreatureKilledIntent(CreatureKilledIntent cki) { - CreatureObject player = cki.getCorpse(); - if (player.isPlayer()) - exitMount(player); - } - - @IntentHandler - private void handleExecuteCommandIntent(ExecuteCommandIntent eci) { - String cmdName = eci.getCommand().getName(); - SWGObject target = eci.getTarget(); - - if (!(target instanceof CreatureObject)) - return; - - if (cmdName.equals("mount")) - enterMount(eci.getSource(), (CreatureObject) target); - else if (cmdName.equals("dismount")) - exitMount(eci.getSource(), (CreatureObject) target); - } - - @IntentHandler - private void handlePlayerTransformedIntent(PlayerTransformedIntent pti) { - CreatureObject player = pti.getPlayer(); - Set mounts = calledMounts.get(player); - if (mounts == null) - return; - - for (Mount m : mounts) { - if (!player.getAware().contains(m.getMount())) - storeMount(player, m.getMount(), m.getPetControlDevice()); - } - } - - @IntentHandler - private void handleMount(MountIntent pmi) { - enterMount(pmi.getCreature(), pmi.getPet()); - } - - @IntentHandler - private void handleDismount(DismountIntent pmi) { - exitMount(pmi.getCreature(), pmi.getPet()); - } - - @IntentHandler - private void handlePetDeviceCall(PetDeviceCallIntent pci) { - callMount(pci.getCreature(), pci.getControlDevice()); - } - - @IntentHandler - private void handlePetStore(StoreMountIntent psi) { - CreatureObject creature = psi.getCreature(); - CreatureObject mount = psi.getPet(); - - Collection mounts = calledMounts.get(creature); - if (mounts != null) { - for (Mount p : mounts) { - if (p.getMount() == mount) { - storeMount(creature, mount, p.getPetControlDevice()); - return; - } - } - } - SystemMessageIntent.broadcastPersonal(psi.getCreature().getOwner(), "Could not find mount to store!"); - } - - @IntentHandler - private void handlePetDeviceStore(PetDeviceStoreIntent psi) { - CreatureObject creature = psi.getCreature(); - IntangibleObject pcd = psi.getControlDevice(); - - Collection mounts = calledMounts.get(creature); - if (mounts != null) { - for (Mount mount : mounts) { - if (mount.getPetControlDevice() == pcd) { - storeMount(creature, mount.getMount(), pcd); - return; - } - } - } - pcd.setCount(IntangibleObject.COUNT_PCD_STORED); - } - - @IntentHandler - private void handleVehicleDeedGenerate(VehicleDeedGenerateIntent vdgi) { - if (!vdgi.getDeed().getTemplate().startsWith("object/tangible/deed/vehicle_deed/")) { - SystemMessageIntent.broadcastPersonal(vdgi.getCreature().getOwner(), "Invalid vehicle deed!"); - return; - } - generateVehicle(vdgi.getCreature(), vdgi.getDeed()); - } - - @IntentHandler - private void handlePlayerEventIntent(PlayerEventIntent pei) { - CreatureObject creature = pei.getPlayer().getCreatureObject(); - if (creature == null) - return; - switch (pei.getEvent()) { - case PE_LOGGED_OUT: - case PE_DISAPPEAR: - case PE_DESTROYED: - case PE_SERVER_KICKED: - storeMounts(creature); - break; - default: - break; - } - } - - private void generateVehicle(CreatureObject creator, SWGObject deed) { - String pcdTemplate = pcdForVehicleDeed(deed.getTemplate()); - VehicleInfo vehicleInfo = DataLoader.vehicles().getVehicleFromPcdIff(pcdTemplate); - if (vehicleInfo == null) { - StandardLog.onPlayerError(this, creator, "Unknown vehicle created from deed: %s", deed.getTemplate()); - return; - } - IntangibleObject vehicleControlDevice = (IntangibleObject) ObjectCreator.createObjectFromTemplate(pcdTemplate); - - DestroyObjectIntent.broadcast(deed); - - vehicleControlDevice.setServerAttribute(ServerAttribute.PCD_PET_TEMPLATE, vehicleInfo.getObjectTemplate()); - vehicleControlDevice.setCount(IntangibleObject.COUNT_PCD_STORED); - vehicleControlDevice.moveToContainer(creator.getDatapad()); - ObjectCreatedIntent.broadcast(vehicleControlDevice); - - callMount(creator, vehicleControlDevice); // Once generated, the vehicle is called - } - - private void callMount(CreatureObject player, IntangibleObject mountControlDevice) { - if (player.getParent() != null || player.isInCombat()) { - return; - } - if (player.getDatapad() != mountControlDevice.getParent()) { - StandardLog.onPlayerError(this, player, "disconnecting - attempted to call another player's mount [%s]", mountControlDevice); - CloseConnectionIntent.broadcast(player.getOwner(), DisconnectReason.SUSPECTED_HACK); - return; - } - - String template = mountControlDevice.getServerTextAttribute(ServerAttribute.PCD_PET_TEMPLATE); - assert template != null : "mount control device doesn't have mount template attribute"; - CreatureObject mount = (CreatureObject) ObjectCreator.createObjectFromTemplate(template); - mount.systemMove(null, player.getLocation()); - mount.addOptionFlags(OptionFlag.MOUNT); // The mount won't appear properly if this isn't set - mount.setPvpFaction(player.getPvpFaction()); - mount.setOwnerId(player.getObjectId()); // Client crash if this isn't set before making anyone aware - - // TODO after combat there's a delay - // TODO update faction status on mount if necessary - - Mount mountRecord = new Mount(mountControlDevice, mount); - Set mounts = calledMounts.computeIfAbsent(player, c -> new CopyOnWriteArraySet<>()); - if (mountControlDevice.getCount() == IntangibleObject.COUNT_PCD_CALLED || !mounts.add(mountRecord)) { - StandardLog.onPlayerTrace(this, player, "already called mount %s", mount); - return; - } - if (mounts.size() > getMountLimit()) { - mounts.remove(mountRecord); - StandardLog.onPlayerTrace(this, player, "hit mount limit of %d", getMountLimit()); - return; - } - mountControlDevice.setCount(IntangibleObject.COUNT_PCD_CALLED); - VehicleInfo vehicleInfo = DataLoader.vehicles().getVehicleFromIff(mount.getTemplate()); - if (vehicleInfo != null) { - mount.setRunSpeed(vehicleInfo.getSpeed()); - mount.setWalkSpeed(vehicleInfo.getMinSpeed()); - mount.setTurnScale(vehicleInfo.getTurnRateMax()); - mount.setAccelScale(vehicleInfo.getAccelMax()); - mount.putCustomization("/private/index_speed_max", (int) (vehicleInfo.getSpeed() * 10d)); - mount.putCustomization("/private/index_speed_min", (int) (vehicleInfo.getMinSpeed() * 10d)); - mount.putCustomization("/private/index_turn_rate_min", vehicleInfo.getTurnRate()); - mount.putCustomization("/private/index_turn_rate_max", vehicleInfo.getTurnRateMax()); - mount.putCustomization("/private/index_accel_min", (int) (vehicleInfo.getAccelMin() * 10d)); - mount.putCustomization("/private/index_accel_max", (int) (vehicleInfo.getAccelMax() * 10d)); - mount.putCustomization("/private/index_decel", (int) (vehicleInfo.getDecel() * 10d)); - mount.putCustomization("/private/index_damp_roll", (int) (vehicleInfo.getDampingRoll() * 10d)); - mount.putCustomization("/private/index_damp_pitch", (int) (vehicleInfo.getDampingPitch() * 10d)); - mount.putCustomization("/private/index_damp_height", (int) (vehicleInfo.getDampingHeight() * 10d)); - mount.putCustomization("/private/index_glide", (int) (vehicleInfo.getGlide() * 10d)); - mount.putCustomization("/private/index_banking", (int) vehicleInfo.getBankingAngle()); - mount.putCustomization("/private/index_hover_height", (int) (vehicleInfo.getHoverHeight() * 10d)); - mount.putCustomization("/private/index_auto_level", (int) (vehicleInfo.getAutoLevel() * 100d)); - mount.putCustomization("/private/index_strafe", vehicleInfo.isStrafe() ? 1 : 0); - } - - ObjectCreatedIntent.broadcast(mount); - StandardLog.onPlayerTrace(this, player, "called mount %s at %s %s", mount, mount.getTerrain(), mount.getLocation().getPosition()); - cleanupCalledMounts(); - } - - private void enterMount(CreatureObject player, CreatureObject mount) { - if (!isMountable(mount) || mount.getParent() != null) { - StandardLog.onPlayerTrace(this, player, "attempted to mount %s when it's not mountable", mount); - return; - } - - if (player.getParent() != null || player.getPosture() != Posture.UPRIGHT) - return; - - if (player.getObjectId() == mount.getOwnerId()) { - player.moveToSlot(mount, "rider", mount.getArrangementId(player)); - } else if (mount.getSlottedObject("rider") != null) { - GroupObject group = (GroupObject) ObjectLookup.getObjectById(player.getGroupId()); - if (group == null || !group.getGroupMembers().values().contains(mount.getOwnerId())) { - StandardLog.onPlayerTrace(this, player, "attempted to mount %s when not in the same group as the owner", mount); - return; - } - boolean added = false; - for (int i = 1; i <= 7; i++) { - if (!mount.hasSlot("rider" + i)) { - StandardLog.onPlayerTrace(this, player, "attempted to mount %s when no slots remain", mount); - return; - } - if (mount.getSlottedObject("rider" + i) == null) { - player.moveToSlot(mount, "rider" + i, mount.getArrangementId(player)); - added = true; - break; - } - } - if (!added) { - StandardLog.onPlayerTrace(this, player, "attempted to mount %s when no slots remain", mount); - return; - } - } else { - return; - } - - player.setStatesBitmask(CreatureState.RIDING_MOUNT); - mount.setStatesBitmask(CreatureState.MOUNTED_CREATURE); - mount.setPosture(Posture.DRIVING_VEHICLE); - - VehicleInfo vehicleInfo = DataLoader.vehicles().getVehicleFromIff(mount.getTemplate()); - if (vehicleInfo != null) { - if (!vehicleInfo.getPlayerBuff().isEmpty()) - BuffIntent.broadcast(vehicleInfo.getPlayerBuff(), player, player, false); - if (!vehicleInfo.getVehicleBuff().isEmpty()) - BuffIntent.broadcast(vehicleInfo.getVehicleBuff(), player, mount, false); - if (!vehicleInfo.getBuffClientEffect().isEmpty()) - player.sendObservers(new PlayClientEffectObjectMessage(vehicleInfo.getBuffClientEffect(), "", mount.getObjectId(), "")); - } - - player.inheritMovement(mount); - StandardLog.onPlayerEvent(this, player, "mounted %s", mount); - } - - private void exitMount(CreatureObject player) { - if (!player.isStatesBitmask(CreatureState.RIDING_MOUNT)) - return; - SWGObject parent = player.getParent(); - if (!(parent instanceof CreatureObject)) - return; - CreatureObject mount = (CreatureObject) parent; - if (isMountable(mount) && mount.isStatesBitmask(CreatureState.MOUNTED_CREATURE)) - exitMount(player, mount); - } - - private void exitMount(CreatureObject player, CreatureObject mount) { - if (!isMountable(mount)) { - StandardLog.onPlayerTrace(this, player, "attempted to dismount %s when it's not mountable", mount); - return; - } - - - VehicleInfo vehicleInfo = DataLoader.vehicles().getVehicleFromIff(mount.getTemplate()); - if (player.getParent() == mount) - dismount(player, mount, vehicleInfo); - - if (mount.getSlottedObject("rider") == null) { - for (SWGObject child : mount.getSlottedObjects()) { - assert child instanceof CreatureObject; - dismount((CreatureObject) child, mount, vehicleInfo); - } - if (vehicleInfo != null && !vehicleInfo.getVehicleBuff().isEmpty()) - BuffIntent.broadcast(vehicleInfo.getVehicleBuff(), player, mount, true); - mount.clearStatesBitmask(CreatureState.MOUNTED_CREATURE); - mount.setPosture(Posture.UPRIGHT); - } - } - - private void storeMount(@NotNull CreatureObject player, @NotNull CreatureObject mount, @NotNull IntangibleObject mountControlDevice) { - if (player.isInCombat()) - return; - - if (isMountable(mount) && mount.getOwnerId() == player.getObjectId()) { - // Dismount anyone riding the mount about to be stored - for (SWGObject rider : mount.getSlottedObjects()) { - assert rider instanceof CreatureObject; - exitMount((CreatureObject) rider, mount); - } - assert mount.getSlottedObjects().isEmpty(); - - // Remove the record of the mount - Collection mounts = calledMounts.get(player); - if (mounts != null) - mounts.remove(new Mount(mountControlDevice, mount)); - - // Destroy the mount - mountControlDevice.setCount(IntangibleObject.COUNT_PCD_STORED); - player.broadcast(new DestroyObjectIntent(mount)); - StandardLog.onPlayerTrace(this, player, "stored mount %s at %s %s", mount, mount.getTerrain(), mount.getLocation().getPosition()); - } - cleanupCalledMounts(); - } - - private void storeMounts(CreatureObject player) { - Collection mounts = calledMounts.remove(player); - if (mounts == null) - return; - - for (Mount mount : mounts) { - storeMount(player, mount.getMount(), mount.getPetControlDevice()); - } - } - - /** - * Stores all called mounts by all players - */ - private void globallyStorePets() { - calledMounts.keySet().forEach(this::storeMounts); - } - - /** - * Removes mount records where there are no mounts called - */ - private void cleanupCalledMounts() { - calledMounts.entrySet().removeIf(e -> e.getValue().isEmpty()); - } - - private void dismount(CreatureObject player, CreatureObject mount, VehicleInfo vehicleInfo) { - assert player.getParent() == mount; - player.clearStatesBitmask(CreatureState.RIDING_MOUNT); - player.moveToContainer(null, mount.getLocation()); - player.resetMovement(); - if (vehicleInfo != null && !vehicleInfo.getPlayerBuff().isEmpty()) - BuffIntent.broadcast(vehicleInfo.getPlayerBuff(), player, mount, true); - StandardLog.onPlayerEvent(this, player, "dismounted %s", mount); - } - - private int getMountLimit() { - return PswgDatabase.config().getInt(this, "mountLimit", 1); - } - - private static boolean isMountable(CreatureObject mount) { - return mount.hasOptionFlags(OptionFlag.MOUNT); - } - - private static class Mount { - - private final IntangibleObject mountControlDevice; - private final CreatureObject mount; - - private Mount(IntangibleObject mountControlDevice, CreatureObject mount) { - this.mountControlDevice = mountControlDevice; - this.mount = mount; - } - - public IntangibleObject getPetControlDevice() { - return mountControlDevice; - } - - public CreatureObject getMount() { - return mount; - } - - @Override - public int hashCode() { - return mountControlDevice.hashCode(); - } - - @Override - public boolean equals(Object o) { - return o instanceof Mount && ((Mount) o).mountControlDevice.equals(mountControlDevice); - } - - } - -} +/*********************************************************************************** + * Copyright (c) 2018 /// Project SWG /// www.projectswg.com * + * * + * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * + * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * + * Our goal is to create an emulator which will provide a server for players to * + * continue playing a game similar to the one they used to play. We are basing * + * it on the final publish of the game prior to end-game events. * + * * + * This file is part of PSWGCommon. * + * * + * --------------------------------------------------------------------------------* + * * + * PSWGCommon 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. * + * * + * PSWGCommon 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 PSWGCommon. If not, see . * + ***********************************************************************************/ +package com.projectswg.holocore.services.gameplay.world.travel; + +import com.projectswg.common.data.encodables.tangible.Posture; +import com.projectswg.common.network.packets.swg.zone.PlayClientEffectObjectMessage; +import com.projectswg.holocore.intents.gameplay.combat.CreatureIncapacitatedIntent; +import com.projectswg.holocore.intents.gameplay.combat.CreatureKilledIntent; +import com.projectswg.holocore.intents.gameplay.combat.buffs.BuffIntent; +import com.projectswg.holocore.intents.gameplay.world.travel.pet.*; +import com.projectswg.holocore.intents.support.global.chat.SystemMessageIntent; +import com.projectswg.holocore.intents.support.global.command.ExecuteCommandIntent; +import com.projectswg.holocore.intents.support.global.network.CloseConnectionIntent; +import com.projectswg.holocore.intents.support.global.zone.PlayerEventIntent; +import com.projectswg.holocore.intents.support.global.zone.PlayerTransformedIntent; +import com.projectswg.holocore.intents.support.objects.swg.DestroyObjectIntent; +import com.projectswg.holocore.intents.support.objects.swg.ObjectCreatedIntent; +import com.projectswg.holocore.resources.support.data.server_info.StandardLog; +import com.projectswg.holocore.resources.support.data.server_info.loader.DataLoader; +import com.projectswg.holocore.resources.support.data.server_info.loader.VehicleLoader.VehicleInfo; +import com.projectswg.holocore.resources.support.data.server_info.mongodb.PswgDatabase; +import com.projectswg.holocore.resources.support.global.network.DisconnectReason; +import com.projectswg.holocore.resources.support.objects.ObjectCreator; +import com.projectswg.holocore.resources.support.objects.swg.SWGObject; +import com.projectswg.holocore.resources.support.objects.swg.ServerAttribute; +import com.projectswg.holocore.resources.support.objects.swg.creature.CreatureObject; +import com.projectswg.holocore.resources.support.objects.swg.creature.CreatureState; +import com.projectswg.holocore.resources.support.objects.swg.group.GroupObject; +import com.projectswg.holocore.resources.support.objects.swg.intangible.IntangibleObject; +import com.projectswg.holocore.resources.support.objects.swg.tangible.OptionFlag; +import com.projectswg.holocore.services.support.objects.ObjectStorageService.ObjectLookup; +import me.joshlarson.jlcommon.control.IntentHandler; +import me.joshlarson.jlcommon.control.Service; +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArraySet; + +public class PlayerMountService extends Service { + + // TODO no-vehicle zones (does not affect creature mounts) + + private final Map> calledMounts; + + public PlayerMountService() { + this.calledMounts = new ConcurrentHashMap<>(); + } + + @Override + public boolean stop() { + globallyStorePets(); // Don't want mounts out and about when server starts up again + + return true; + } + + String mobileForVehicleDeed(String deedTemplate) { + assert deedTemplate.startsWith("object/tangible/deed/") : "invalid vehicle deed"; + return deedTemplate.replace("_deed", "").replace("tangible/deed", "mobile"); + } + + String pcdForVehicleDeed(String deedTemplate) { + assert deedTemplate.startsWith("object/tangible/deed/") : "invalid vehicle deed"; + return deedTemplate.replace("tangible/deed/vehicle_deed", "intangible/vehicle").replace("deed", "pcd"); + } + + @IntentHandler + private void handleCreatureIncapacitatedIntent(CreatureIncapacitatedIntent cii) { + CreatureObject player = cii.getIncappee(); + if (player.isPlayer()) + exitMount(player); + } + + @IntentHandler + private void handleCreatureKilledIntent(CreatureKilledIntent cki) { + CreatureObject player = cki.getCorpse(); + if (player.isPlayer()) + exitMount(player); + } + + @IntentHandler + private void handleExecuteCommandIntent(ExecuteCommandIntent eci) { + String cmdName = eci.getCommand().getName(); + SWGObject target = eci.getTarget(); + + if (!(target instanceof CreatureObject)) + return; + + if (cmdName.equals("mount")) + enterMount(eci.getSource(), (CreatureObject) target); + else if (cmdName.equals("dismount")) + exitMount(eci.getSource(), (CreatureObject) target); + } + + @IntentHandler + private void handlePlayerTransformedIntent(PlayerTransformedIntent pti) { + CreatureObject player = pti.getPlayer(); + Set mounts = calledMounts.get(player); + if (mounts == null) + return; + + for (Mount m : mounts) { + if (!player.getAware().contains(m.getMount())) + storeMount(player, m.getMount(), m.getPetControlDevice()); + } + } + + @IntentHandler + private void handleMount(MountIntent pmi) { + enterMount(pmi.getCreature(), pmi.getPet()); + } + + @IntentHandler + private void handleDismount(DismountIntent pmi) { + exitMount(pmi.getCreature(), pmi.getPet()); + } + + @IntentHandler + private void handlePetDeviceCall(PetDeviceCallIntent pci) { + callMount(pci.getCreature(), pci.getControlDevice()); + } + + @IntentHandler + private void handlePetStore(StoreMountIntent psi) { + CreatureObject creature = psi.getCreature(); + CreatureObject mount = psi.getPet(); + + Collection mounts = calledMounts.get(creature); + if (mounts != null) { + for (Mount p : mounts) { + if (p.getMount() == mount) { + storeMount(creature, mount, p.getPetControlDevice()); + return; + } + } + } + SystemMessageIntent.broadcastPersonal(psi.getCreature().getOwner(), "Could not find mount to store!"); + } + + @IntentHandler + private void handlePetDeviceStore(PetDeviceStoreIntent psi) { + CreatureObject creature = psi.getCreature(); + IntangibleObject pcd = psi.getControlDevice(); + + Collection mounts = calledMounts.get(creature); + if (mounts != null) { + for (Mount mount : mounts) { + if (mount.getPetControlDevice() == pcd) { + storeMount(creature, mount.getMount(), pcd); + return; + } + } + } + pcd.setCount(IntangibleObject.COUNT_PCD_STORED); + } + + @IntentHandler + private void handleVehicleDeedGenerate(VehicleDeedGenerateIntent vdgi) { + if (!vdgi.getDeed().getTemplate().startsWith("object/tangible/deed/vehicle_deed/")) { + SystemMessageIntent.broadcastPersonal(vdgi.getCreature().getOwner(), "Invalid vehicle deed!"); + return; + } + generateVehicle(vdgi.getCreature(), vdgi.getDeed()); + } + + @IntentHandler + private void handlePlayerEventIntent(PlayerEventIntent pei) { + CreatureObject creature = pei.getPlayer().getCreatureObject(); + if (creature == null) + return; + switch (pei.getEvent()) { + case PE_LOGGED_OUT: + case PE_DISAPPEAR: + case PE_DESTROYED: + case PE_SERVER_KICKED: + storeMounts(creature); + break; + default: + break; + } + } + + private void generateVehicle(CreatureObject creator, SWGObject deed) { + String pcdTemplate = pcdForVehicleDeed(deed.getTemplate()); + VehicleInfo vehicleInfo = DataLoader.vehicles().getVehicleFromPcdIff(pcdTemplate); + if (vehicleInfo == null) { + StandardLog.onPlayerError(this, creator, "Unknown vehicle created from deed: %s", deed.getTemplate()); + return; + } + IntangibleObject vehicleControlDevice = (IntangibleObject) ObjectCreator.createObjectFromTemplate(pcdTemplate); + + DestroyObjectIntent.broadcast(deed); + + vehicleControlDevice.setServerAttribute(ServerAttribute.PCD_PET_TEMPLATE, vehicleInfo.getObjectTemplate()); + vehicleControlDevice.setCount(IntangibleObject.COUNT_PCD_STORED); + vehicleControlDevice.moveToContainer(creator.getDatapad()); + ObjectCreatedIntent.broadcast(vehicleControlDevice); + + callMount(creator, vehicleControlDevice); // Once generated, the vehicle is called + } + + private void callMount(CreatureObject player, IntangibleObject mountControlDevice) { + if (player.getParent() != null || player.isInCombat()) { + return; + } + if (player.getDatapad() != mountControlDevice.getParent()) { + StandardLog.onPlayerError(this, player, "disconnecting - attempted to call another player's mount [%s]", mountControlDevice); + CloseConnectionIntent.broadcast(player.getOwner(), DisconnectReason.SUSPECTED_HACK); + return; + } + + String template = mountControlDevice.getServerTextAttribute(ServerAttribute.PCD_PET_TEMPLATE); + assert template != null : "mount control device doesn't have mount template attribute"; + CreatureObject mount = (CreatureObject) ObjectCreator.createObjectFromTemplate(template); + mount.systemMove(null, player.getLocation()); + mount.addOptionFlags(OptionFlag.MOUNT); // The mount won't appear properly if this isn't set + mount.setPvpFaction(player.getPvpFaction()); + mount.setOwnerId(player.getObjectId()); // Client crash if this isn't set before making anyone aware + + // TODO after combat there's a delay + // TODO update faction status on mount if necessary + + Mount mountRecord = new Mount(mountControlDevice, mount); + Set mounts = calledMounts.computeIfAbsent(player, c -> new CopyOnWriteArraySet<>()); + if (mountControlDevice.getCount() == IntangibleObject.COUNT_PCD_CALLED || !mounts.add(mountRecord)) { + StandardLog.onPlayerTrace(this, player, "already called mount %s", mount); + return; + } + if (mounts.size() > getMountLimit()) { + mounts.remove(mountRecord); + StandardLog.onPlayerTrace(this, player, "hit mount limit of %d", getMountLimit()); + return; + } + mountControlDevice.setCount(IntangibleObject.COUNT_PCD_CALLED); + VehicleInfo vehicleInfo = DataLoader.vehicles().getVehicleFromIff(mount.getTemplate()); + if (vehicleInfo != null) { + mount.setRunSpeed(vehicleInfo.getSpeed()); + mount.setWalkSpeed(vehicleInfo.getMinSpeed()); + mount.setTurnScale(vehicleInfo.getTurnRateMax()); + mount.setAccelScale(vehicleInfo.getAccelMax()); + mount.putCustomization("/private/index_speed_max", (int) (vehicleInfo.getSpeed() * 10d)); + mount.putCustomization("/private/index_speed_min", (int) (vehicleInfo.getMinSpeed() * 10d)); + mount.putCustomization("/private/index_turn_rate_min", vehicleInfo.getTurnRate()); + mount.putCustomization("/private/index_turn_rate_max", vehicleInfo.getTurnRateMax()); + mount.putCustomization("/private/index_accel_min", (int) (vehicleInfo.getAccelMin() * 10d)); + mount.putCustomization("/private/index_accel_max", (int) (vehicleInfo.getAccelMax() * 10d)); + mount.putCustomization("/private/index_decel", (int) (vehicleInfo.getDecel() * 10d)); + mount.putCustomization("/private/index_damp_roll", (int) (vehicleInfo.getDampingRoll() * 10d)); + mount.putCustomization("/private/index_damp_pitch", (int) (vehicleInfo.getDampingPitch() * 10d)); + mount.putCustomization("/private/index_damp_height", (int) (vehicleInfo.getDampingHeight() * 10d)); + mount.putCustomization("/private/index_glide", (int) (vehicleInfo.getGlide() * 10d)); + mount.putCustomization("/private/index_banking", (int) vehicleInfo.getBankingAngle()); + mount.putCustomization("/private/index_hover_height", (int) (vehicleInfo.getHoverHeight() * 10d)); + mount.putCustomization("/private/index_auto_level", (int) (vehicleInfo.getAutoLevel() * 100d)); + mount.putCustomization("/private/index_strafe", vehicleInfo.isStrafe() ? 1 : 0); + } + + ObjectCreatedIntent.broadcast(mount); + StandardLog.onPlayerTrace(this, player, "called mount %s at %s %s", mount, mount.getTerrain(), mount.getLocation().getPosition()); + cleanupCalledMounts(); + } + + private void enterMount(CreatureObject player, CreatureObject mount) { + if (!isMountable(mount) || mount.getParent() != null) { + StandardLog.onPlayerTrace(this, player, "attempted to mount %s when it's not mountable", mount); + return; + } + + if (player.getParent() != null || player.getPosture() != Posture.UPRIGHT) + return; + + if (player.getObjectId() == mount.getOwnerId()) { + player.moveToSlot(mount, "rider", mount.getArrangementId(player)); + } else if (mount.getSlottedObject("rider") != null) { + GroupObject group = (GroupObject) ObjectLookup.getObjectById(player.getGroupId()); + if (group == null || !group.getGroupMembers().values().contains(mount.getOwnerId())) { + StandardLog.onPlayerTrace(this, player, "attempted to mount %s when not in the same group as the owner", mount); + return; + } + boolean added = false; + for (int i = 1; i <= 7; i++) { + if (!mount.hasSlot("rider" + i)) { + StandardLog.onPlayerTrace(this, player, "attempted to mount %s when no slots remain", mount); + return; + } + if (mount.getSlottedObject("rider" + i) == null) { + player.moveToSlot(mount, "rider" + i, mount.getArrangementId(player)); + added = true; + break; + } + } + if (!added) { + StandardLog.onPlayerTrace(this, player, "attempted to mount %s when no slots remain", mount); + return; + } + } else { + return; + } + + player.setStatesBitmask(CreatureState.RIDING_MOUNT); + mount.setStatesBitmask(CreatureState.MOUNTED_CREATURE); + mount.setPosture(Posture.DRIVING_VEHICLE); + + VehicleInfo vehicleInfo = DataLoader.vehicles().getVehicleFromIff(mount.getTemplate()); + if (vehicleInfo != null) { + if (!vehicleInfo.getPlayerBuff().isEmpty()) + BuffIntent.broadcast(vehicleInfo.getPlayerBuff(), player, player, false); + if (!vehicleInfo.getVehicleBuff().isEmpty()) + BuffIntent.broadcast(vehicleInfo.getVehicleBuff(), player, mount, false); + if (!vehicleInfo.getBuffClientEffect().isEmpty()) + player.sendObservers(new PlayClientEffectObjectMessage(vehicleInfo.getBuffClientEffect(), "", mount.getObjectId(), "")); + } + + player.inheritMovement(mount); + StandardLog.onPlayerEvent(this, player, "mounted %s", mount); + } + + private void exitMount(CreatureObject player) { + if (!player.isStatesBitmask(CreatureState.RIDING_MOUNT)) + return; + SWGObject parent = player.getParent(); + if (!(parent instanceof CreatureObject)) + return; + CreatureObject mount = (CreatureObject) parent; + if (isMountable(mount) && mount.isStatesBitmask(CreatureState.MOUNTED_CREATURE)) + exitMount(player, mount); + } + + private void exitMount(CreatureObject player, CreatureObject mount) { + if (!isMountable(mount)) { + StandardLog.onPlayerTrace(this, player, "attempted to dismount %s when it's not mountable", mount); + return; + } + + + VehicleInfo vehicleInfo = DataLoader.vehicles().getVehicleFromIff(mount.getTemplate()); + if (player.getParent() == mount) + dismount(player, mount, vehicleInfo); + + if (mount.getSlottedObject("rider") == null) { + for (SWGObject child : mount.getSlottedObjects()) { + assert child instanceof CreatureObject; + dismount((CreatureObject) child, mount, vehicleInfo); + } + if (vehicleInfo != null && !vehicleInfo.getVehicleBuff().isEmpty()) + BuffIntent.broadcast(vehicleInfo.getVehicleBuff(), player, mount, true); + mount.clearStatesBitmask(CreatureState.MOUNTED_CREATURE); + mount.setPosture(Posture.UPRIGHT); + } + } + + private void storeMount(@NotNull CreatureObject player, @NotNull CreatureObject mount, @NotNull IntangibleObject mountControlDevice) { + if (player.isInCombat()) + return; + + if (isMountable(mount) && mount.getOwnerId() == player.getObjectId()) { + // Dismount anyone riding the mount about to be stored + for (SWGObject rider : mount.getSlottedObjects()) { + assert rider instanceof CreatureObject; + exitMount((CreatureObject) rider, mount); + } + assert mount.getSlottedObjects().isEmpty(); + + // Remove the record of the mount + Collection mounts = calledMounts.get(player); + if (mounts != null) + mounts.remove(new Mount(mountControlDevice, mount)); + + // Destroy the mount + mountControlDevice.setCount(IntangibleObject.COUNT_PCD_STORED); + player.broadcast(new DestroyObjectIntent(mount)); + StandardLog.onPlayerTrace(this, player, "stored mount %s at %s %s", mount, mount.getTerrain(), mount.getLocation().getPosition()); + } + cleanupCalledMounts(); + } + + private void storeMounts(CreatureObject player) { + Collection mounts = calledMounts.remove(player); + if (mounts == null) + return; + + for (Mount mount : mounts) { + storeMount(player, mount.getMount(), mount.getPetControlDevice()); + } + } + + /** + * Stores all called mounts by all players + */ + private void globallyStorePets() { + calledMounts.keySet().forEach(this::storeMounts); + } + + /** + * Removes mount records where there are no mounts called + */ + private void cleanupCalledMounts() { + calledMounts.entrySet().removeIf(e -> e.getValue().isEmpty()); + } + + private void dismount(CreatureObject player, CreatureObject mount, VehicleInfo vehicleInfo) { + assert player.getParent() == mount; + player.clearStatesBitmask(CreatureState.RIDING_MOUNT); + player.moveToContainer(null, mount.getLocation()); + player.resetMovement(); + if (vehicleInfo != null && !vehicleInfo.getPlayerBuff().isEmpty()) + BuffIntent.broadcast(vehicleInfo.getPlayerBuff(), player, mount, true); + StandardLog.onPlayerEvent(this, player, "dismounted %s", mount); + } + + private int getMountLimit() { + return PswgDatabase.INSTANCE.getConfig().getInt(this, "mountLimit", 1); + } + + private static boolean isMountable(CreatureObject mount) { + return mount.hasOptionFlags(OptionFlag.MOUNT); + } + + private static class Mount { + + private final IntangibleObject mountControlDevice; + private final CreatureObject mount; + + private Mount(IntangibleObject mountControlDevice, CreatureObject mount) { + this.mountControlDevice = mountControlDevice; + this.mount = mount; + } + + public IntangibleObject getPetControlDevice() { + return mountControlDevice; + } + + public CreatureObject getMount() { + return mount; + } + + @Override + public int hashCode() { + return mountControlDevice.hashCode(); + } + + @Override + public boolean equals(Object o) { + return o instanceof Mount && ((Mount) o).mountControlDevice.equals(mountControlDevice); + } + + } + +} diff --git a/src/main/java/com/projectswg/holocore/services/gameplay/world/travel/TravelService.java b/src/main/java/com/projectswg/holocore/services/gameplay/world/travel/TravelService.java index 7cad934e..7c756e07 100644 --- a/src/main/java/com/projectswg/holocore/services/gameplay/world/travel/TravelService.java +++ b/src/main/java/com/projectswg/holocore/services/gameplay/world/travel/TravelService.java @@ -261,7 +261,7 @@ public class TravelService extends Service { } private double getTicketPriceFactor() { - return PswgDatabase.config().getDouble(this, "ticketPriceFactor", 1); + return PswgDatabase.INSTANCE.getConfig().getDouble(this, "ticketPriceFactor", 1); } private void sendTravelMessage(CreatureObject creature, String message) { diff --git a/src/main/java/com/projectswg/holocore/services/support/data/PacketRecordingService.java b/src/main/java/com/projectswg/holocore/services/support/data/PacketRecordingService.java index a7a9bfa1..ff26a945 100644 --- a/src/main/java/com/projectswg/holocore/services/support/data/PacketRecordingService.java +++ b/src/main/java/com/projectswg/holocore/services/support/data/PacketRecordingService.java @@ -36,7 +36,7 @@ public class PacketRecordingService extends Service { } private boolean isPacketDebug() { - return PswgDatabase.config().getBoolean(this, "packetLogging", false); + return PswgDatabase.INSTANCE.getConfig().getBoolean(this, "packetLogging", false); } } diff --git a/src/main/java/com/projectswg/holocore/services/support/data/ServerDataService.java b/src/main/java/com/projectswg/holocore/services/support/data/ServerDataService.java index 1f1987ae..e7a54267 100644 --- a/src/main/java/com/projectswg/holocore/services/support/data/ServerDataService.java +++ b/src/main/java/com/projectswg/holocore/services/support/data/ServerDataService.java @@ -12,8 +12,8 @@ public class ServerDataService extends Service { @Override public boolean initialize() { - if (PswgDatabase.config().getBoolean(this, "wipeObjects", false)) { - Log.d("Cleared %d objects", PswgDatabase.objects().clearObjects()); + if (PswgDatabase.INSTANCE.getConfig().getBoolean(this, "wipeObjects", false)) { + Log.d("Cleared %d objects", PswgDatabase.INSTANCE.getObjects().clearObjects()); } return super.initialize(); diff --git a/src/main/java/com/projectswg/holocore/services/support/data/dev/DeveloperService.java b/src/main/java/com/projectswg/holocore/services/support/data/dev/DeveloperService.java index 67e550b8..2f8f57d1 100644 --- a/src/main/java/com/projectswg/holocore/services/support/data/dev/DeveloperService.java +++ b/src/main/java/com/projectswg/holocore/services/support/data/dev/DeveloperService.java @@ -47,7 +47,7 @@ public class DeveloperService extends Service { public boolean start() { setupDeveloperArea(); - if (PswgDatabase.config().getBoolean(this, "characterBuilder", false)) + if (PswgDatabase.INSTANCE.getConfig().getBoolean(this, "characterBuilder", false)) setupCharacterBuilders(); return super.start(); diff --git a/src/main/java/com/projectswg/holocore/services/support/global/chat/ChatSpatialService.java b/src/main/java/com/projectswg/holocore/services/support/global/chat/ChatSpatialService.java index 587b75f2..c07c0daf 100644 --- a/src/main/java/com/projectswg/holocore/services/support/global/chat/ChatSpatialService.java +++ b/src/main/java/com/projectswg/holocore/services/support/global/chat/ChatSpatialService.java @@ -14,7 +14,7 @@ public class ChatSpatialService extends Service { private final int chatRange; public ChatSpatialService() { - this.chatRange = PswgDatabase.config().getInt(this, "spatialChatRange", 128); + this.chatRange = PswgDatabase.INSTANCE.getConfig().getInt(this, "spatialChatRange", 128); } @IntentHandler diff --git a/src/main/java/com/projectswg/holocore/services/support/global/health/ServerHealthService.java b/src/main/java/com/projectswg/holocore/services/support/global/health/ServerHealthService.java index 718ae31b..b8da9173 100644 --- a/src/main/java/com/projectswg/holocore/services/support/global/health/ServerHealthService.java +++ b/src/main/java/com/projectswg/holocore/services/support/global/health/ServerHealthService.java @@ -32,7 +32,7 @@ public class ServerHealthService extends Service { this.previousGcTime = new AtomicLong(0); this.completedInitialIntents = new AtomicBoolean(true); - if (PswgDatabase.config().getBoolean(this, "performanceLog", false)) { + if (PswgDatabase.INSTANCE.getConfig().getBoolean(this, "performanceLog", false)) { this.performanceOutput = new BasicLogStream(new File("log/performance.txt")); executor.start(); performanceOutput.log("%s\t%s\t%s\t%s\t%s\t%s", "cpu", "memory-used", "memory-max", "gc-collectionRate", "gc-time", "intents"); diff --git a/src/main/java/com/projectswg/holocore/services/support/global/network/NetworkClientService.java b/src/main/java/com/projectswg/holocore/services/support/global/network/NetworkClientService.java index 7f627ee4..2b1e1079 100644 --- a/src/main/java/com/projectswg/holocore/services/support/global/network/NetworkClientService.java +++ b/src/main/java/com/projectswg/holocore/services/support/global/network/NetworkClientService.java @@ -32,118 +32,82 @@ import com.projectswg.holocore.ProjectSWG; import com.projectswg.holocore.ProjectSWG.CoreException; import com.projectswg.holocore.intents.support.global.network.CloseConnectionIntent; import com.projectswg.holocore.intents.support.global.network.ConnectionClosedIntent; +import com.projectswg.holocore.resources.support.data.server_info.StandardLog; import com.projectswg.holocore.resources.support.data.server_info.mongodb.PswgDatabase; -import com.projectswg.holocore.resources.support.global.network.AdminNetworkClient; import com.projectswg.holocore.resources.support.global.network.NetworkClient; import com.projectswg.holocore.resources.support.global.network.UDPServer; import com.projectswg.holocore.resources.support.global.network.UDPServer.UDPPacket; -import me.joshlarson.jlcommon.concurrency.ScheduledThreadPool; -import me.joshlarson.jlcommon.concurrency.ThreadPool; +import com.projectswg.holocore.resources.support.global.player.Player; +import me.joshlarson.jlcommon.concurrency.BasicThread; import me.joshlarson.jlcommon.control.IntentHandler; import me.joshlarson.jlcommon.control.Service; import me.joshlarson.jlcommon.log.Log; -import me.joshlarson.jlcommon.network.TCPServer; -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManagerFactory; -import java.io.File; -import java.io.FileInputStream; import java.io.IOException; -import java.io.InputStream; -import java.net.BindException; import java.net.InetAddress; import java.net.InetSocketAddress; -import java.net.SocketException; +import java.nio.ByteBuffer; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; -import java.security.KeyStore; -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; public class NetworkClientService extends Service { - private final ThreadPool securityExecutor; - private final TCPServer tcpServer; - private final TCPServer adminServer; - private final ScheduledThreadPool networkFlushPeriodic; - private final List clients; + private static final int INBOUND_BUFFER_SIZE = 4096; + + private final ServerSocketChannel tcpServer; + private final BasicThread acceptThreadPool; + private final Map clients; + private final ByteBuffer inboundBuffer; private final UDPServer udpServer; - private final SSLContext sslContext; - private final int flushRate; + private volatile boolean operational; public NetworkClientService() { - this.flushRate = getFlushRate(); - this.securityExecutor = new ThreadPool(3, "network-client-security-%d"); - this.sslContext = initializeSecurity(); - this.networkFlushPeriodic = new ScheduledThreadPool(1, "network-client-flush"); - this.clients = new CopyOnWriteArrayList<>(); + this.acceptThreadPool = new BasicThread("network-client-accept", this::acceptLoop); + this.clients = new ConcurrentHashMap<>(); + this.inboundBuffer = ByteBuffer.allocate(INBOUND_BUFFER_SIZE); + this.operational = true; { int bindPort = getBindPort(); - int bufferSize = getBufferSize(); - tcpServer = TCPServer.builder() - .setAddr(new InetSocketAddress((InetAddress) null, bindPort)) - .setBufferSize(bufferSize) - .setSessionCreator(this::createStandardClient) - .createTCPServer(); try { - udpServer = new UDPServer(bindPort, bufferSize); - } catch (SocketException e) { - throw new CoreException("Socket Exception on UDP bind: " + e); + tcpServer = ServerSocketChannel.open(); + tcpServer.bind(new InetSocketAddress(bindPort), 64); + tcpServer.configureBlocking(false); + udpServer = new UDPServer(bindPort, 32); + } catch (IOException e) { + throw new CoreException("Failed to start networking", e); } udpServer.setCallback(this::onUdpPacket); } - { - int adminServerPort = ProjectSWG.getGalaxy().getAdminServerPort(); - if (adminServerPort > 0) { - InetSocketAddress localhost = new InetSocketAddress(InetAddress.getLoopbackAddress(), adminServerPort); - adminServer = TCPServer.builder() - .setAddr(localhost) - .setBufferSize(1024) - .setSessionCreator(this::createAdminClient) - .createTCPServer(); - } else { - adminServer = null; - } - } } @Override public boolean start() { - securityExecutor.start(); - networkFlushPeriodic.start(); - networkFlushPeriodic.executeWithFixedRate(0, 1000 / flushRate, this::flush); - int bindPort = -1; - try { - bindPort = getBindPort(); - tcpServer.bind(); - bindPort = ProjectSWG.getGalaxy().getAdminServerPort(); - if (adminServer != null) { - adminServer.bind(); - } - } catch (BindException e) { - Log.e("Failed to bind to %d", bindPort); - return false; - } catch (IOException e) { - Log.e(e); - return false; - } - return super.start(); + acceptThreadPool.start(); + return true; + } + + @Override + public boolean isOperational() { + return operational; } @Override public boolean stop() { - for (NetworkClient client : tcpServer.getSessions()) + for (NetworkClient client : clients.values()) client.close(ConnectionStoppedReason.APPLICATION); - tcpServer.close(); - if (adminServer != null) { - for (NetworkClient client : adminServer.getSessions()) - client.close(ConnectionStoppedReason.APPLICATION); - adminServer.close(); + try { + tcpServer.close(); + } catch (IOException e) { + Log.w("Failed to close TCP server"); } - networkFlushPeriodic.stop(); - securityExecutor.stop(false); - return securityExecutor.awaitTermination(3000) && networkFlushPeriodic.awaitTermination(1000); + acceptThreadPool.stop(true); + return acceptThreadPool.awaitTermination(1000); } @Override @@ -152,15 +116,64 @@ public class NetworkClientService extends Service { return super.terminate(); } - /** - * Requests a flush for each network client - */ - private void flush() { - clients.forEach(NetworkClient::flush); + private void acceptLoop() { + try (Selector selector = Selector.open()) { + tcpServer.register(selector, SelectionKey.OP_ACCEPT); + while (tcpServer.isOpen()) { + selector.select(); + Iterator it = selector.selectedKeys().iterator(); + while (it.hasNext()) { + SelectionKey key = it.next(); + if (key.isAcceptable()) { + acceptConnection(selector); + } + if (key.isReadable()) { + read(key); + } + it.remove(); + } + } + } catch (IOException e) { + Log.a(e); + } finally { + operational = false; + } + } + + private void acceptConnection(Selector selector) { + try { + SocketChannel client = tcpServer.accept(); + if (client != null) { + client.configureBlocking(false); + NetworkClient networkClient = new NetworkClient(client); + client.register(selector, SelectionKey.OP_READ, networkClient); + clients.put(networkClient.getId(), networkClient); + } + } catch (Throwable t) { + Log.w("%s: Failed to accept connection", getClass().getSimpleName()); + } + } + + private void read(SelectionKey key) { + Player player = null; + try { + SocketChannel channel = (SocketChannel) key.channel(); + NetworkClient client = (NetworkClient) key.attachment(); + player = client.getPlayer(); + inboundBuffer.clear(); + channel.read(inboundBuffer); + inboundBuffer.flip(); + client.addToInbound(inboundBuffer); + } catch (Throwable t) { + if (player != null) + StandardLog.onPlayerError(this, player, "failed to read data"); + else + Log.w("%s: Failed to read data", getClass().getSimpleName()); + } } private void disconnect(long networkId) { - disconnect(getClient(networkId)); + disconnect(clients.get(networkId)); } private void disconnect(NetworkClient client) { @@ -173,9 +186,8 @@ public class NetworkClientService extends Service { private void onUdpPacket(UDPPacket packet) { if (packet.getLength() <= 0) return; - switch (packet.getData()[0]) { - case 1: sendState(packet.getAddress(), packet.getPort()); break; - default: break; + if (packet.getData()[0] == 1) { + sendState(packet.getAddress(), packet.getPort()); } } @@ -187,14 +199,6 @@ public class NetworkClientService extends Service { udpServer.send(port, addr, data.array()); } - private NetworkClient createStandardClient(SocketChannel channel) { - return new NetworkClient(channel, sslContext, securityExecutor, clients); - } - - private AdminNetworkClient createAdminClient(SocketChannel channel) { - return new AdminNetworkClient(channel, sslContext, securityExecutor, clients); - } - @IntentHandler private void handleCloseConnectionIntent(CloseConnectionIntent ccii) { disconnect(ccii.getPlayer().getNetworkId()); @@ -205,56 +209,8 @@ public class NetworkClientService extends Service { disconnect(cci.getPlayer().getNetworkId()); } - private NetworkClient getClient(long id) { - NetworkClient client = tcpServer.getSession(id); - if (client != null || adminServer == null) - return client; - return adminServer.getSession(id); - } - - private SSLContext initializeSecurity() { - Log.t("Initializing encryption..."); - try { - File keystoreFile = new File(PswgDatabase.config().getString(this, "keystoreFile", "")); - InputStream keystoreStream; - char[] passphrase; - KeyStore keystore; - if (!keystoreFile.isFile()) { - Log.w("Failed to enable security! Keystore file does not exist: %s", keystoreFile); - keystoreStream = getClass().getResourceAsStream("/security/Holocore.p12"); - passphrase = new char[]{'p', 'a', 's', 's'}; - keystore = KeyStore.getInstance("PKCS12"); - } else { - keystoreStream = new FileInputStream(keystoreFile); - passphrase = PswgDatabase.config().getString(this, "keystorePass", "").toCharArray(); - keystore = KeyStore.getInstance(PswgDatabase.config().getString(this, "keystoreType", "PKCS12")); - } - - keystore.load(keystoreStream, passphrase); - KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - keyManagerFactory.init(keystore, passphrase); - TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - trustManagerFactory.init(keystore); - SSLContext ctx = SSLContext.getInstance("TLSv1.3"); - ctx.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null); - Log.i("Enabled TLS encryption"); - return ctx; - } catch (Exception e) { - Log.a("Failed to enable security! %s: %s", e.getClass(), e.getMessage()); - throw new RuntimeException("Failed to enable TLS", e); - } - } - private int getBindPort() { - return PswgDatabase.config().getInt(this, "bindPort", 44463); - } - - private int getBufferSize() { - return PswgDatabase.config().getInt(this, "bufferSize", 4096); - } - - private int getFlushRate() { - return PswgDatabase.config().getInt(this, "flushRate", 10); + return PswgDatabase.INSTANCE.getConfig().getInt(this, "bindPort", 44463); } } diff --git a/src/main/java/com/projectswg/holocore/services/support/global/zone/LoginService.java b/src/main/java/com/projectswg/holocore/services/support/global/zone/LoginService.java index a984fe7f..c0679b97 100644 --- a/src/main/java/com/projectswg/holocore/services/support/global/zone/LoginService.java +++ b/src/main/java/com/projectswg/holocore/services/support/global/zone/LoginService.java @@ -49,7 +49,7 @@ import com.projectswg.holocore.intents.support.objects.swg.DestroyObjectIntent; import com.projectswg.holocore.intents.support.objects.swg.ObjectCreatedIntent; import com.projectswg.holocore.resources.support.data.server_info.StandardLog; import com.projectswg.holocore.resources.support.data.server_info.mongodb.PswgDatabase; -import com.projectswg.holocore.resources.support.data.server_info.mongodb.PswgUserDatabase.UserMetadata; +import com.projectswg.holocore.resources.support.data.server_info.mongodb.UserMetadata; import com.projectswg.holocore.resources.support.global.network.DisconnectReason; import com.projectswg.holocore.resources.support.global.player.AccessLevel; import com.projectswg.holocore.resources.support.global.player.Player; @@ -94,7 +94,7 @@ public class LoginService extends Service { @IntentHandler private void handleDeleteCharacterIntent(DeleteCharacterIntent dci) { SWGObject obj = dci.getCreature(); - if (PswgDatabase.objects().removeObject(obj.getObjectId())) { + if (PswgDatabase.INSTANCE.getObjects().removeObject(obj.getObjectId())) { DestroyObjectIntent.broadcast(obj); Player owner = obj.getOwner(); if (owner != null) @@ -119,8 +119,8 @@ public class LoginService extends Service { } private String getServerString() { - String name = PswgDatabase.config().getString(this, "loginServerName", "LoginServer"); - int id = PswgDatabase.config().getInt(this, "loginServerId", 1); + String name = PswgDatabase.INSTANCE.getConfig().getString(this, "loginServerName", "LoginServer"); + int id = PswgDatabase.INSTANCE.getConfig().getInt(this, "loginServerId", 1); return name + ':' + id; } @@ -134,7 +134,7 @@ public class LoginService extends Service { player.setPlayerState(PlayerState.LOGGING_IN); player.setPlayerServer(PlayerServer.LOGIN); - UserMetadata user = PswgDatabase.users().getUser(loginRequest.getUsername()); + UserMetadata user = PswgDatabase.INSTANCE.getUsers().getUser(loginRequest.getUsername()); player.setUsername(loginRequest.getUsername()); if (user == null) { StandardLog.onPlayerEvent(this, player, "failed to login [incorrect username] from %s", loginRequest.getSocketAddress()); @@ -163,7 +163,7 @@ public class LoginService extends Service { SWGObject obj = ObjectLookup.getObjectById(request.getPlayerId()); boolean success; if (obj instanceof CreatureObject) { - success = PswgDatabase.objects().removeObject(obj.getObjectId()); + success = PswgDatabase.INSTANCE.getObjects().removeObject(obj.getObjectId()); players.getOrDefault(player.getAccountId(), new ArrayList<>()).remove(obj); } else { success = false; @@ -192,14 +192,14 @@ public class LoginService extends Service { assert player.getPlayerServer() == PlayerServer.NONE; player.setPlayerState(PlayerState.LOGGING_IN); player.setPlayerServer(PlayerServer.LOGIN); - final boolean doClientCheck = PswgDatabase.config().getBoolean(this, "loginVersionChecks", true); + final boolean doClientCheck = PswgDatabase.INSTANCE.getConfig().getBoolean(this, "loginVersionChecks", true); if (!id.getVersion().equals(REQUIRED_VERSION) && doClientCheck) { StandardLog.onPlayerEvent(this, player, "failed to login [incorrect version: %s] from %s", id.getVersion(), id.getSocketAddress()); onLoginClientVersionError(player, id); return; } - UserMetadata user = PswgDatabase.users().getUser(id.getUsername()); + UserMetadata user = PswgDatabase.INSTANCE.getUsers().getUser(id.getUsername()); player.setUsername(id.getUsername()); if (user == null) { StandardLog.onPlayerEvent(this, player, "failed to login [incorrect username] from %s", id.getSocketAddress()); @@ -261,7 +261,7 @@ public class LoginService extends Service { cluster.addGalaxy(g); clusterStatus.addGalaxy(g); } - cluster.setMaxCharacters(PswgDatabase.config().getInt(this, "galaxyMaxCharacters", 2)); + cluster.setMaxCharacters(PswgDatabase.INSTANCE.getConfig().getInt(this, "galaxyMaxCharacters", 2)); player.sendPacket(new ServerNowEpochTime((int)(System.currentTimeMillis()/1E3))); player.sendPacket(token); player.sendPacket(cluster); diff --git a/src/main/java/com/projectswg/holocore/services/support/global/zone/ZoneService.java b/src/main/java/com/projectswg/holocore/services/support/global/zone/ZoneService.java index 2cf6d285..51c202d4 100644 --- a/src/main/java/com/projectswg/holocore/services/support/global/zone/ZoneService.java +++ b/src/main/java/com/projectswg/holocore/services/support/global/zone/ZoneService.java @@ -79,7 +79,7 @@ public class ZoneService extends Service { } private void sendMessageOfTheDay(Player player) { - String message = PswgDatabase.config().getString(this, "firstZoneMessage", ""); + String message = PswgDatabase.INSTANCE.getConfig().getString(this, "firstZoneMessage", ""); if(!message.isEmpty()) // If the message isn't nothing new SystemMessageIntent(player, message).broadcast(); // Send it diff --git a/src/main/java/com/projectswg/holocore/services/support/global/zone/creation/CharacterCreationService.java b/src/main/java/com/projectswg/holocore/services/support/global/zone/creation/CharacterCreationService.java index 55fa44e1..39101500 100644 --- a/src/main/java/com/projectswg/holocore/services/support/global/zone/creation/CharacterCreationService.java +++ b/src/main/java/com/projectswg/holocore/services/support/global/zone/creation/CharacterCreationService.java @@ -73,7 +73,7 @@ public class CharacterCreationService extends Service { @Override public boolean start() { - creationRestriction.setCreationsPerPeriod(PswgDatabase.config().getInt(this, "galaxyMaxCharactersPerPeriod", 2)); + creationRestriction.setCreationsPerPeriod(PswgDatabase.INSTANCE.getConfig().getInt(this, "galaxyMaxCharactersPerPeriod", 2)); return super.start(); } @@ -94,7 +94,7 @@ public class CharacterCreationService extends Service { int spaceIndex = name.indexOf(' '); if (spaceIndex != -1) name = name.substring(0, spaceIndex); - return PswgDatabase.objects().isCharacter(name); + return PswgDatabase.INSTANCE.getObjects().isCharacter(name); } private void handleRandomNameRequest(Player player, RandomNameRequest request) { @@ -110,7 +110,7 @@ public class CharacterCreationService extends Service { private void handleApproveNameRequest(Player player, ClientVerifyAndLockNameRequest request) { String name = request.getName(); ErrorMessage err = getNameValidity(name, player.getAccessLevel() != AccessLevel.PLAYER); - int max = PswgDatabase.config().getInt(this, "galaxyMaxCharacters", 0); + int max = PswgDatabase.INSTANCE.getConfig().getInt(this, "galaxyMaxCharacters", 0); if (max != 0 && getCharacterCount(player.getAccountId()) >= max) err = ErrorMessage.SERVER_CHARACTER_CREATION_MAX_CHARS; if (err == ErrorMessage.NAME_APPROVED_MODIFIED) @@ -143,7 +143,7 @@ public class CharacterCreationService extends Service { return null; } // Too many characters - int max = PswgDatabase.config().getInt(this, "galaxyMaxCharacters", 0); + int max = PswgDatabase.INSTANCE.getConfig().getInt(this, "galaxyMaxCharacters", 0); if (max != 0 && getCharacterCount(player.getAccountId()) >= max) { sendCharCreationFailure(player, create, ErrorMessage.SERVER_CHARACTER_CREATION_MAX_CHARS, "too many characters"); return null; @@ -191,7 +191,7 @@ public class CharacterCreationService extends Service { } private int getCharacterCount(String username) { - return PswgDatabase.objects().getCharacterCount(username); + return PswgDatabase.INSTANCE.getObjects().getCharacterCount(username); } private ErrorMessage getNameValidity(String name, boolean admin) { @@ -218,7 +218,7 @@ public class CharacterCreationService extends Service { } private CreatureObject createCharacter(Player player, ClientCreateCharacter create) { - String spawnLocation = PswgDatabase.config().getString(this, "primarySpawnLocation", "tat_moseisley"); + String spawnLocation = PswgDatabase.INSTANCE.getConfig().getString(this, "primarySpawnLocation", "tat_moseisley"); StandardLog.onPlayerTrace(this, player, "created player at spawn location %s", spawnLocation); ZoneInsertion info = DataLoader.zoneInsertions().getInsertion(spawnLocation); if (info == null) { diff --git a/src/main/java/com/projectswg/holocore/services/support/npc/spawn/SpawnerService.java b/src/main/java/com/projectswg/holocore/services/support/npc/spawn/SpawnerService.java index 8d36bcb4..7076f833 100644 --- a/src/main/java/com/projectswg/holocore/services/support/npc/spawn/SpawnerService.java +++ b/src/main/java/com/projectswg/holocore/services/support/npc/spawn/SpawnerService.java @@ -76,7 +76,7 @@ public final class SpawnerService extends Service { @Override public boolean initialize() { executor.start(); - if (PswgDatabase.config().getBoolean(this, "spawnEggsEnabled", true)) + if (PswgDatabase.INSTANCE.getConfig().getBoolean(this, "spawnEggsEnabled", true)) loadSpawners(); return true; diff --git a/src/main/java/com/projectswg/holocore/services/support/objects/ObjectStorageService.java b/src/main/java/com/projectswg/holocore/services/support/objects/ObjectStorageService.java index 3ad92015..7a4135ba 100644 --- a/src/main/java/com/projectswg/holocore/services/support/objects/ObjectStorageService.java +++ b/src/main/java/com/projectswg/holocore/services/support/objects/ObjectStorageService.java @@ -79,7 +79,7 @@ public class ObjectStorageService extends Service { private boolean initializeSavedObjects() { long startTime = StandardLog.onStartLoad("server objects"); - List objectDocuments = PswgDatabase.objects().getObjects(); + List objectDocuments = PswgDatabase.INSTANCE.getObjects().getObjects(); Map objects = new HashMap<>(); AtomicLong highestId = new AtomicLong(0); for (MongoData doc : objectDocuments) { @@ -135,7 +135,7 @@ public class ObjectStorageService extends Service { private void saveObjects() { List saveList = new ArrayList<>(); persistedObjects.forEach(obj -> saveChildren(saveList, obj)); - PswgDatabase.objects().addObjects(saveList); + PswgDatabase.INSTANCE.getObjects().addObjects(saveList); } private void saveChildren(Collection saveList, @Nullable SWGObject obj) { @@ -156,7 +156,7 @@ public class ObjectStorageService extends Service { if (persistedObjects.add(obj)) { List saveList = new ArrayList<>(); saveChildren(saveList, obj); - PswgDatabase.objects().addObjects(saveList); + PswgDatabase.INSTANCE.getObjects().addObjects(saveList); } } } @@ -196,13 +196,13 @@ public class ObjectStorageService extends Service { } if (object.isPersisted()) persistedObjects.remove(object); - PswgDatabase.objects().removeObject(object.getObjectId()); + PswgDatabase.INSTANCE.getObjects().removeObject(object.getObjectId()); objectMap.remove(object.getObjectId()); } private List createEventList() { List events = new ArrayList<>(); - for (String event : PswgDatabase.config().getString(this, "events", "").split(",")) { + for (String event : PswgDatabase.INSTANCE.getConfig().getString(this, "events", "").split(",")) { if (event.isEmpty()) continue; events.add(event.toLowerCase(Locale.US)); diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 13e96c13..dbf396e2 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -13,4 +13,5 @@ open module holocore { requires com.projectswg.common; requires fast.json; requires commons.cli; + requires kotlin.stdlib; }