diff --git a/.gitattributes b/.gitattributes index 04d1f488..9a7cf49b 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,5 +1,5 @@ *.reg -crlf *.bat -crlf -*.sh -crlf -*.bash -crlf -gradlew -crlf +*.sh -lf +*.bash -lf +gradlew -lf diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 8c4bda48..f6855c57 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -23,8 +23,12 @@ jobs: distribution: 'temurin' - name: Grant execute permission for gradlew run: chmod +x gradlew + - name: Create dependency folder + run: mkdir deps + - name: Download connective + run: git clone https://github.com/SkySwimmer/connective-http deps/connective-http - name: Build with Gradle - uses: gradle/gradle-build-action@v2.4.2 + uses: gradle/gradle-build-action@v2 with: arguments: build - name: Publish build artifact diff --git a/.gitignore b/.gitignore index 66b67be3..d1b5fafa 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,9 @@ build/ .gradle +# Dependencies +deps/ + # Eclipse bin/ .settings/ @@ -31,4 +34,4 @@ update.list updater.jar # macOS -**/.DS_Store \ No newline at end of file +**/.DS_Store diff --git a/Centuria.launch b/Centuria.launch index 7f7af9a6..b590a015 100644 --- a/Centuria.launch +++ b/Centuria.launch @@ -1,5 +1,6 @@ + diff --git a/README.md b/README.md index a61931aa..432c72ec 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,12 @@ To build Centuria you will need to have Java 17 JDK installed. Centuria is build ## Building on Windows On windows, run the following commands in cmd or powershell:: +Download dependencies: +```powershell +mkdir deps +git clone https://github.com/SkySwimmer/connective-http deps/connective-http +``` + Set up a development environment (optional): ```powershell .\gradlew eclipse @@ -27,6 +33,12 @@ Configure permissions: chmod +x gradlew ``` +Download dependencies: +```bash +mkdir deps +git clone https://github.com/SkySwimmer/connective-http deps/connective-http +``` + Set up a development environment (optional): ```bash ./gradlew eclipse createEclipseLaunches @@ -43,7 +55,7 @@ After building, you can find the compiled server in `build/Installations`, you c
## Note about self-hosted servers -Due to the lack of a easy-to-use launcher, hosting a server yourself may prove difficult. You will need to edit your client's `Fer.al_Data/sharedassets1.assets` and swap out the server endpoints, however this can be tricky, you may run into string length issues. You can obtain a localhost sharedassets patch from the [AerialWorks Centuria Server](https://aerialworks.ddns.net/emuferal/sharedassets1.assets). +Due to the lack of a easy-to-use launcher, hosting a server yourself may prove difficult. You will need to edit your client's `Fer.al_Data/sharedassets1.assets` and swap out the server endpoints, however this can be tricky, you may run into string length issues. (a guide will be made in the near-future)
diff --git a/Requirements.txt b/Requirements.txt new file mode 100644 index 00000000..c5299d8c --- /dev/null +++ b/Requirements.txt @@ -0,0 +1,7 @@ +Centuria requires the following projects: + - connective-http (2023 rewrite and above): https://github.com/SkySwimmer/connective-http + +Commands to set up dependencies: + - Connective: + mkdir deps + git clone https://github.com/SkySwimmer/connective-http deps/connective-http diff --git a/build.gradle b/build.gradle index c4638bfa..99b2d499 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ plugins { id 'maven-publish' } -version = "1.6.4.B3" +version = "b1.7" group = "org.asf.centuria" sourceCompatibility = '1.17' @@ -17,7 +17,7 @@ apply plugin: 'idea' repositories { mavenCentral() flatDir { - dirs 'libraries', 'fluid' + dirs 'fluid' } } @@ -60,8 +60,7 @@ artifacts { } dependencies { - implementation name: "ConnectiveHTTP" - implementation name: "RatsMemory" + implementation project(":deps:connective-http") implementation group: 'org.asf.cyan', name: 'Fluid', version: '1.0.0.A33' @@ -71,8 +70,9 @@ dependencies { implementation 'org.ow2.asm:asm:9.1' implementation 'org.ow2.asm:asm-tree:9.1' implementation 'org.ow2.asm:asm-commons:9.1' + implementation group: 'com.google.code.gson', name: 'gson', version: '2.9.0' - implementation group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-xml', version: '2.13.3' + implementation group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-xml', version: '2.15.2' implementation group: 'javax.activation', name: 'activation', version: '1.1.1' diff --git a/buildupdate.sh b/buildupdate.sh new file mode 100755 index 00000000..aa2a2978 --- /dev/null +++ b/buildupdate.sh @@ -0,0 +1,78 @@ +#!/bin/bash + +function copyUpdateData() { + version="$1" + channel="$2" + + # Create directory + mkdir -p "build/updatedata/emuferal.ddns.net/httpdocs/$channel" || exit 1 + + # Copy files + function copyData() { + for file in $1/* ; do + pref=$2 + targetf="${file:${#pref}}" + if [ -f "$file" ]; then + dir="$(dirname "build/updatedata/emuferal.ddns.net/httpdocs/$channel/${version}/${targetf}")" + if [ ! -d "$dir" ]; then + mkdir -p "$dir" || exit 1 + fi + cp -rfv "$file" "build/updatedata/emuferal.ddns.net/httpdocs/$channel/${version}/${targetf}" + fi + if [ "$?" != "0" ]; then + echo Build failure! + exit 1 + fi + if [ -d "$file" ]; then + copyData "$file" "$2" + fi + done + } + + copyData build/update build/update/ + echo -n "$version" > "build/updatedata/emuferal.ddns.net/httpdocs/$channel/update.info" + + source version.info +} + +# Current channel +echo Preparing... +source version.info +rm -rf build/updatedata +rm -rf build/update +echo +echo +echo Centuria Update Builder +echo Version: $version +echo Version type: $channel +echo +echo +read -p "Are you sure you want to build this version's update files? [Y/n] " prompt + +if [ "$prompt" != "y" ] && [ "$prompt" != "Y" ]; then + exit +fi + +echo Building centuria... +./gradlew build updateData || exit $? +echo + +echo Copying data... +copyUpdateData "$version" "$channel" + +# Other channels +if [ "$channel" == "beta" ]; then + copyUpdateData "$version" alpha +fi +if [ "$channel" == "prerelease" ]; then + copyUpdateData "$version" alpha + copyUpdateData "$version" beta +fi +if [ "$channel" == "release" ]; then + copyUpdateData "$version" alpha + copyUpdateData "$version" beta + copyUpdateData "$version" prerelease +fi + +echo +echo Build completed. diff --git a/changelogs/b1.7 b/changelogs/b1.7 new file mode 100644 index 00000000..f7055709 --- /dev/null +++ b/changelogs/b1.7 @@ -0,0 +1,48 @@ +New features in 1.7: +- Implemented Dizzywing Dispatch partially (credits to animal animalson) +- Implemented Kino minigames (credits to animal animalson, unclear if its fully in place, needs testing) +- Saves can now have different colors, you will be able to tell if someone is in experience mode or creative by the name colors +- Staff now have different name colors and a tag before their name showing their rank + +Fixes in 1.7: +- Fixed account unpair issues with account deletion +- Fixed loot spawning locking up sometimes which caused loot not to respawn every now and then +- Fixed True Friendship inspiration and some others not being given to the player +- Fix dms and gcs having ghost entries if accounts get deleted (chat has been fixed) +- Fixed levels not being refreshed on logout and save switch +- Fixed server not fully teleporting players +- Fixed invalid unread counts +- Fixed player name changes not synchronizing immediately +- Corrected time bonus of Do or Dye level 104 +- Patched account registration + +Changes in 1.7: +- FTL (modloader) has been overhauled +- Made it that modules can override existing network packets +- Updated Connective libraries, this will help with stability +- Updated the FeralTweaks server module data processor so that it doesnt require authorization for chart patch downloads +- Made it that maintenance mode doesnt automatically shut the server down when everyone is offline +- Maintenance mode no longer allows staff login apart from admins +- FeralTweaks server code has been overhaulled to be more clean and easier to navigate through + +Command changes: +- The 'toggleghostmode' command no longer requires admin clearance +- New staff command: startmaintenancetimer: command to schedule server maintenance (minutes) +- New staff command: schedulemaintenance: command to schedule server maintenance (date/time) +- New staff command: cancelmaintenance: cancels scheduled server maintenance +- new staff command: stopserver: stops the server (uses a update shutdown message without feraltweaks, but uses a custom message with feraltweaks) +- New player-usable command: stafflist: lists all staff users on the server (with online status) + +Modloader changes: +- Removed patches that caused the game to lag +- Overhauled inner workings of feraltweaks making it much more stable +- Implemented asset injection and chart injection for mods +- Implemented mod packages and packaging system +- Implemented a mod networking framework +- Fixed issues preventing multiple of the same client from starting +- Added support for command line arguments +- Logging overhaul + +Launcher fixes: +- Fixed issues where on some systems the modloader could freeze +- Fixed issues with the start scripts, making them more compatible with installers diff --git a/examplemodule/build.gradle b/examplemodule/build.gradle index c1fa66e6..a8236cc2 100644 --- a/examplemodule/build.gradle +++ b/examplemodule/build.gradle @@ -34,7 +34,6 @@ ext.buildyear = cal.get(Calendar.YEAR).toString(); repositories { mavenCentral() - maven { name = "AerialWorks"; url = "https://aerialworks.ddns.net/maven" } flatDir { dirs 'libraries' } @@ -59,7 +58,7 @@ dependencies { testImplementation group: 'junit', name: 'junit', version: '4.13.2' implementation name: "Centuria" - implementation name: "ConnectiveHTTP" + implementation name: "connective-http" } createEclipseLaunches { diff --git a/examplemodule/createlocalserver.bat b/examplemodule/createlocalserver.bat index 1a6fc24c..5e51d38d 100644 --- a/examplemodule/createlocalserver.bat +++ b/examplemodule/createlocalserver.bat @@ -16,13 +16,13 @@ echo Building... goto execute :execute +if NOT EXIST deps mkdir deps +git clone https://github.com/SkySwimmer/connective-http deps/connective-http cmd /c java -cp gradle/wrapper/gradle-wrapper.jar org.gradle.wrapper.GradleWrapperMain installation if NOT EXIST "%dir%\server" mkdir "%dir%\server" robocopy /E /NFL /NDL /NJH /NJS /nc /ns /np build/Installations "%dir%\server" -for /R libraries %%f in (*-javadoc.jar) do copy /Y %%f "%dir%\server\libs" >NUL -for /R libraries %%f in (*-sources.jar) do copy /Y %%f "%dir%\server\libs" >NUL if NOT EXIST "%dir%\libraries" mkdir "%dir%\libraries" copy /Y build\Installations\Centuria.jar "%dir%\libraries" >NUL diff --git a/examplemodule/createlocalserver.sh b/examplemodule/createlocalserver.sh index 74aaffc0..e5e4b46c 100644 --- a/examplemodule/createlocalserver.sh +++ b/examplemodule/createlocalserver.sh @@ -24,13 +24,13 @@ function exitmeth() { function execute() { chmod +x gradlew + mkdir deps + git clone https://github.com/SkySwimmer/connective-http deps/connective-http ./gradlew installation || return $? if [ ! -d "$dir/server" ]; then mkdir "$dir/server" fi cp -rf "build/Installations/." "$dir/server" - cp -rf "libraries/"*-javadoc.jar "$dir/server/libs" - cp -rf "libraries/"*-sources.jar "$dir/server/libs" cp "build/Installations/Centuria.jar" "$dir/server/libs" if [ ! -d "$dir/libraries" ]; then diff --git a/libraries/ConnectiveHTTP-javadoc.jar b/libraries/ConnectiveHTTP-javadoc.jar deleted file mode 100644 index 8fa99e42..00000000 Binary files a/libraries/ConnectiveHTTP-javadoc.jar and /dev/null differ diff --git a/libraries/ConnectiveHTTP-sources.jar b/libraries/ConnectiveHTTP-sources.jar deleted file mode 100644 index 27835f9c..00000000 Binary files a/libraries/ConnectiveHTTP-sources.jar and /dev/null differ diff --git a/libraries/ConnectiveHTTP.jar b/libraries/ConnectiveHTTP.jar deleted file mode 100644 index 99a23d51..00000000 Binary files a/libraries/ConnectiveHTTP.jar and /dev/null differ diff --git a/libraries/RatsMemory.jar b/libraries/RatsMemory.jar deleted file mode 100644 index 1efb8d7c..00000000 Binary files a/libraries/RatsMemory.jar and /dev/null differ diff --git a/libraries/log4j-api.jar b/libraries/log4j-api.jar deleted file mode 100644 index 16d9061d..00000000 Binary files a/libraries/log4j-api.jar and /dev/null differ diff --git a/libraries/log4j-core.jar b/libraries/log4j-core.jar deleted file mode 100644 index 0fd00514..00000000 Binary files a/libraries/log4j-core.jar and /dev/null differ diff --git a/settings.gradle b/settings.gradle index 1911cd1b..cc628ab0 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,2 @@ rootProject.name = 'Centuria' +include("deps:connective-http") diff --git a/src/main/java/org/asf/centuria/Centuria.java b/src/main/java/org/asf/centuria/Centuria.java index 80e10f91..fb83b5f4 100644 --- a/src/main/java/org/asf/centuria/Centuria.java +++ b/src/main/java/org/asf/centuria/Centuria.java @@ -37,7 +37,7 @@ import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; -import org.asf.connective.https.ConnectiveHTTPSServer; +import org.asf.connective.ConnectiveHttpServer; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.asf.centuria.entities.components.ComponentManager; @@ -78,16 +78,14 @@ import org.asf.centuria.networking.http.api.custom.UserDetailsHandler; import org.asf.centuria.networking.http.director.GameServerRequestHandler; import org.asf.centuria.seasonpasses.SeasonPassManager; -import org.asf.rats.ConnectiveHTTPServer; -import org.asf.rats.ConnectiveServerFactory; import com.google.gson.Gson; import com.google.gson.JsonObject; public class Centuria { // Update - public static String SERVER_UPDATE_VERSION = "1.6.4.B3"; - public static String DOWNLOAD_BASE_URL = "https://aerialworks.ddns.net/extra/centuria"; + public static String SERVER_UPDATE_VERSION = "b1.7"; + public static String DOWNLOAD_BASE_URL = "https://emuferal.ddns.net"; // Configuration public static Logger logger; @@ -108,8 +106,8 @@ public class Centuria { public static String spawnBehaviour; // Servers - private static ConnectiveHTTPServer apiServer; - public static ConnectiveHTTPServer directorServer; + private static ConnectiveHttpServer apiServer; + public static ConnectiveHttpServer directorServer; public static GameServer gameServer; public static ChatServer chatServer; @@ -143,7 +141,7 @@ public static void main(String[] args) System.out.println(" Centuria "); System.out.println(" Fer.al Server Emulator "); System.out.println(" "); - System.out.println(" Version 1.6.4.B3 "); // not doing this + System.out.println(" Version b1.7 "); // not doing this // dynamically as // centering is a // pain @@ -235,8 +233,8 @@ public static void main(String[] args) startServer(); // Wait for exit - directorServer.waitExit(); - apiServer.waitExit(); + directorServer.waitForExit(); + apiServer.waitForExit(); chatServer.getServerSocket().close(); gameServer.getServerSocket().close(); } @@ -287,6 +285,7 @@ public static boolean runUpdater(int mins) { message = "%xt%ua%-1%7390|1%"; break; case 0: + // Shut down updateShutdown(); cancelUpdate = false; return; @@ -331,6 +330,20 @@ public static void updateShutdown() { EventBus.getInstance().dispatchEvent(new ServerUpdateEvent(nextVersion, -1)); } + // Shut down the server + disconnectPlayersForShutdown(); + + // Dispatch completion event + EventBus.getInstance().dispatchEvent(new ServerUpdateCompletionEvent(nextVersion)); + + // Exit + System.exit(0); + } + + /** + * Disconnects all players with a update message + */ + public static void disconnectPlayersForShutdown() { // Disconnect everyone for (Player plr : Centuria.gameServer.getPlayers()) { plr.client.sendPacket("%xt%ua%-1%__FORCE_RELOGIN__%"); @@ -359,7 +372,7 @@ public static void updateShutdown() { plr.client.disconnect(); } - // Wait for log off and exit + // Wait for log off int l = 0; while (Centuria.gameServer.getPlayers().length != 0) { l++; @@ -371,12 +384,6 @@ public static void updateShutdown() { } catch (InterruptedException e) { } } - - // Dispatch completion event - EventBus.getInstance().dispatchEvent(new ServerUpdateCompletionEvent(nextVersion)); - - // Exit - System.exit(0); } public static void startServer() @@ -469,6 +476,11 @@ public static void startServer() // Create save array JsonObject saves = new JsonObject(); + JsonObject colors = new JsonObject(); + colors.addProperty("developer", "#ff00e6"); + colors.addProperty("admin", "red"); + colors.addProperty("moderator", "orange"); + colors.addProperty("player", "default"); JsonObject save = new JsonObject(); save.addProperty("sanctuaryLimitOverride", -1); save.addProperty("giveAllAvatars", defaultGiveAllAvatars); @@ -479,6 +491,7 @@ public static void startServer() save.addProperty("giveAllSanctuaryTypes", defaultGiveAllSanctuaryTypes); save.addProperty("giveAllCurrency", defaultGiveAllCurrency); save.addProperty("giveAllResources", defaultGiveAllResources); + save.add("saveColors", colors); saves.add(defaultSaveName, save); // Set basics @@ -502,117 +515,100 @@ public static void startServer() // // Start API server - ConnectiveServerFactory factory; try { - factory = new ConnectiveServerFactory().setPort(Integer.parseInt(properties.get("api-port"))) - .setOption(ConnectiveServerFactory.OPTION_AUTOSTART) - .setOption(ConnectiveServerFactory.OPTION_ASSIGN_PORT); + // Create properties + HashMap props = new HashMap(); + props.put("address", "0.0.0.0"); + props.put("port", properties.get("api-port")); + // Check HTTPS if (properties.getOrDefault("encrypt-api", "false").equals("true") && new File("keystore.jks").exists() && new File("keystore.jks.password").exists()) { - factory = factory.setImplementation(ConnectiveHTTPSServer.class); + // Start HTTPS + props.put("keystore", "keystore.jks"); + props.put("keystore-password", Files.readString(Path.of("keystore.jks.password"))); + apiServer = ConnectiveHttpServer.createNetworked("HTTPS/1.1", props); + setupAPI(apiServer); + apiServer.start(); + } else { + // Start HTTP + apiServer = ConnectiveHttpServer.createNetworked("HTTP/1.1", props); + setupAPI(apiServer); + apiServer.start(); } - - apiServer = factory.build(); } catch (Exception e) { Centuria.logger.fatal("Unable to start on port " + Integer.parseInt(properties.get("api-port")) + "! Switching to debug mode!"); Centuria.logger.fatal("If you are not attempting to debug the server, please run as root."); - factory = new ConnectiveServerFactory().setPort(6970).setOption(ConnectiveServerFactory.OPTION_AUTOSTART) - .setOption(ConnectiveServerFactory.OPTION_ASSIGN_PORT); + HashMap props = new HashMap(); + props.put("address", "0.0.0.0"); + props.put("port", "6970"); if (properties.getOrDefault("encrypt-api", "false").equals("true") && new File("keystore.jks").exists() && new File("keystore.jks.password").exists()) { - factory = factory.setImplementation(ConnectiveHTTPSServer.class); + props.put("keystore", "keystore.jks"); + props.put("keystore-password", Files.readString(Path.of("keystore.jks.password"))); + apiServer = ConnectiveHttpServer.createNetworked("HTTPS/1.1", props); + setupAPI(apiServer); + apiServer.start(); + } else { + apiServer = ConnectiveHttpServer.createNetworked("HTTP/1.1", props); + setupAPI(apiServer); + apiServer.start(); } - - apiServer = factory.build(); } - // Allow modules to register handlers - EventBus.getInstance().dispatchEvent(new APIServerStartupEvent(apiServer)); - - // API processors - apiServer.registerProcessor(new UserHandler()); - apiServer.registerProcessor(new XPDetailsHandler()); - apiServer.registerProcessor(new AuthenticateHandler()); - apiServer.registerProcessor(new UpdateDisplayNameHandler()); - apiServer.registerProcessor(new DisplayNamesRequestHandler()); - apiServer.registerProcessor(new DisplayNameValidationHandler()); - apiServer.registerProcessor(new RequestTokenHandler()); - apiServer.registerProcessor(new GameRegistrationHandler()); - apiServer.registerProcessor(new SeasonPassRequestHandler()); - - // Custom API - apiServer.registerProcessor(new LoginRefreshHandler()); - apiServer.registerProcessor(new ChangePasswordHandler()); - apiServer.registerProcessor(new ChangeDisplayNameHandler()); - apiServer.registerProcessor(new ChangeLoginNameHandler()); - apiServer.registerProcessor(new DeleteAccountHandler()); - apiServer.registerProcessor(new UserDetailsHandler()); - apiServer.registerProcessor(new ListPlayersHandler()); - apiServer.registerProcessor(new RegistrationHandler()); - apiServer.registerProcessor(new PlayerDataDownloadHandler()); - apiServer.registerProcessor(new SaveManagerHandler()); - - // Fallback - apiServer.registerProcessor(new FallbackAPIProcessor()); - // // Debug API if (System.getProperty("debugAPI") != null) { Centuria.logger.info("Starting debug api..."); - factory = new ConnectiveServerFactory().setPort(Integer.parseInt(System.getProperty("debugAPI"))) - .setOption(ConnectiveServerFactory.OPTION_AUTOSTART) - .setOption(ConnectiveServerFactory.OPTION_ASSIGN_PORT); - var apiServer = factory.build(); - - // Allow modules to register handlers - EventBus.getInstance().dispatchEvent(new APIServerStartupEvent(apiServer)); - - // API processors - apiServer.registerProcessor(new UserHandler()); - apiServer.registerProcessor(new XPDetailsHandler()); - apiServer.registerProcessor(new AuthenticateHandler()); - apiServer.registerProcessor(new UpdateDisplayNameHandler()); - apiServer.registerProcessor(new DisplayNamesRequestHandler()); - apiServer.registerProcessor(new DisplayNameValidationHandler()); - apiServer.registerProcessor(new RequestTokenHandler()); - apiServer.registerProcessor(new GameRegistrationHandler()); - apiServer.registerProcessor(new SeasonPassRequestHandler()); - - // Custom API - apiServer.registerProcessor(new LoginRefreshHandler()); - apiServer.registerProcessor(new ChangePasswordHandler()); - apiServer.registerProcessor(new ChangeDisplayNameHandler()); - apiServer.registerProcessor(new ChangeLoginNameHandler()); - apiServer.registerProcessor(new DeleteAccountHandler()); - apiServer.registerProcessor(new UserDetailsHandler()); - apiServer.registerProcessor(new ListPlayersHandler()); - apiServer.registerProcessor(new RegistrationHandler()); - apiServer.registerProcessor(new PlayerDataDownloadHandler()); - apiServer.registerProcessor(new SaveManagerHandler()); - - // Fallback - apiServer.registerProcessor(new FallbackAPIProcessor()); + HashMap props = new HashMap(); + props.put("address", "0.0.0.0"); + props.put("port", System.getProperty("debugAPI")); + ConnectiveHttpServer apiServer = ConnectiveHttpServer.createNetworked("HTTP/1.1", props); + setupAPI(apiServer); + apiServer.start(); } // // Start director server Centuria.logger .info("Starting Director server on port " + Integer.parseInt(properties.get("director-port")) + "..."); - factory = new ConnectiveServerFactory().setPort(Integer.parseInt(properties.get("director-port"))) - .setOption(ConnectiveServerFactory.OPTION_AUTOSTART) - .setOption(ConnectiveServerFactory.OPTION_ASSIGN_PORT); + // Create properties + HashMap props = new HashMap(); + props.put("address", "0.0.0.0"); + props.put("port", properties.get("director-port")); + + // Check HTTPS if (properties.getOrDefault("encrypt-director", "false").equals("true") && new File("keystore.jks").exists() && new File("keystore.jks.password").exists()) { - factory = factory.setImplementation(ConnectiveHTTPSServer.class); - } - directorServer = factory.build(); - directorServer.registerProcessor(new GameServerRequestHandler()); - EventBus.getInstance().dispatchEvent(new DirectorServerStartupEvent(directorServer)); + // Start HTTPS + props.put("keystore", "keystore.jks"); + props.put("keystore-password", Files.readString(Path.of("keystore.jks.password"))); + directorServer = ConnectiveHttpServer.createNetworked("HTTPS/1.1", props); + + // Register handlers + directorServer.registerProcessor(new GameServerRequestHandler()); + + // Dispatch event + EventBus.getInstance().dispatchEvent(new DirectorServerStartupEvent(directorServer)); + + // Start + directorServer.start(); + } else { + // Start HTTP + directorServer = ConnectiveHttpServer.createNetworked("HTTP/1.1", props); + + // Register handlers + directorServer.registerProcessor(new GameServerRequestHandler()); + // Dispatch event + EventBus.getInstance().dispatchEvent(new DirectorServerStartupEvent(directorServer)); + + // Start + directorServer.start(); + } // // Load game server ServerSocket sock; @@ -708,6 +704,37 @@ && new File("keystore.jks.password").exists()) { Centuria.logger.info("Successfully started emulated servers."); } + private static void setupAPI(ConnectiveHttpServer apiServer) { + // Allow modules to register handlers + EventBus.getInstance().dispatchEvent(new APIServerStartupEvent(apiServer)); + + // API processors + apiServer.registerProcessor(new UserHandler()); + apiServer.registerProcessor(new XPDetailsHandler()); + apiServer.registerProcessor(new AuthenticateHandler()); + apiServer.registerProcessor(new UpdateDisplayNameHandler()); + apiServer.registerProcessor(new DisplayNamesRequestHandler()); + apiServer.registerProcessor(new DisplayNameValidationHandler()); + apiServer.registerProcessor(new RequestTokenHandler()); + apiServer.registerProcessor(new GameRegistrationHandler()); + apiServer.registerProcessor(new SeasonPassRequestHandler()); + + // Custom API + apiServer.registerProcessor(new LoginRefreshHandler()); + apiServer.registerProcessor(new ChangePasswordHandler()); + apiServer.registerProcessor(new ChangeDisplayNameHandler()); + apiServer.registerProcessor(new ChangeLoginNameHandler()); + apiServer.registerProcessor(new DeleteAccountHandler()); + apiServer.registerProcessor(new UserDetailsHandler()); + apiServer.registerProcessor(new ListPlayersHandler()); + apiServer.registerProcessor(new RegistrationHandler()); + apiServer.registerProcessor(new PlayerDataDownloadHandler()); + apiServer.registerProcessor(new SaveManagerHandler()); + + // Fallback + apiServer.registerProcessor(new FallbackAPIProcessor()); + } + private static SSLContext getContext(File keystore, char[] password) throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyManagementException, CertificateException, FileNotFoundException, IOException { diff --git a/src/main/java/org/asf/centuria/accounts/SaveManager.java b/src/main/java/org/asf/centuria/accounts/SaveManager.java index 1ec53ca5..e6dba0be 100644 --- a/src/main/java/org/asf/centuria/accounts/SaveManager.java +++ b/src/main/java/org/asf/centuria/accounts/SaveManager.java @@ -18,6 +18,20 @@ public abstract class SaveManager { */ public abstract PlayerInventory getSaveSpecificInventoryOf(String save); + /** + * Retrieves the settings of a specific save + * + * @since Beta 1.6.5 + * @param save Save name + * @return SaveSettings instance or null if invalid + */ + public SaveSettings getSaveSettingsOf(String save) { + PlayerInventory inv = getSaveSpecificInventoryOf(save); + if (inv == null) + return null; + return inv.getSaveSettings(); + } + /** * Retrieves the current active save name * diff --git a/src/main/java/org/asf/centuria/accounts/SaveSettings.java b/src/main/java/org/asf/centuria/accounts/SaveSettings.java index b9d96f16..b9f2a7e1 100644 --- a/src/main/java/org/asf/centuria/accounts/SaveSettings.java +++ b/src/main/java/org/asf/centuria/accounts/SaveSettings.java @@ -23,6 +23,7 @@ public class SaveSettings { public boolean giveAllSanctuaryTypes = true; public boolean giveAllCurrency = true; public boolean giveAllResources = true; + public JsonObject saveColors = null; public String tradeLockID = "default"; public SaveSettings() { @@ -52,6 +53,8 @@ public void load(JsonObject data) { this.giveAllCurrency = data.get("giveAllCurrency").getAsBoolean(); this.giveAllResources = data.get("giveAllResources").getAsBoolean(); this.tradeLockID = data.get("tradeLockID").getAsString(); + if (data.has("saveColors") && !data.get("saveColors").isJsonNull()) + this.saveColors = data.get("saveColors").getAsJsonObject(); } /** @@ -71,6 +74,8 @@ public JsonObject writeToObject() { obj.addProperty("giveAllCurrency", giveAllCurrency); obj.addProperty("giveAllResources", giveAllResources); obj.addProperty("tradeLockID", tradeLockID); + if (saveColors != null) + obj.add("saveColors", saveColors); return obj; } diff --git a/src/main/java/org/asf/centuria/accounts/impl/FileBasedAccountManager.java b/src/main/java/org/asf/centuria/accounts/impl/FileBasedAccountManager.java index caf06c9e..aac3b2c6 100644 --- a/src/main/java/org/asf/centuria/accounts/impl/FileBasedAccountManager.java +++ b/src/main/java/org/asf/centuria/accounts/impl/FileBasedAccountManager.java @@ -522,13 +522,15 @@ public void runForAllAccounts(Consumer action) { // Find all accounts for (File accFile : new File("accounts").listFiles(t -> !t.isDirectory())) { try { + // Verify id UUID.fromString(accFile.getName()); - - // Run action - action.accept(getAccount(accFile.getName())); } catch (Exception e) { // Not a account file + continue; } + + // Run action + action.accept(getAccount(accFile.getName())); } } diff --git a/src/main/java/org/asf/centuria/accounts/impl/FileBasedAccountObject.java b/src/main/java/org/asf/centuria/accounts/impl/FileBasedAccountObject.java index 1a6002d2..6d280207 100644 --- a/src/main/java/org/asf/centuria/accounts/impl/FileBasedAccountObject.java +++ b/src/main/java/org/asf/centuria/accounts/impl/FileBasedAccountObject.java @@ -307,7 +307,7 @@ public String getActiveLook() { looks.addProperty("activeSanctuaryLook", UUID.randomUUID().toString()); mainInv.setItem("activelooks", looks); } - if (!looks.has("activeLook")){ + if (!looks.has("activeLook")) { looks.addProperty("activeLook", UUID.randomUUID().toString()); mainInv.setItem("activelooks", looks); } @@ -344,7 +344,7 @@ public String getActiveSanctuaryLook() { looks.addProperty("activeSanctuaryLook", UUID.randomUUID().toString()); mainInv.setItem("activelooks", looks); } - if (!looks.has("activeSanctuaryLook")){ + if (!looks.has("activeSanctuaryLook")) { looks.addProperty("activeSanctuaryLook", UUID.randomUUID().toString()); mainInv.setItem("activelooks", looks); } @@ -448,7 +448,7 @@ public void login() { @Override public Player getOnlinePlayerInstance() { - return Centuria.gameServer.getPlayer(getAccountID()); + return Centuria.gameServer != null ? Centuria.gameServer.getPlayer(getAccountID()) : null; } @Override @@ -666,6 +666,7 @@ public void migrateSaveDataToManagedMode() throws IllegalArgumentException { migrateItem(sharedInv, "303", inv); migrateItem(sharedInv, "304", inv); migrateItem(sharedInv, "311", inv); + migrateItem(sharedInv, "315", inv); migrateItem(sharedInv, "4", inv); migrateItem(sharedInv, "400", inv); migrateItem(sharedInv, "5", inv); diff --git a/src/main/java/org/asf/centuria/entities/players/Player.java b/src/main/java/org/asf/centuria/entities/players/Player.java index 8677f2eb..9918a3b2 100644 --- a/src/main/java/org/asf/centuria/entities/players/Player.java +++ b/src/main/java/org/asf/centuria/entities/players/Player.java @@ -118,7 +118,7 @@ public void updateSyncBlock(String targetPlayerID, boolean blocked) { // If the player is ingame, show this player to them Player plr = blockedPlayer.getOnlinePlayerInstance(); if (plr != null && roomReady && plr.roomReady && plr.room.equals(room) && plr.levelID == levelID) { - syncTo(plr); + syncTo(plr, WorldObjectMoverNodeType.InitPosition); } } } @@ -190,7 +190,7 @@ public void destroyAt(Player player) { lastAction = 0; } - public void syncTo(Player player) { + public void syncTo(Player player, WorldObjectMoverNodeType nodeType) { if (ghostMode && !player.hasModPerms || player.disableSync) return; // Ghosting @@ -212,7 +212,6 @@ public void syncTo(Player player) { } if (lookObj != null) { - // Spawn player AvatarObjectInfoPacket packet = new AvatarObjectInfoPacket(); @@ -226,13 +225,13 @@ public void syncTo(Player player) { packet.lastMove.positionInfo = new WorldObjectPositionInfo(lastPos.x, lastPos.y, lastPos.z, lastRot.x, lastRot.y, lastRot.z, lastRot.w); packet.lastMove.velocity = new Velocity(); - packet.lastMove.nodeType = WorldObjectMoverNodeType.InitPosition; + packet.lastMove.nodeType = nodeType; packet.lastMove.actorActionType = lastAction; // Look and name packet.look = lookObj.get("components").getAsJsonObject().get("AvatarLook").getAsJsonObject().get("info") .getAsJsonObject(); - packet.displayName = account.getDisplayName(); + packet.displayName = GameServer.getPlayerNameWithPrefix(account); packet.unknownValue = 0; // TODO: What is this?? player.client.sendPacket(packet); @@ -642,7 +641,7 @@ else if (privSetting == 2) ObjectUpdatePacket pkt = new ObjectUpdatePacket(); pkt.action = 0; pkt.mode = 2; - pkt.targetUUID = player.account.getAccountID(); + pkt.id = player.account.getAccountID(); pkt.position = plr.lastPos; pkt.rotation = plr.lastRot; pkt.heading = plr.lastHeading; diff --git a/src/main/java/org/asf/centuria/interactions/InteractionManager.java b/src/main/java/org/asf/centuria/interactions/InteractionManager.java index 289dc4d6..fd0467cb 100644 --- a/src/main/java/org/asf/centuria/interactions/InteractionManager.java +++ b/src/main/java/org/asf/centuria/interactions/InteractionManager.java @@ -566,8 +566,6 @@ public static void runStates(ArrayList states, Player plr, NetworkedO break; } case "35": - case "84": - // Handled by group objects case "3": // Build quest command XtWriter pk = new XtWriter(); diff --git a/src/main/java/org/asf/centuria/interactions/groupobjects/spawnbehaviourproviders/RandomizedSpawnBehaviour.java b/src/main/java/org/asf/centuria/interactions/groupobjects/spawnbehaviourproviders/RandomizedSpawnBehaviour.java index 84462016..8ddef7a0 100644 --- a/src/main/java/org/asf/centuria/interactions/groupobjects/spawnbehaviourproviders/RandomizedSpawnBehaviour.java +++ b/src/main/java/org/asf/centuria/interactions/groupobjects/spawnbehaviourproviders/RandomizedSpawnBehaviour.java @@ -174,7 +174,7 @@ public GroupObject[] provideCurrent(int levelID, Player plr) { return obj; }).toArray(t -> new GroupObject[t]); - // Create rotation object + // Create rotation spawn memory object GroupObjectRotation newRot = new GroupObjectRotation(); newRot.time = System.currentTimeMillis(); @@ -184,9 +184,7 @@ public GroupObject[] provideCurrent(int levelID, Player plr) { int minLockpicks = Integer.parseInt(properties.getOrDefault("lockpicks-min", "8")); int maxLockpicks = Integer.parseInt(properties.getOrDefault("lockpicks-max", "11")); - int lockpicksToSpawn = rnd.nextInt(maxLockpicks + 1); - while (lockpicksToSpawn < minLockpicks) - lockpicksToSpawn = rnd.nextInt(maxLockpicks + 1); + int lockpicksToSpawn = rnd.nextInt(minLockpicks, maxLockpicks + 1); // Find lockpicks GroupObject[] lockpicks = Stream.of(objects).filter(t -> { @@ -203,14 +201,19 @@ public GroupObject[] provideCurrent(int levelID, Player plr) { distanceBetweenLockpicks = (int) ((100d / (double) remainingLockpickObjects) * (double) distanceBetweenLockpicks); for (int i = 0; i < lockpicksToSpawn && remainingLockpickObjects > 0; i++) { - while (true) { - GroupObject lockpick = RandomSelectorUtil.selectRandom(Stream.of(lockpicks).toList()); + // Create list of possible spawns + ArrayList safeToSpawn = new ArrayList(); + for (GroupObject lockpick : lockpicks) { if (!overlaps(lockpick, newRot.objects, distanceBetweenLockpicks) - && isSafeToSpawn(lockpick, newRot)) { - newRot.objects.add(lockpick); - remainingLockpickObjects--; - break; - } + && isSafeToSpawn(lockpick, newRot)) + safeToSpawn.add(lockpick); + } + + // Spawn + if (safeToSpawn.size() != 0) { + GroupObject lockpick = RandomSelectorUtil.selectRandom(safeToSpawn); + newRot.objects.add(lockpick); + remainingLockpickObjects--; } } @@ -220,9 +223,7 @@ && isSafeToSpawn(lockpick, newRot)) { int minWaystonePaths = Integer.parseInt(properties.getOrDefault("waystone-paths-min", "3")); int maxWaystonePaths = Integer.parseInt(properties.getOrDefault("waystone-paths-max", "4")); - int waystonePathsToSpawn = rnd.nextInt(maxWaystonePaths + 1); - while (waystonePathsToSpawn < minWaystonePaths) - waystonePathsToSpawn = rnd.nextInt(maxWaystonePaths + 1); + int waystonePathsToSpawn = rnd.nextInt(minWaystonePaths, maxWaystonePaths + 1); // Find waystone paths GroupObject[] waystonePaths = Stream.of(objects).filter(t -> { @@ -241,15 +242,20 @@ && isSafeToSpawn(lockpick, newRot)) { * 1.5); ArrayList waystones = new ArrayList(); for (int i = 0; i < waystonePathsToSpawn && remainingWaystonePaths > 0; i++) { - while (true) { - GroupObject waystonePath = RandomSelectorUtil.selectRandom(Stream.of(waystonePaths).toList()); + // Create list of possible spawns + ArrayList safeToSpawn = new ArrayList(); + for (GroupObject waystonePath : waystonePaths) { if (!overlaps(waystonePath, waystones, distanceWaystonePaths) - && isSafeToSpawn(waystonePath, newRot)) { - newRot.objects.add(waystonePath); - remainingWaystonePaths--; - waystones.add(waystonePath); - break; - } + && isSafeToSpawn(waystonePath, newRot)) + safeToSpawn.add(waystonePath); + } + + // Spawn + if (safeToSpawn.size() != 0) { + GroupObject waystonePath = RandomSelectorUtil.selectRandom(safeToSpawn); + newRot.objects.add(waystonePath); + remainingWaystonePaths--; + waystones.add(waystonePath); } } @@ -259,9 +265,7 @@ && isSafeToSpawn(waystonePath, newRot)) { int minDigspots = Integer.parseInt(properties.getOrDefault("digspot-min", "6")); int maxDigspots = Integer.parseInt(properties.getOrDefault("digspot-max", "8")); - int digspotsToSpawn = rnd.nextInt(maxDigspots + 1); - while (digspotsToSpawn < minDigspots) - digspotsToSpawn = rnd.nextInt(maxDigspots + 1); + int digspotsToSpawn = rnd.nextInt(minDigspots, maxDigspots + 1); // Find dig spots GroupObject[] digspots = Stream.of(objects).filter(t -> { @@ -279,37 +283,41 @@ && isSafeToSpawn(waystonePath, newRot)) { distanceDigspots = (int) (((100d / (double) remainingDigspots) * (double) distanceDigspots) / 1.7d); ArrayList spawnedDigspots = new ArrayList(); for (int i = 0; i < digspotsToSpawn && remainingDigspots > 0; i++) { - while (true) { - GroupObject digspot = RandomSelectorUtil.selectRandom(Stream.of(digspots).toList()); - if (!overlaps(digspot, spawnedDigspots, distanceDigspots) && isSafeToSpawn(digspot, newRot)) { - newRot.objects.add(digspot); - remainingDigspots--; - spawnedDigspots.add(digspot); - - // Find related interactables - for (String objId : linearObjects.objects.keySet()) { - NetworkedObject nObj = linearObjects.objects.get(objId); - - // Check states - for (ArrayList states : nObj.stateInfo.values()) { - boolean found = false; - for (StateInfo state : states) { - if (state.command.equals("35") && state.params.length == 2 - && state.params[1].equals(digspot.id)) { - found = true; - break; - } - } - if (found) { - GroupObject obj = new GroupObject(); - obj.id = objId; - obj.type = nObj.subObjectInfo.type; - newRot.objects.add(obj); + // Create list of possible spawns + ArrayList safeToSpawn = new ArrayList(); + for (GroupObject digspot : digspots) { + if (!overlaps(digspot, spawnedDigspots, distanceDigspots) && isSafeToSpawn(digspot, newRot)) + safeToSpawn.add(digspot); + } + + // Spawn + if (safeToSpawn.size() != 0) { + GroupObject digspot = RandomSelectorUtil.selectRandom(safeToSpawn); + newRot.objects.add(digspot); + remainingDigspots--; + spawnedDigspots.add(digspot); + + // Find related interactables + for (String objId : linearObjects.objects.keySet()) { + NetworkedObject nObj = linearObjects.objects.get(objId); + + // Check states + for (ArrayList states : nObj.stateInfo.values()) { + boolean found = false; + for (StateInfo state : states) { + if (state.command.equals("35") && state.params.length == 2 + && state.params[1].equals(digspot.id)) { + found = true; + break; } } + if (found) { + GroupObject obj = new GroupObject(); + obj.id = objId; + obj.type = nObj.subObjectInfo.type; + newRot.objects.add(obj); + } } - - break; } } } @@ -320,9 +328,7 @@ && isSafeToSpawn(waystonePath, newRot)) { int minChests = Integer.parseInt(properties.getOrDefault("lockedchests-min", "18")); int maxChests = Integer.parseInt(properties.getOrDefault("lockedchests-max", "20")); - int chestsToSpawn = rnd.nextInt(maxChests + 1); - while (chestsToSpawn < minChests) - chestsToSpawn = rnd.nextInt(maxChests + 1); + int chestsToSpawn = rnd.nextInt(minChests, maxChests + 1); // Find chests GroupObject[] lockedChests = Stream.of(objects).filter(t -> { @@ -340,55 +346,63 @@ && isSafeToSpawn(waystonePath, newRot)) { distanceChests = (int) (((100d / (double) remainingChests) * (double) distanceChests) / 1.7d); ArrayList chests = new ArrayList(); for (int i = 0; i < chestsToSpawn && remainingChests > 0; i++) { - while (true) { - GroupObject chest = RandomSelectorUtil.selectRandom(Stream.of(lockedChests).toList()); - if (!overlaps(chest, chests, distanceChests) && isSafeToSpawn(chest, newRot)) { - newRot.objects.add(chest); - remainingChests--; - chests.add(chest); - - // Okay tricky mess time - // There is no way to know, other than via deduction, what interactions are - // related - // Usually its: interaction (main), use lockpick, gamesuccess - // We are following that chain - - // Find index - NetworkedObject obj = linearObjects.objects.get(chest.id); - NetworkedObject[] objs = linearObjects.objects.values().toArray(t -> new NetworkedObject[t]); - String[] keys = linearObjects.objects.keySet().toArray(t -> new String[t]); - int i2 = 0; - for (NetworkedObject o : objs) { - if (o == obj) - break; - i2++; - } + // Create list of possible spawns + ArrayList safeToSpawn = new ArrayList(); + for (GroupObject chest : lockedChests) { + if (!overlaps(chest, chests, distanceChests) && isSafeToSpawn(chest, newRot)) + safeToSpawn.add(chest); + } - // Find objects - NetworkedObject useLockpick = objs[i2 + 1]; - if (!useLockpick.objectName.equals("Use Lockpick")) - throw new RuntimeException( - "Chest failed to load: " + chest.id + ", chart deduction was incorrect!"); - NetworkedObject gameSuccess = objs[i2 + 2]; - if (!gameSuccess.objectName.equals("GameSuccess")) - throw new RuntimeException( - "Chest failed to load: " + chest.id + ", chart deduction was incorrect!"); - - // Add objects - GroupObject gobj = new GroupObject(); - gobj.id = keys[i2 + 1]; - gobj.type = useLockpick.subObjectInfo.type; - newRot.objects.add(gobj); - gobj = new GroupObject(); - gobj.id = keys[i2 + 2]; - gobj.type = gameSuccess.subObjectInfo.type; - newRot.objects.add(gobj); - - // Mapping - newRot.mapping.put(chest.id, keys[i2 + 2]); - - break; + // Spawn + if (safeToSpawn.size() != 0) { + GroupObject chest = RandomSelectorUtil.selectRandom(safeToSpawn); + newRot.objects.add(chest); + remainingChests--; + chests.add(chest); + + // + // Okay tricky mess time + // + // There is no way to know, other than via deduction, what interactions are + // related + // + // Usually its: interaction (main), use lockpick, gamesuccess + // We are following that chain here to add the chest child objects + // + + // Find index + NetworkedObject obj = linearObjects.objects.get(chest.id); + NetworkedObject[] objs = linearObjects.objects.values().toArray(t -> new NetworkedObject[t]); + String[] keys = linearObjects.objects.keySet().toArray(t -> new String[t]); + int i2 = 0; + for (NetworkedObject o : objs) { + if (o == obj) + break; + i2++; } + + // Find objects + NetworkedObject useLockpick = objs[i2 + 1]; + if (!useLockpick.objectName.equals("Use Lockpick")) + throw new RuntimeException( + "Chest failed to load: " + chest.id + ", chart deduction was incorrect!"); + NetworkedObject gameSuccess = objs[i2 + 2]; + if (!gameSuccess.objectName.equals("GameSuccess")) + throw new RuntimeException( + "Chest failed to load: " + chest.id + ", chart deduction was incorrect!"); + + // Add objects + GroupObject gobj = new GroupObject(); + gobj.id = keys[i2 + 1]; + gobj.type = useLockpick.subObjectInfo.type; + newRot.objects.add(gobj); + gobj = new GroupObject(); + gobj.id = keys[i2 + 2]; + gobj.type = gameSuccess.subObjectInfo.type; + newRot.objects.add(gobj); + + // Mapping + newRot.mapping.put(chest.id, keys[i2 + 2]); } } diff --git a/src/main/java/org/asf/centuria/interactions/modules/InspirationCollectionModule.java b/src/main/java/org/asf/centuria/interactions/modules/InspirationCollectionModule.java index e078b8a1..bfdb8bd5 100644 --- a/src/main/java/org/asf/centuria/interactions/modules/InspirationCollectionModule.java +++ b/src/main/java/org/asf/centuria/interactions/modules/InspirationCollectionModule.java @@ -31,7 +31,6 @@ public boolean canHandle(Player player, String id, NetworkedObject object) { if (stateInfo.params[0].equals("1") && stateInfo.params[1].equals("4")) { return true; } - } else { // check if any branches do if (!stateInfo.branches.isEmpty()) { @@ -66,7 +65,6 @@ public int isDataRequestValid(Player player, String id, NetworkedObject object, return 1; // Safe to run } return -1; - } @Override diff --git a/src/main/java/org/asf/centuria/minigames/MinigameManager.java b/src/main/java/org/asf/centuria/minigames/MinigameManager.java index 3ebf1251..9d7a7314 100644 --- a/src/main/java/org/asf/centuria/minigames/MinigameManager.java +++ b/src/main/java/org/asf/centuria/minigames/MinigameManager.java @@ -4,8 +4,10 @@ import org.asf.centuria.entities.players.Player; import org.asf.centuria.minigames.games.GameDoOrDye; +import org.asf.centuria.minigames.games.GameKinoParlor; import org.asf.centuria.minigames.games.GameTwiggleBuilders; import org.asf.centuria.minigames.games.GameWhatTheHex; +import org.asf.centuria.minigames.games.GameDizzywingDispatch; /** * @@ -23,6 +25,8 @@ public class MinigameManager { registerMinigame(new GameWhatTheHex()); registerMinigame(new GameTwiggleBuilders()); registerMinigame(new GameDoOrDye()); + registerMinigame(new GameDizzywingDispatch()); + registerMinigame(new GameKinoParlor()); } /** diff --git a/src/main/java/org/asf/centuria/minigames/games/DDVis.java b/src/main/java/org/asf/centuria/minigames/games/DDVis.java new file mode 100644 index 00000000..fe296ef2 --- /dev/null +++ b/src/main/java/org/asf/centuria/minigames/games/DDVis.java @@ -0,0 +1,168 @@ +package org.asf.centuria.minigames.games; + +import java.awt.Color; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.HashMap; +import java.util.Map; + +import javax.swing.JFrame; +import javax.swing.JButton; +import javax.swing.JLabel; +import org.asf.centuria.Centuria; +import org.asf.centuria.data.XtReader; +import org.asf.centuria.minigames.games.GameDizzywingDispatch.GameState.GridCell; +import org.asf.centuria.minigames.games.GameDizzywingDispatch.GameState.TileType; +import org.joml.Vector2i; + +public class DDVis { + + public JFrame frame; + public Map gameBoardDisplay = new HashMap<>(); + public Map gameBoardDisplayInverseMapping = new HashMap<>(); + private GameDizzywingDispatch ses; + + /** + * Create the application. + */ + public DDVis() { + initialize(); + } + + /** + * Initialize the contents of the frame. + */ + private void initialize() { + frame = new JFrame(); + frame.setBounds(100, 100, 1000, 650); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.getContentPane().setLayout(null); + + MyMouseListener myMouseListener = new MyMouseListener(); + LevelIncrementListener levelIncrementListener = new LevelIncrementListener(); + + for (int y = 0; y < 9; y++) + { + for (int x = 0; x < 9; x++) + { + JLabel l1 = new JLabel("New label"); + l1.setBounds((1000/9)*x, (500/9)*y, (1000/9), (500/9)); + l1.addMouseListener(myMouseListener); + frame.getContentPane().add(l1); + gameBoardDisplay.put(new Vector2i(x, y), l1); + gameBoardDisplayInverseMapping.put(l1, new Vector2i(x, 8-y)); + } + } + + JButton b = new JButton("next level"); + b.setBounds((1000/9)*1, (500/9)*10, (1000/9), (500/9)); + b.addMouseListener(levelIncrementListener); + frame.getContentPane().add(b); + + + new Thread(() -> { + while (true) { + if (Centuria.gameServer.getPlayers().length != 0) { + if (Centuria.gameServer.getPlayers()[0].currentGame != null + && Centuria.gameServer.getPlayers()[0].currentGame instanceof GameDizzywingDispatch) { + ses = (GameDizzywingDispatch) Centuria.gameServer.getPlayers()[0].currentGame; + for (int y = 0; y < 9; y++) + { + for (int x = 0; x < 9; x++) + { + if(ses.gameState != null){ + + gameBoardDisplay.get(new Vector2i(x, 8-y)).setText( + "" + + ses.gameState.getCell(new Vector2i(x, y)).getTileType().toString() + + "
" + + ses.gameState.getCell(new Vector2i(x, y)).getBooster().toString() + + "
" + + ses.gameState.floodFillGetToVisit(new Vector2i(x, y)) + + ""); + gameBoardDisplay.get(new Vector2i(x, 8-y)).setForeground(TileColor(ses.gameState.getCell(new Vector2i(x, y)).getTileType().toString())); + } + } + } + } + } + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + }).start(); + } + + private Color TileColor(String tileTypeString) { + switch(tileTypeString) { + case "RedBird": + return Color.RED; + case "BlueBird": + return Color.BLUE; + case "AquaBird": + return Color.CYAN; + case "GreenBird": + return Color.GREEN; + case "SnowyBird": + return Color.LIGHT_GRAY; + case "YellowBird": + return Color.ORANGE; + case "PurpleBird": + return Color.MAGENTA; + case "PinkBird": + return Color.PINK; + default: + return Color.BLACK; + } + } + + class MyMouseListener extends MouseAdapter { + + @Override + public void mousePressed(MouseEvent e) + { + JLabel label = (JLabel) e.getComponent(); + Vector2i pos = gameBoardDisplayInverseMapping.get(label); + if(ses.gameState != null){ + GridCell curr = ses.gameState.getCell(pos); + + if(curr.getTileType() != TileType.PinkBird){ + curr.setTileType(TileType.PinkBird); + } else if (curr.getHealth() == 0) { + curr.setHealth(1); + } else if (curr.getTileType() != TileType.HatOrPurse){ + curr.setHealth(0); + curr.setTileType(TileType.HatOrPurse); + } + + ses.gameState.setCell(pos, curr); + ses.syncClient(Centuria.gameServer.getPlayers()[0], new XtReader("")); + } + } + + } + + class LevelIncrementListener extends MouseAdapter { + + @Override + public void mousePressed(MouseEvent e) + { + if(ses.gameState != null){ + ses.gameState.levelObjectives.objectivesTracker[0][0] = 1; + ses.gameState.levelObjectives.objectivesTracker[0][1] = 2; + ses.gameState.levelObjectives.objectivesTracker[1][0] = -1; + ses.gameState.levelObjectives.objectivesTracker[1][1] = -1; + ses.gameState.levelObjectives.objectivesTracker[2][0] = -1; + ses.gameState.levelObjectives.objectivesTracker[2][1] = -1; + ses.gameState.levelObjectives.objectivesTracker[3][0] = -1; + ses.gameState.levelObjectives.objectivesTracker[3][1] = -1; + ses.syncClient(Centuria.gameServer.getPlayers()[0], new XtReader("")); + } + } + + } + +} diff --git a/src/main/java/org/asf/centuria/minigames/games/GameDizzywingDispatch.java b/src/main/java/org/asf/centuria/minigames/games/GameDizzywingDispatch.java new file mode 100644 index 00000000..70ef3759 --- /dev/null +++ b/src/main/java/org/asf/centuria/minigames/games/GameDizzywingDispatch.java @@ -0,0 +1,2007 @@ +package org.asf.centuria.minigames.games; + +import java.util.Queue; +import java.util.Random; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Base64; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.asf.centuria.Centuria; +import org.asf.centuria.data.XtReader; +import org.asf.centuria.data.XtWriter; +import org.asf.centuria.entities.players.Player; +import org.asf.centuria.entities.uservars.UserVarValue; +import org.asf.centuria.minigames.AbstractMinigame; +import org.asf.centuria.minigames.MinigameMessage; +import org.asf.centuria.packets.xt.gameserver.inventory.InventoryItemDownloadPacket; +import org.asf.centuria.packets.xt.gameserver.inventory.InventoryItemPacket; +import org.asf.centuria.packets.xt.gameserver.minigame.MinigameCurrencyPacket; +import org.asf.centuria.packets.xt.gameserver.minigame.MinigameMessagePacket; +import org.asf.centuria.packets.xt.gameserver.minigame.MinigamePrizePacket; +import org.asf.centuria.interactions.modules.ResourceCollectionModule; +import org.asf.centuria.interactions.modules.resourcecollection.rewards.LootInfo; +import org.asf.centuria.levelevents.LevelEvent; +import org.asf.centuria.levelevents.LevelEventBus; +import org.joml.Vector2i; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +public class GameDizzywingDispatch extends AbstractMinigame{ + + private String currentGameUUID; + public GameState gameState; + public PuzzleObjectives puzzleObjectives; + public int level = 0; + private int score = 0; + private int moveCount = 0; + private int dizzyBirdMeter = 0; + + static private JsonArray specialOrders; + static private JsonArray specialOrderCountRanges; + static private JsonArray levelRewards; + static private JsonArray puzzleRewards; + static private JsonArray achievementToUserVarIndexList; + + static { + // Load level info + try { + // Load all level info into a JsonObject + InputStream strm = InventoryItemDownloadPacket.class.getClassLoader() + .getResourceAsStream("minigames/dizzywingdispatch.json"); + JsonObject helper = JsonParser.parseString(new String(strm.readAllBytes(), "UTF-8")).getAsJsonObject(); + strm.close(); + + // Seperate the level data by Json key + specialOrders = helper.getAsJsonArray("specialOrders"); + specialOrderCountRanges = helper.getAsJsonArray("specialOrderCountRanges"); + levelRewards = helper.getAsJsonArray("levelRewards"); + puzzleRewards = helper.getAsJsonArray("puzzleRewards"); + achievementToUserVarIndexList = helper.getAsJsonArray("achievementToUserVarIndexList"); + + } catch (IOException e) { + // This is very bad, should not start allow the server to continue otherwise + // things will break HARD + throw new RuntimeException(e); + } + + /* + if (Centuria.debugMode) { + DDVis vis = new DDVis(); + new Thread(() -> vis.frame.setVisible(true)).start(); + } + */ + } + + // Types of puzzles objectives to be kept track by the server + enum PuzzleObjectiveType + { + HighScore(0), + TotalScore(1), + MakeFlyers(2), + MakeFlyers_SingleGame(3), + MakeBombBirds(4), + MakeBombBirds_SingleGame(5), + MakePeacocks(6), + MakePeacocks_SingleGame(7), + ClearRedBirds(8), + ClearRedBirds_SingleGame(9), + ClearBlueBirds(10), + ClearBlueBirds_SingleGame(11), + ClearGreenBirds(12), + ClearGreenBirds_SingleGame(13), + ClearYellowBirds(14), + ClearYellowBirds_SingleGame(15), + ClearPinkBirds(17), + ClearPinkBirds_SingleGame(18), + ClearWhiteBirds(19), + ClearWhiteBirds_SingleGame(20), + ClearPurpleBirds(21), + ClearPurpleBirds_SingleGame(22), + ClearAquaBirds(23), + ClearAquaBirds_SingleGame(24), + CompleteOrders(25), + CompleteOrders_SingleGame(26), + CompleteSpecialOrders(27), + CompleteSpecialOrders_SingleGame(28), + CompleteRushHourOrders(29), + CompleteRushHourOrders_SingleGame(31), + CompleteShortStaffedOrders(32), + CompleteShortStaffedOrders_SingleGame(33), + CompleteComboOrders(34), + CompleteComboOrders_SingleGame(35), + ClearedWithPeacockRed(36), + ClearedWithPeacockRed_SingleGame(37), + ClearedWithPeacockBlue(38), + ClearedWithPeacockBlue_SingleGame(39), + ClearedWithPeacockGreen(40), + ClearedWithPeacockGreen_SingleGame(41), + ClearedWithPeacockYellow(42), + ClearedWithPeacockYellow_SingleGame(43), + ClearedWithPeacockPink(44), + ClearedWithPeacockPink_SingleGame(45), + ClearedWithPeacockWhite(46), + ClearedWithPeacockWhite_SingleGame(47), + ClearedWithPeacockPurple(48), + ClearedWithPeacockPurple_SingleGame(49), + ClearedWithPeacockAqua(50), + ClearedWithPeacockAqua_SingleGame(51), + HatchPowerups(52), + HatchPowerups_SingleGame(53), + ComboFlyerBomb(54), + ComboFlyerBomb_SingleGame(55), + ComboFlyerPeacock(56), + ComboFlyerPeacock_SingleGame(57), + ComboBombPeacock(58), + ComboBombPeacock_SingleGame(59), + ComboFlyerFlyer(60), + ComboFlyerFlyer_SingleGame(61), + ComboBombBomb(62), + ComboBombBomb_SingleGame(63), + ComboPeacockPeacock(64), + ComboPeacockPeacock_SingleGame(65); + + private final int value; + private PuzzleObjectiveType(int value) { + this.value = value; + } + public int getVal() { + return value; + } + + } + + // Short hand for constants used in saving to player inventory + enum UserVarIDs + { + persistentAchievementDataUserVarDefId(13392), + puzzleRedemptionStatusUserVarDefId(13404), + puzzlePieceRedemptionStatusUserVarDefId(13405), + savedGameUserVarDefId(30613), + tutorial(13624), + userVarInventory(303); + + private final int value; + private UserVarIDs(int value) { + this.value = value; + } + public int getVal() { + return value; + } + + } + + // all game logic is stored in this class + public class GameState { + + // helper classes + + // represents a tile in the game board + public class GridCell { + private int tileHealth; + private TileType TileType; + private BoosterType Booster; + + public GridCell(int health, TileType tileType, BoosterType booster){ + tileHealth = health; + TileType = tileType; + Booster = booster; + } + + public void setTileType(TileType tileType){ + TileType = tileType; + } + + public TileType getTileType(){ + return TileType; + } + + public void setBooster(BoosterType booster){ + Booster = booster; + } + + public BoosterType getBooster(){ + return Booster; + } + + public void setHealth(int health){ + tileHealth = health; + } + + public int getHealth(){ + return tileHealth; + } + + public boolean isBoosted(){ + return Booster != BoosterType.None; + } + + } + + // keep tracks of the types of orders that need to be cleared before a level may end + public class LevelObjectives { + public int objectivesTracker[][] = { {-1, -1, 0}, + {-1, -1, 1}, + {-1, -1, 2}, + {-1, -1, 3}}; + + JsonObject specialOrderData; + JsonObject specialOrderCountRangeData; + + int currScore = 0; + + public LevelObjectives(){ + newLevelNewObjectives(); + } + + public void newLevelNewObjectives(){ + + currScore = 0; + if(level != 0){ + + //get level data for the level range the current level falls into + + for(JsonElement ele : specialOrders){ + JsonObject data = ele.getAsJsonObject(); + if((level >= data.get("_fromLevelNumber").getAsInt() && + level <= data.get("_toLevelNumber").getAsInt()) || + data.get("_isToLevelInfinite").getAsBoolean()){ + + specialOrderData = data; + break; + } + } + + for(JsonElement ele : specialOrderCountRanges){ + JsonObject data = ele.getAsJsonObject(); + if((level >= data.get("_fromLevelNumber").getAsInt() && + level <= data.get("_toLevelNumber").getAsInt()) || + data.get("_isToLevelInfinite").getAsBoolean()){ + + specialOrderCountRangeData = data; + break; + } + } + } else { + specialOrderData = specialOrders.get(0).getAsJsonObject(); + specialOrderCountRangeData = specialOrderCountRanges.get(0).getAsJsonObject(); + } + + // clear the score objective + initObjective(LevelObjectiveType.ScoreRequirement, scoreRequirement()); + + // randomly pick objectives to be given to the player + if(!diceRoll("_regularLevelAppearancePercent")){ + + int clothing = 0; + int eggs = 0; + + if(diceRoll("_accessoriesAppearancePercent")){ + clothing = initObjective(LevelObjectiveType.ClothingLeft, "_minimumAccessoryCount", "_maximumAccessoryCount"); + } else { + clearObjective(LevelObjectiveType.ClothingLeft); + } + + if (diceRoll("_eggsAppearancePercent")){ + eggs = initObjective(LevelObjectiveType.EggsLeft, "_minimumEggRowCount", "_maximumEggRowCount"); + } else { + clearObjective(LevelObjectiveType.EggsLeft); + } + + if (diceRoll("_limitedMovesAppearancePercent")){ + initObjective(LevelObjectiveType.MovesLeft, "_minimumLimitedMovesOnlyCount", "_maximumLimitedMovesOnlyCount"); + } else { + clearObjective(LevelObjectiveType.MovesLeft); + } + + // Add clothing and eggs to the board if the respective objectives are given + for(int y = gridSize.y-1; y >= 0; y--){ + for(int x = gridSize.x-1; x >= 0; x--){ + Vector2i curr = new Vector2i(x, y); + GridCell currCell = getCell(curr); + + if(!currCell.isBoosted()) { + if(currCell.getTileType() != TileType.HatOrPurse && + !currCell.isBoosted() && clothing > 0){ + + currCell.setTileType(TileType.HatOrPurse); + setCell(curr, currCell); + clothing--; + } else if(currCell.getHealth() == 0 && !currCell.isBoosted() && eggs > 0){ + currCell.setHealth(1); + setCell(curr, currCell); + eggs--; + } + } + } + } + + } + + + + } + + public boolean isNextLevel(){ // returns true if level has increased + trackEggsAndClothing(); + + boolean goToNextLevel = true; + goToNextLevel &= isAchieved(LevelObjectiveType.ClothingLeft); + goToNextLevel &= isAchieved(LevelObjectiveType.EggsLeft); + goToNextLevel &= isAchieved(LevelObjectiveType.ScoreRequirement); + goToNextLevel &= !hasRunOutOfMoves(); + goToNextLevel &= isObjective(LevelObjectiveType.ScoreRequirement); + + if(goToNextLevel){ + level++; + + // update puzzle objectives + + int numberOfOrders = 0; + + if(isAchieved(LevelObjectiveType.ClothingLeft) && + isObjective(LevelObjectiveType.ClothingLeft)){ + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.CompleteSpecialOrders); + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.CompleteSpecialOrders_SingleGame); + numberOfOrders++; + } + if(isAchieved(LevelObjectiveType.MovesLeft) && + isObjective(LevelObjectiveType.MovesLeft)){ + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.CompleteRushHourOrders); + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.CompleteRushHourOrders_SingleGame); + numberOfOrders++; + } + if(isAchieved(LevelObjectiveType.EggsLeft) && + isObjective(LevelObjectiveType.EggsLeft)){ + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.CompleteShortStaffedOrders); + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.CompleteShortStaffedOrders_SingleGame); + numberOfOrders++; + } + if(numberOfOrders > 1){ + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.CompleteComboOrders); + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.CompleteComboOrders_SingleGame); + } + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.CompleteOrders); + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.CompleteOrders_SingleGame); + + // add new bird types to be placed on to the board + + if(level > 25 && !spawnTiles.contains(TileType.AquaBird)){ + spawnTiles.add(TileType.AquaBird); + } else if (level > 75 && !spawnTiles.contains(TileType.BlueBird)){ + spawnTiles.add(TileType.BlueBird); + } else if (level > 100 && !spawnTiles.contains(TileType.PinkBird)){ + spawnTiles.add(TileType.PinkBird); + } + + // perform a loot table lookup to give a reward to the player + + for(JsonElement ele : levelRewards){ + JsonObject levelData = (JsonObject)ele; + JsonArray rewardData = levelData.getAsJsonArray("lootData"); + + int randNo = randomizer.nextInt(100); + int sumWeight = 0; + + if((level >= levelData.get("levelIndex").getAsInt() && + level <= levelData.get("endLevelIndex").getAsInt()) || + levelData.get("isEndLevelInfinite").getAsBoolean()){ + + for(JsonElement eleReward : rewardData){ + JsonObject reward = (JsonObject)eleReward; + + if(sumWeight < randNo && randNo <= sumWeight+reward.get("weight").getAsInt()){ + + String lootTableDefID = reward.get("lootTableDefID").getAsString(); + Centuria.logger.debug(lootTableDefID, "lootTableDefId"); + LootInfo chosenReward = ResourceCollectionModule.getLootReward(lootTableDefID); + + // Give reward + String itemId = chosenReward.reward.itemId; + while(itemId == null){ + chosenReward = ResourceCollectionModule.getLootReward(chosenReward.reward.referencedTableId); + itemId = chosenReward.reward.itemId; + } + Centuria.logger.debug(itemId, "itemID"); + + int count = chosenReward.count; + + player.account.getSaveSpecificInventory().getItemAccessor(player) + .add(Integer.parseInt(itemId), count); + + // XP + LevelEventBus.dispatch(new LevelEvent("levelevents.minigames.dizzywingdispatch", + new String[] { "level:" + level }, player)); + + if(lootTableDefID != "13629"){ + // Send packet + MinigamePrizePacket p1 = new MinigamePrizePacket(); + p1.given = true; + p1.itemDefId = itemId; + p1.itemCount = count; + p1.given = true; + p1.prizeIndex1 = 0; + p1.prizeIndex2 = 0; + player.client.sendPacket(p1); + } else { + MinigameCurrencyPacket currency = new MinigameCurrencyPacket(); + currency.Currency = Integer.parseInt(itemId); + player.client.sendPacket(currency); + } + + break; + + } else { + sumWeight += reward.get("weight").getAsInt(); + } + + } + break; + + + } + } + + // get the objectives for the new level + newLevelNewObjectives(); + } + + return goToNextLevel; + } + + public void addScore(int increase){ // called externally to keep track of score + currScore += increase; + updateObjective(LevelObjectiveType.ScoreRequirement, currScore); + } + + public void trackMoves(){ // called externally to keep track of moves remaining + if(isObjective(LevelObjectiveType.MovesLeft)){ + updateObjectiveByChange(LevelObjectiveType.MovesLeft, 1); + } + } + + public boolean hasRunOutOfMoves(){ + if(isObjective(LevelObjectiveType.MovesLeft)){ + return objectivesTracker[LevelObjectiveType.MovesLeft.ordinal()][1] < 0; + } else { + return false; + } + } + + private void trackEggsAndClothing(){ // called to keep track of remaining eggs and clothing + if(isObjective(LevelObjectiveType.EggsLeft)){ + int numberOfEggs = 0; + for(int x = 0; x < gridSize.x; x++){ + for(int y = 0; y < gridSize.y; y++){ + Vector2i curr = new Vector2i(x, y); + + if(getCell(curr).getHealth() > 0) { + numberOfEggs++; + } + } + } + updateObjectiveRemaining(LevelObjectiveType.EggsLeft, numberOfEggs); + } + + if(isObjective(LevelObjectiveType.ClothingLeft)){ + int numberOfClothing = 0; + for(int x = 0; x < gridSize.x; x++){ + for(int y = 0; y < gridSize.y; y++){ + Vector2i curr = new Vector2i(x, y); + + if(getCell(curr).getTileType() == TileType.HatOrPurse) { + numberOfClothing++; + } + } + } + updateObjectiveRemaining(LevelObjectiveType.ClothingLeft, numberOfClothing); + } + } + + // helper functions + + private boolean diceRoll(String attribute){ // probability of objectives is expressed as percentages + return randomizer.nextInt(100) < specialOrderData.get(attribute).getAsInt(); + } + + private boolean isObjective(LevelObjectiveType objectiveType) { // check if objective has been assigned to the player + return objectivesTracker[objectiveType.ordinal()][0] != -1; + } + + private boolean isAchieved(LevelObjectiveType objectiveType){ // returns true if objective is fulfilled or not assigned + if(objectivesTracker[objectiveType.ordinal()][0] == -1){ + return true; + } else if (objectivesTracker[objectiveType.ordinal()][0] <= + objectivesTracker[objectiveType.ordinal()][1]) { + return true; + } else{ + return false; + } + } + + private int initObjective(LevelObjectiveType objectiveType, String minimum, String maximum){ // generates objective given names of Json elements that contain level data + + int goal = randomizer.nextInt( + specialOrderCountRangeData.get(minimum).getAsInt(), + specialOrderCountRangeData.get(maximum).getAsInt()+1); + if(goal > 0){ + objectivesTracker[objectiveType.ordinal()][0] = goal; + objectivesTracker[objectiveType.ordinal()][1] = 0; + } else { + objectivesTracker[objectiveType.ordinal()][0] = -1; + objectivesTracker[objectiveType.ordinal()][1] = -1; + } + + return objectivesTracker[objectiveType.ordinal()][0]; + } + + private int scoreRequirement(){ // formula for score required to pass a level + + int r = 50; // round to the nearest r + int s = 500; // starting level score + Float g = 2.5f; // growth rate + int o = 125; // offset + + return (int) (r * Math.round((s * Math.log1p(g * (level + o) - g + 1f) + s) / r)); + } + + // These are internal functions for changing objectives in a level or update their progression + + private void initObjective(LevelObjectiveType objectiveType, int requirement){ + objectivesTracker[objectiveType.ordinal()][0] = requirement; + objectivesTracker[objectiveType.ordinal()][1] = 0; + } + + private void clearObjective(LevelObjectiveType objectiveType){ + objectivesTracker[objectiveType.ordinal()][0] = -1; + objectivesTracker[objectiveType.ordinal()][1] = -1; + } + + private void updateObjectiveByChange(LevelObjectiveType objectiveType, int value){ + objectivesTracker[objectiveType.ordinal()][1] += value; + } + + private void updateObjectiveRemaining(LevelObjectiveType objectiveType, int value){ + objectivesTracker[objectiveType.ordinal()][1] = objectivesTracker[objectiveType.ordinal()][0] - value; + } + + private void updateObjective(LevelObjectiveType objectiveType, int value){ + objectivesTracker[objectiveType.ordinal()][1] = value; + } + + } + + // the "power ups" in the game + enum BoosterType + { + None, + BuzzyBirdHorizontal, + BuzzyBirdVertical, + BoomBird, + PrismPeacock + } + + // the tile "colors" in the game + enum TileType { + AquaBird, + BlueBird, + GreenBird, + PinkBird, + PurpleBird, + RedBird, + SnowyBird, + YellowBird, + HatOrPurse, + None + } + + // the types of orders in a level, including the score equirement + enum LevelObjectiveType + { + ScoreRequirement, + MovesLeft, + EggsLeft, + ClothingLeft + } + + + + // class fields + + // game board data + private GridCell[][] grid; + private Vector2i gridSize; + + // used by the flood fill algorithm + private boolean toVisit[][]; + private boolean visited[][]; + + // used to generate new tiles + private List spawnTiles; + + // source of all randomness in the game + private Random randomizer; + + // used to calculate current score as multiple matches in a row give more points + private int matchComboScore; + + // used to keep track of level objectives + public LevelObjectives levelObjectives; + + // to send level rewards to the player + private Player player; + + public GameState(Player Player){ + gridSize = new Vector2i(9, 9); + spawnTiles = new ArrayList(Arrays.asList( + TileType.GreenBird, + TileType.PurpleBird, + TileType.RedBird, + TileType.SnowyBird, + TileType.YellowBird)); + randomizer = new Random(currentGameUUID.hashCode()); + initializeGameBoard(); + floodFillClearVisited(); + levelObjectives = new LevelObjectives(); + player = Player; + } + + + + // functions related to accessing or modifying game tiles + + public GridCell getCell(Vector2i pos) + { + if (pos.x >= 0 && pos.x < gridSize.x && pos.y >= 0 && pos.y < gridSize.y) + { + return grid[pos.x][pos.y]; + } + return null; + } + + public void setCell(Vector2i pos, GridCell cell){ + if (pos.x >= 0 && pos.x < gridSize.x && pos.y >= 0 && pos.y < gridSize.y && cell != null) + { + grid[pos.x][pos.y] = cell; + } + } + + // template function with one less argument to avoid errors + private void clearCell(Vector2i pos, boolean isScore, boolean forceClear){ + clearCell(pos, isScore, forceClear, false); + } + + private void clearCell(Vector2i pos, boolean isScore, boolean forceClear, boolean isClearedByPeacock){ + + if (getCell(pos) == null) { + return; + } + + switch(getCell(pos).getTileType()){ + case RedBird: + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.ClearRedBirds); + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.ClearRedBirds_SingleGame); + break; + case BlueBird: + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.ClearBlueBirds); + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.ClearBlueBirds_SingleGame); + break; + case GreenBird: + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.ClearGreenBirds); + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.ClearGreenBirds_SingleGame); + break; + case YellowBird: + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.ClearYellowBirds); + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.ClearYellowBirds_SingleGame); + break; + case PinkBird: + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.ClearPinkBirds); + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.ClearPinkBirds_SingleGame); + break; + case SnowyBird: + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.ClearWhiteBirds); + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.ClearWhiteBirds_SingleGame); + break; + case PurpleBird: + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.ClearPurpleBirds); + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.ClearPurpleBirds_SingleGame); + break; + case AquaBird: + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.ClearAquaBirds); + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.ClearAquaBirds_SingleGame); + break; + case None: + break; + default: + break; + } + if(isClearedByPeacock){ + switch(getCell(pos).getTileType()){ + case RedBird: + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.ClearedWithPeacockRed); + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.ClearedWithPeacockRed_SingleGame); + break; + case BlueBird: + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.ClearedWithPeacockBlue); + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.ClearedWithPeacockBlue_SingleGame); + break; + case GreenBird: + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.ClearedWithPeacockGreen); + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.ClearedWithPeacockGreen_SingleGame); + break; + case YellowBird: + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.ClearedWithPeacockYellow); + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.ClearedWithPeacockYellow_SingleGame); + break; + case PinkBird: + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.ClearedWithPeacockPink); + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.ClearedWithPeacockPink_SingleGame); + break; + case SnowyBird: + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.ClearedWithPeacockWhite); + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.ClearedWithPeacockWhite_SingleGame); + break; + case PurpleBird: + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.ClearedWithPeacockPurple); + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.ClearedWithPeacockPurple_SingleGame); + break; + case AquaBird: + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.ClearedWithPeacockAqua); + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.ClearedWithPeacockAqua_SingleGame); + break; + case None: + break; + default: + break; + } + } + + if(forceClear){ + setCell(pos, new GridCell(0, TileType.None, BoosterType.None)); + return; + } else if(getCell(pos).getHealth() > 0) { + breakEggTile(pos); + } else if(getCell(pos).getTileType() != TileType.HatOrPurse && getCell(pos).getBooster() != BoosterType.PrismPeacock) { + setCell(pos, new GridCell(0, TileType.None, BoosterType.None)); + } + + if(isScore) { + score += 30; + dizzyBirdMeter += 30; + } + + switch (getCell(pos).getBooster()) { + case BuzzyBirdHorizontal: + buzzyBirdHorizontalBehaviour(pos, 1); + break; + case BuzzyBirdVertical: + buzzyBirdVerticalBehaviour(pos, 1); + break; + case BoomBird: + boomBirdBehaviour(pos, 3); + break; + default: + + break; + } + } + + private void breakEggTile(Vector2i pos){ + GridCell eggTile = getCell(pos); + if(eggTile != null){ + eggTile.setHealth(0); + setCell(pos, eggTile); + levelObjectives.trackEggsAndClothing(); + } + } + + private void clearGrid(){ + grid = new GridCell[gridSize.x][gridSize.y]; + } + + + // functions that generate some type of string representation of the game board + + public int calculateBoardChecksum() // level of emulation accuracy uncertain + { + byte[] inArray = new byte[gridSize.x * gridSize.y]; + + for(int x = 0; x < gridSize.x; x++){ + for(int y = 0; y < gridSize.y; y++){ + inArray[x + gridSize.x * y] = gridCellByteValueForChecksum(new Vector2i(x, y)); + } + } + + //Board checksum algorithm! + String string1 = Base64.getEncoder().encodeToString(inArray); + String string2 = currentGameUUID.toString(); + String string3 = ((Integer)(moveCount)).toString(); + return (string1 + string2 + string3).hashCode(); + } + + private byte gridCellByteValueForChecksum(Vector2i pos) // level of emulation accuracy uncertain + { + int byteValue = 0; + GridCell cell = getCell(pos); + + int tileTypeValue = cell.getTileType().ordinal(); + + if (cell.isBoosted()) { + byteValue += (cell.isBoosted() ? 1 : 0) * 20 + tileTypeValue; + } else { + byteValue += tileTypeValue * 2 + cell.tileHealth; + } + + return (byte)byteValue; + } + + public String toBase64String(){ + byte[] inArray = new byte[gridSize.x * gridSize.y]; + + for(int x = 0; x < gridSize.x; x++){ + for(int y = 0; y < gridSize.y; y++){ + inArray[x + gridSize.x * y] = gridCellByteValueForBase64String(new Vector2i(x, y)); + } + } + + return Base64.getEncoder().encodeToString(inArray); + } + + public byte gridCellByteValueForBase64String(Vector2i pos) + { + int byteValue = 0; + GridCell cell = getCell(pos); + + if(cell.TileType == TileType.HatOrPurse){ + byteValue = 18; + } else if (cell.Booster == BoosterType.PrismPeacock){ + byteValue = 88; + } else { + byteValue += ((cell.Booster == BoosterType.None ? 2 : 1) * cell.TileType.ordinal() + (cell.tileHealth == 0 ? 0 : 1)); + byteValue += cell.Booster.ordinal() * 20; + } + + + return (byte)byteValue; + } + + + // functions that generate new game boards + + private void initializeGameBoard() // level of emulation accuracy uncertain + { + + clearGrid(); + List shuffledSpawnTiles = new ArrayList(spawnTiles); + + for (int y = 0; y < gridSize.y; y++) + { + for (int x = 0; x < gridSize.x; x++) + { + Collections.shuffle(shuffledSpawnTiles, randomizer); + + for (TileType spawnTile : shuffledSpawnTiles) + { + // checks so that matches don't naturally form + if ((x < 2 || !(getCell(new Vector2i(x - 1, y)).TileType == spawnTile) || + !(getCell(new Vector2i(x - 2, y)).TileType == spawnTile)) && + (y < 2 || !(getCell(new Vector2i(x, y - 1)).TileType == spawnTile) || + !(getCell(new Vector2i(x, y - 2)).TileType == spawnTile))) + { + setCell(new Vector2i(x, y), new GridCell(0, spawnTile, BoosterType.None)); + break; + } + } + } + } + } + + // used by the scramble tiles command + public void scrambleTiles() { + List scrambledTiles = new ArrayList<>(); + + for(int x = 0; x < gridSize.x; x++){ + for(int y = 0; y < gridSize.y; y++){ + Vector2i curr = new Vector2i(x, y); + scrambledTiles.add(getCell(curr)); + } + } + + Collections.shuffle(scrambledTiles, randomizer); + + int tileNumber = 0; + for(GridCell tile : scrambledTiles){ + setCell(new Vector2i(tileNumber % gridSize.y, tileNumber / gridSize.y), tile); + tileNumber++; + } + + while(true){ // copied over from the calculateMoves function + + boolean isMatches = findAndClearMatches(new Vector2i(-1, -1), new Vector2i(-1, -1)); // blank input + fillGaps(); + clearHats(); + fillGaps(); // replace tiles marked as having being cleared + + if(!isMatches){ + break; + } + + } + } + + private void clearHats() { // level of emulation accuracy uncertain + for(int x = 0; x < gridSize.x; x++){ + for(int y = 0; y < gridSize.y; y++){ // for each column + Vector2i curr = new Vector2i(x, y); + if(getCell(curr).getTileType() == TileType.HatOrPurse){ + clearCell(curr, false, true); + } else { + break; + } + } + } + + fillGaps(); + levelObjectives.trackEggsAndClothing(); + } + + public void fillGaps(){ // called whenever tiles have been removed, it shifts down tiles suspended midair and generates new ones + for(int x = 0; x < gridSize.x; x++){ + int noGapsInColumn = 0; + for(int y = 0; y < gridSize.y; y++){ // for each column + GridCell currCell = getCell(new Vector2i(x, y)); + if(currCell.TileType == TileType.None && currCell.Booster == BoosterType.None){ + noGapsInColumn++; + } else { + setCell(new Vector2i(x, y - noGapsInColumn), currCell); // move all the tiles down to eliminate gaps + } + } + for(int y = gridSize.y; y >= gridSize.y-noGapsInColumn; y--){ + setCell(new Vector2i(x, y), new GridCell(0, spawnTiles.get(randomizer.nextInt(spawnTiles.size())), BoosterType.None)); // now fill the gaps + } + } + } + + + + // functions related to responding to a move from the player + + // first function called by the move command handling function + public void calculateMove(Vector2i pos1, Vector2i pos2){ + + moveCount++; + levelObjectives.trackMoves(); + + matchComboScore = 0; + + if(pos2.y != -1){ // if given two tiles as input + + GridCell cell1 = getCell(pos1); // swap the tiles + setCell(pos1, getCell(pos2)); + setCell(pos2, cell1); + +/* + while(findAndClearMatches(pos1, pos2)){ // for as long as there are still matches on the game board + + fillGaps(); + clearHats(); + + } +*/ + floodFillClearVisited(); // reset the visited array + + // Guarantee pos1 to have a more powerful or as powerful booster as pos2 + if(getCell(pos2).getBooster().ordinal() > getCell(pos1).getBooster().ordinal()){ + Vector2i temp = pos1; + pos1 = pos2; + pos2 = temp; + } + + if(getCell(pos1).isBoosted() && getCell(pos2).isBoosted()){ // booster combo behaviours + + if((getCell(pos2).getBooster() == BoosterType.BuzzyBirdHorizontal || + getCell(pos2).getBooster() == BoosterType.BuzzyBirdVertical) && + getCell(pos1).getBooster() == BoosterType.BoomBird){ // Buzzy bird and boom bird + + ComboBuzzyBoomBirdBehaviour(pos1, pos2); + + } else if (getCell(pos1).getBooster() == BoosterType.BoomBird && + getCell(pos2).getBooster() == BoosterType.BoomBird){ // Boom bird and boom bird + + ComboBoomBoomBirdBehaviour(pos1, pos2); + + } else if ((getCell(pos1).getBooster() == BoosterType.BuzzyBirdHorizontal || + getCell(pos1).getBooster() == BoosterType.BuzzyBirdVertical) && + (getCell(pos2).getBooster() == BoosterType.BuzzyBirdHorizontal || + getCell(pos2).getBooster() == BoosterType.BuzzyBirdVertical)){ // Buzzy bird and buzzy bird + + ComboBuzzyBuzzyBirdBehaviour(pos1, pos2); + + } else if (getCell(pos1).getBooster() == BoosterType.PrismPeacock && + (getCell(pos2).getBooster() == BoosterType.BuzzyBirdHorizontal || + getCell(pos2).getBooster() == BoosterType.BuzzyBirdVertical)){ // Prism peacock and buzzy bird + + ComboPrismBuzzyBirdBehaviour(pos1, pos2); + + } else if (getCell(pos1).getBooster() == BoosterType.PrismPeacock && + (getCell(pos2).getBooster() == BoosterType.BoomBird)){ // Prism peacock and boom bird + + ComboPrismBoomBirdBehaviour(pos1, pos2); + + } else if (getCell(pos1).getBooster() == BoosterType.PrismPeacock && + getCell(pos1).getBooster() == BoosterType.PrismPeacock){ // Prism peacock and prism peacock + + ComboPrismPrismPeacockBehaviour(pos1, pos2); + } + + } else if(getCell(pos1).isBoosted() && !getCell(pos2).isBoosted()){ // single booster behaviours + + switch (getCell(pos1).getBooster()) { + case BuzzyBirdHorizontal: + buzzyBirdHorizontalBehaviour(pos1, 1); + break; + case BuzzyBirdVertical: + buzzyBirdVerticalBehaviour(pos1, 1); + break; + case BoomBird: + boomBirdBehaviour(pos1, 3); + break; + case PrismPeacock: + prismPeacockBehaviour(pos1, getCell(pos2).getTileType()); + break; + default: + break; + } + } + + } else { // only one tile as input + + switch (getCell(pos1).getBooster()) { + case BuzzyBirdHorizontal: + buzzyBirdHorizontalBehaviour(pos1, 1); + break; + case BuzzyBirdVertical: + buzzyBirdVerticalBehaviour(pos1, 1); + break; + case BoomBird: + boomBirdBehaviour(pos1, 3); + break; + default: + break; + } + + } + + while(findAndClearMatches(pos1, pos2)){ // for as long as there are still matches on the game board + + fillGaps(); + clearHats(); + + } + + } + + // identifies continuous lines of tiles with the same color, main function called by calculateMove + public boolean findAndClearMatches(Vector2i pos1, Vector2i pos2){ // Returns true if a match is found. + + floodFillClearVisited(); + + boolean isMatch = false; + + Map matchType = new HashMap<>(); + + // find all vertical matches + for(int x = 0; x < gridSize.x; x++){ + int sameTiles = 1; + for(int y = 0; y < gridSize.y; y++){ // for each column + if(floodFillIsNeighbourCell(new Vector2i(x, y), new Vector2i(x, y+1), false)){ // keep track of number of tiles with the same tile type next to each other + sameTiles++; + } else if (sameTiles > 2) { + isMatch = true; + + for(int backtrack = y-sameTiles+1; backtrack <= y; backtrack++){ + Vector2i back = new Vector2i(x, backtrack); + + floodFillSetToVisit(back); + matchType.put(back, sameTiles); + } + sameTiles = 1; + } else { + sameTiles = 1; + } + } + } + + // find all horizontal matches + for(int y = 0; y < gridSize.y; y++){ // for each row + int sameTiles = 1; + for(int x = 0; x < gridSize.x; x++){ + if(floodFillIsNeighbourCell(new Vector2i(x+1, y), new Vector2i(x, y), false)){ // keep track of number of tiles with the same tile type next to each other + sameTiles++; + } else if (sameTiles > 2) { + isMatch = true; + + for(int backtrack = x-sameTiles+1; backtrack <= x; backtrack++){ + Vector2i back = new Vector2i(backtrack, y); + + floodFillSetToVisit(back); + matchType.put(back, sameTiles); + } + sameTiles = 1; + } else { + sameTiles = 1; + } + } + } + + // clear all matches + for(int x = 0; x < gridSize.x; x++){ + for(int y = 0; y < gridSize.y; y++){ + Vector2i curr = new Vector2i(x, y); + + if(floodFillGetVisited(curr)) { + floodFill(curr, matchType.get(curr), pos1, pos2); + } + } + } + + return isMatch; + } + + // a breadth-first search algorithm designed to clear matches and place a booster tile (doesn't always place in correct position) + public void floodFill(Vector2i pos, int matchType, Vector2i swap1, Vector2i swap2){ + + TileType refType = getCell(pos).getTileType(); + BoosterType refBooster = BoosterType.None; + + List connectedNodes = new LinkedList<>(); + Queue floodFillQueue = new LinkedList<>(); + floodFillQueue.add(pos); + + int matchTileSize = matchType; + + while(!floodFillQueue.isEmpty()){ + Vector2i curr = floodFillQueue.poll(); + + if(getCell(curr) == null) continue; + if(!floodFillGetVisited(curr)) continue; + if(getCell(pos).getTileType() != getCell(curr).getTileType()) continue; + //if(getCell(pos).getBooster() != getCell(curr).getBooster()) continue; + //if(getCell(pos).getHealth() > 0 && getCell(curr).getHealth() > 0) continue; + + floodFillSetVisited(curr); + connectedNodes.add(curr); + + floodFillQueue.add(new Vector2i(curr.x-1, curr.y)); + floodFillQueue.add(new Vector2i(curr.x+1, curr.y)); + floodFillQueue.add(new Vector2i(curr.x, curr.y-1)); + floodFillQueue.add(new Vector2i(curr.x, curr.y+1)); + + + // detect tiles connected to other tiles in an L or T shape + + int horizontalConnections = 0; + int verticalConnections = 0; + + if(floodFillIsNeighbourCell(curr, new Vector2i(curr.x-1, curr.y), true)) horizontalConnections++; + if(floodFillIsNeighbourCell(curr, new Vector2i(curr.x+1, curr.y), true)) horizontalConnections++; + if(floodFillIsNeighbourCell(curr, new Vector2i(curr.x, curr.y-1), true)) verticalConnections++; + if(floodFillIsNeighbourCell(curr, new Vector2i(curr.x, curr.y+1), true)) verticalConnections++; + + if(((horizontalConnections == 1 && verticalConnections == 1) || + (horizontalConnections == 2 && verticalConnections == 1) || + (horizontalConnections == 1 && verticalConnections == 2)) && + matchTileSize != 5) { // do not change to boom bird if a prism peacock would form + matchTileSize = 6; + } + } + + // keep track of puzzle objectives + if(getCell(pos).getTileType() != TileType.None){ + switch(matchTileSize) { + case 3: + scoreCombo(30); + break; + case 4: + refBooster = randomizer.nextInt(2) == 0 ? BoosterType.BuzzyBirdHorizontal : BoosterType.BuzzyBirdVertical; + scoreCombo(150); + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.MakeFlyers); + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.MakeFlyers_SingleGame); + break; + case 5: + refBooster = BoosterType.PrismPeacock; + scoreCombo(150); + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.MakePeacocks); + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.MakePeacocks_SingleGame); + break; + case 6: + refBooster = BoosterType.BoomBird; + scoreCombo(150); + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.MakeBombBirds); + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.MakeBombBirds_SingleGame); + break; + default: + break; + } + + if(matchTileSize > 3){ // I am not sure what type of goal "Hatch Powerups" is + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.HatchPowerups); + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.HatchPowerups_SingleGame); + } + } + + + + for(Vector2i node : connectedNodes){ + clearCell(node, false, false); + + breakEggTile(new Vector2i(node.x-1, node.y)); // break the eggs surrounding a match + breakEggTile(new Vector2i(node.x+1, node.y)); + breakEggTile(new Vector2i(node.x, node.y-1)); + breakEggTile(new Vector2i(node.x, node.y+1)); + } + + if(refBooster != BoosterType.None){ + + if(refBooster == BoosterType.PrismPeacock){ + refType = TileType.None; + } + + if(connectedNodes.contains(swap1)){ + setCell(swap1, new GridCell(0, refType, refBooster)); + } else if(connectedNodes.contains(swap2)){ + setCell(swap2, new GridCell(0, refType, refBooster)); + } else { + setCell(pos, new GridCell(0, refType, refBooster)); + } + } + + } + + // flood fill helper functions + public boolean floodFillIsNeighbourCell(Vector2i pos1, Vector2i pos2, boolean checkMatch){ + GridCell cell1 = getCell(pos1); + GridCell cell2 = getCell(pos2); + + if(cell1 == null || cell2 == null || cell1.getTileType() == TileType.HatOrPurse || cell2.getTileType() == TileType.HatOrPurse){ + return false; + } else if (cell1.getTileType() == cell2.getTileType()){ + //!cell1.isBoosted() && !cell2.isBoosted() && + //cell1.getHealth() == 0 && cell1.getHealth() == 0 + + return checkMatch ? floodFillGetToVisit(pos2) && floodFillGetToVisit(pos2) : true; + } + + return false; + } + + // false = don't visit this cell/cell has been visited + // true = visit this cell/cell has not been visited + public void floodFillSetToVisit(Vector2i pos){ + toVisit[pos.x][pos.y] = true; + visited[pos.x][pos.y] = true; + } + + public boolean floodFillGetToVisit(Vector2i pos){ // also returns true if uninitialized + if (pos.x >= 0 && pos.x < gridSize.x && pos.y >= 0 && pos.y < gridSize.y) + { + return toVisit[pos.x][pos.y]; + } + return false; + } + + public void floodFillSetVisited(Vector2i pos){ + visited[pos.x][pos.y] = false; + } + + public boolean floodFillGetVisited(Vector2i pos){ + if (pos.x >= 0 && pos.x < gridSize.x && pos.y >= 0 && pos.y < gridSize.y) + { + return visited[pos.x][pos.y]; + } + return false; + } + + public void floodFillClearVisited(){ + toVisit = new boolean[gridSize.x][gridSize.y]; + visited = new boolean[gridSize.x][gridSize.y]; + } + + + + // functions that define booster tile behaviours, as in how they clear surrounding tiles + + // template function to avoid errors + private void buzzyBirdHorizontalBehaviour(Vector2i pos, int size){ + buzzyBirdHorizontalBehaviour(pos, size, false); + } + + private void buzzyBirdHorizontalBehaviour(Vector2i pos, int size, boolean isClearedByPeacock) { + + clearCell(pos, false, true); + + int d = Math.floorDiv(size, 2); + + for(int y = pos.y-d; y <= pos.y+d; y++){ + for(int x = 0; x < gridSize.x; x++){ + Vector2i curr = new Vector2i(x, y); + if(getCell(curr) != null){ + if(getCell(curr).getBooster() != BoosterType.BuzzyBirdHorizontal){ // two buzzy birds in the same direction results in an X instead + clearCell(curr, true, false, isClearedByPeacock); + } else { + clearCell(curr, true, true, isClearedByPeacock); + buzzyBirdVerticalBehaviour(curr, 1); + } + } + } + } + fillGaps(); + } + + private void buzzyBirdVerticalBehaviour(Vector2i pos, int size){ + buzzyBirdVerticalBehaviour(pos, size, false); + } + + private void buzzyBirdVerticalBehaviour(Vector2i pos, int size, boolean isClearedByPeacock) { + + clearCell(pos, false, true); + + int d = Math.floorDiv(size, 2); + + for(int x = pos.x-d; x <= pos.x+d; x++){ + for(int y = 0; y < gridSize.y; y++){ + Vector2i curr = new Vector2i(x, y); + if(getCell(curr) != null){ + if(getCell(curr).getBooster() != BoosterType.BuzzyBirdVertical){ + clearCell(curr, true, false, isClearedByPeacock); + } else { + clearCell(curr, true, true, isClearedByPeacock); + buzzyBirdHorizontalBehaviour(curr, 1); + } + } + } + } + fillGaps(); + } + + private void boomBirdBehaviour(Vector2i pos, int size) { + + clearCell(pos, false, true); // sometimes the original booster needs to be removed to prevent an infinte loop + + int d = Math.floorDiv(size, 2); + + for(int x = pos.x-d; x <= pos.x+d; x++){ + for(int y = pos.y-d; y <= pos.y+d; y++){ + + Vector2i curr = new Vector2i(x, y); + + if(x != pos.x && y != pos.y) { + clearCell(curr, true, false); + } + } + } + fillGaps(); + for(int x = pos.x-d; x <= pos.x+d; x++){ + for(int y = pos.y-d-1; y <= pos.y+d-1; y++){ + Vector2i curr = new Vector2i(x, y); + clearCell(curr, true, false); + } + } + fillGaps(); + } + + private void prismPeacockBehaviour(Vector2i pos, TileType refType) { + + clearCell(pos, false, true); + + for(int y = 0; y < gridSize.y; y++){ + for(int x = 0; x < gridSize.x; x++){ + Vector2i curr = new Vector2i(x, y); + GridCell currCell = getCell(curr); + + if(currCell.TileType == refType && !currCell.isBoosted()) { + clearCell(curr, true, false, true); + } + } + } + fillGaps(); + } + + private void ComboPrismPrismPeacockBehaviour(Vector2i pos1, Vector2i pos2) { + clearCell(pos1, false, true); + clearCell(pos2, false, true); + + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.ComboPeacockPeacock); + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.ComboPeacockPeacock_SingleGame); + + for(int x = 0; x < gridSize.x; x++){ + for(int y = 0; y < gridSize.y; y++){ + Vector2i curr = new Vector2i(x, y); + clearCell(curr, true, false, true); + } + } + + fillGaps(); + } + + private void ComboPrismBoomBirdBehaviour(Vector2i pos1, Vector2i pos2) { + TileType refType = getCell(pos2).TileType; + + clearCell(pos1, false, true); + clearCell(pos2, true, true); + + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.ComboBombPeacock); + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.ComboBombPeacock_SingleGame); + + List newBoomBirds = new ArrayList<>(); + + for(int x = 0; x < gridSize.x; x++){ + for(int y = 0; y < gridSize.y; y++){ + Vector2i curr = new Vector2i(x, y); + GridCell currCell = getCell(curr); + + if(currCell.getTileType() == refType && !currCell.isBoosted()) { + currCell.setBooster(BoosterType.BoomBird); // presumably no need to check for tile health + setCell(curr, currCell); + newBoomBirds.add(curr); + } + } + } + + int d = Math.floorDiv(3, 2); + + for (Vector2i pos : newBoomBirds) { // first explosions + + for(int x = pos.x-d; x <= pos.x+d; x++){ + for(int y = pos.y-d; y <= pos.y+d; y++){ + + Vector2i curr = new Vector2i(x, y); + + if(getCell(curr) != null && !newBoomBirds.contains(curr) && getCell(curr).getTileType() != TileType.None) { + clearCell(curr, true, false, true); + } + } + } + } + fillGaps(); + + newBoomBirds.clear(); + + for(int x = 0; x < gridSize.x; x++){ + for(int y = 0; y < gridSize.y; y++){ + Vector2i curr = new Vector2i(x, y); + GridCell currCell = getCell(curr); + + if(currCell.getTileType() == refType && currCell.getBooster() == BoosterType.BoomBird) { + newBoomBirds.add(curr); + } + } + } + + for (Vector2i pos : newBoomBirds) { // second explosions + + for(int x = pos.x-d; x <= pos.x+d; x++){ + for(int y = pos.y-d; y <= pos.y+d; y++){ + + Vector2i curr = new Vector2i(x, y); + clearCell(curr, true, true, true); + } + } + } + + fillGaps(); + } + + private void ComboPrismBuzzyBirdBehaviour(Vector2i pos1, Vector2i pos2) { + TileType refType = getCell(pos2).TileType; + + clearCell(pos1, false, true); + clearCell(pos2, true, true); + + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.ComboFlyerPeacock); + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.ComboFlyerPeacock_SingleGame); + + List newBuzzyBirds = new ArrayList<>(); + + for(int x = 0; x < gridSize.x; x++){ + for(int y = 0; y < gridSize.y; y++){ + Vector2i curr = new Vector2i(x, y); + + if(getCell(curr).getTileType() == refType && !getCell(curr).isBoosted()) { + newBuzzyBirds.add(curr); + } + } + } + + for (Vector2i newBuzzyBird : newBuzzyBirds) { + boolean coinflip = randomizer.nextInt(2) == 1; + + if(coinflip) { + buzzyBirdHorizontalBehaviour(newBuzzyBird, 1, true); + } else { + buzzyBirdVerticalBehaviour(newBuzzyBird, 1, true); + } + } + } + + private void ComboBuzzyBuzzyBirdBehaviour(Vector2i pos1, Vector2i pos2) { + clearCell(pos1, false, true); + clearCell(pos2, true, true); + + buzzyBirdHorizontalBehaviour(pos1, 1); + buzzyBirdVerticalBehaviour(pos1, 1); + + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.ComboFlyerFlyer); + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.ComboFlyerFlyer_SingleGame); + } + + private void ComboBoomBoomBirdBehaviour(Vector2i pos1, Vector2i pos2) { + clearCell(pos1, false, true); + clearCell(pos2, true, true); + + boomBirdBehaviour(pos1, 5); + + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.ComboBombBomb); + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.ComboBombBomb_SingleGame); + } + + private void ComboBuzzyBoomBirdBehaviour(Vector2i pos1, Vector2i pos2) { + clearCell(pos1, false, true); + clearCell(pos2, true, true); + + buzzyBirdHorizontalBehaviour(pos1, 3); + buzzyBirdVerticalBehaviour(pos1, 3); + + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.ComboFlyerBomb); + puzzleObjectives.incrementPuzzleTemp(PuzzleObjectiveType.ComboFlyerBomb_SingleGame); + } + + + // functions related to level objectives, or call functions in the level objectives class + + private void scoreCombo(int scoreIncrease) { + matchComboScore += scoreIncrease; + + dizzyBirdMeter += scoreIncrease / 30; + score += matchComboScore; + levelObjectives.addScore(matchComboScore); + puzzleObjectives.addScore(matchComboScore); + } + + public boolean isNextLevel(){ + return levelObjectives.isNextLevel(); + } + + public List getObjectiveProgress(){ // called externally when building a syncClient packet + levelObjectives.trackEggsAndClothing(); + List progress = new ArrayList<>(); + for(LevelObjectiveType objectiveType : LevelObjectiveType.values()){ + int[] objectiveData = levelObjectives.objectivesTracker[objectiveType.ordinal()]; + if(objectiveData[0] != -1){ + progress.add(objectiveData); + } + } + return progress; + } + + public boolean hasRunOutOfMoves(){ + return levelObjectives.hasRunOutOfMoves(); + } + + + + + // functions related to updating the board when a syncClient message is sent + + } + + public class PuzzleObjectives { + + private int puzzleCurrentProgress[]; + private int addToTotalScore; + + public PuzzleObjectives(){ + puzzleCurrentProgress = new int[100]; + addToTotalScore = 0; + } + + // functions keeping track of changes to be made to the player inventory in relation to puzzle objectives + + public void incrementPuzzleTemp(PuzzleObjectiveType puzzle){ + puzzleCurrentProgress[puzzle.getVal()]++; + } + + public void setPuzzleTemp(PuzzleObjectiveType puzzle, int value){ + puzzleCurrentProgress[puzzle.getVal()] = value; + } + + public void clearPuzzleTemp(PuzzleObjectiveType puzzle){ + puzzleCurrentProgress[puzzle.getVal()] = 0; + } + + public int getPuzzleTemp(PuzzleObjectiveType puzzle){ + return puzzleCurrentProgress[puzzle.getVal()]; + } + + + + // functions related to saving puzzle objectives data to the player inventory. + + private int puzzleTypeToUserVarIndex(PuzzleObjectiveType puzzle){ + for(JsonElement ele : achievementToUserVarIndexList){ + JsonObject dataPair = (JsonObject) ele; + if(dataPair.get("achievementType").getAsInt() == puzzle.getVal()){ + return dataPair.get("userVarIndex").getAsInt(); + } + } + return -1; + } + + private void setPuzzleObjectiveUserVar(Player player, PuzzleObjectiveType puzzle, int value){ + player.account.getSaveSpecificInventory().getUserVarAccesor().setPlayerVarValue(UserVarIDs.persistentAchievementDataUserVarDefId.getVal(), puzzleTypeToUserVarIndex(puzzle), value); + } + + private int getPuzzleObjectiveUserVar(Player player, PuzzleObjectiveType puzzle){ + + UserVarValue value = player.account.getSaveSpecificInventory().getUserVarAccesor().getPlayerVarValue(UserVarIDs.persistentAchievementDataUserVarDefId.getVal(), puzzle.getVal()); + if(value == null){ + setPuzzleObjectiveUserVar(player, puzzle, 0); + return 0; + } else { + return player.account.getSaveSpecificInventory().getUserVarAccesor().getPlayerVarValue(UserVarIDs.persistentAchievementDataUserVarDefId.getVal(), puzzle.getVal()).value; + } + } + + // these are called when the game starts and ends. + + private void saveSavedGameUserVar(Player player) { + + setPuzzleTemp(PuzzleObjectiveType.HighScore, score); + setPuzzleTemp(PuzzleObjectiveType.TotalScore, score); + + // write into the player inventory the number of moves made in the level, send inventory item packet + player.account.getSaveSpecificInventory().getUserVarAccesor().setPlayerVarValue(UserVarIDs.savedGameUserVarDefId.getVal(), level, moveCount); + + // in case no puzzle objective has ever been written to the player inventory + if(player.account.getSaveSpecificInventory().getUserVarAccesor().getPlayerVarValue( + UserVarIDs.persistentAchievementDataUserVarDefId.getVal()) == null){ + + for(int i = 0; i < PuzzleObjectiveType.values().length; i++){ + player.account.getSaveSpecificInventory().getUserVarAccesor().setPlayerVarValue( + UserVarIDs.persistentAchievementDataUserVarDefId.getVal(), i, 0); + } + } + + // write into the player inventory the puzzle objectives + for(PuzzleObjectiveType puzzleType : PuzzleObjectiveType.values()){ + if(getPuzzleTemp(puzzleType) > 0){ + Centuria.logger.debug(puzzleType.toString(), Integer.toString(getPuzzleTemp(puzzleType))); + } + } + for(PuzzleObjectiveType puzzleType : PuzzleObjectiveType.values()){ + if(puzzleType.toString().contains("SingleGame") || + puzzleType == PuzzleObjectiveType.HighScore){ // highest in a game + + setPuzzleObjectiveUserVar(player, puzzleType, + Math.max(getPuzzleTemp(puzzleType), + getPuzzleObjectiveUserVar(player, puzzleType))); + } else if (puzzleType != PuzzleObjectiveType.TotalScore){ // cumulative, total score handled in resetSavedGameUserVar + setPuzzleObjectiveUserVar(player, puzzleType, + getPuzzleTemp(puzzleType) + + getPuzzleObjectiveUserVar(player, puzzleType)); + clearPuzzleTemp(puzzleType); + } + } + setPuzzleObjectiveUserVar(player, PuzzleObjectiveType.TotalScore, getPuzzleObjectiveUserVar(player, PuzzleObjectiveType.TotalScore) + addToTotalScore); + addToTotalScore = 0; + } + + public void addScore(int value){ + addToTotalScore += value; + } + + private void resetSavedGameUserVar(Player player) { + + + // reset these values as a new game has been started + level = 0; + score = 0; + moveCount = 0; + dizzyBirdMeter = 0; + + + // initialise user vars + player.account.getSaveSpecificInventory().getUserVarAccesor().setPlayerVarValue(UserVarIDs.savedGameUserVarDefId.getVal(), 0, 1); + player.account.getSaveSpecificInventory().getUserVarAccesor().setPlayerVarValue(UserVarIDs.tutorial.getVal(), 0, 1); + } + + private void loadSavedGameUserVar(Player player) { // the game only keeps track of the highest score ever and I'm not sure what the continue game button should do + + // retrieve the data from the player inventory + UserVarValue[] savedGame = player.account.getSaveSpecificInventory().getUserVarAccesor().getPlayerVarValue(UserVarIDs.savedGameUserVarDefId.getVal()); + int prevScore = getPuzzleObjectiveUserVar(player, PuzzleObjectiveType.HighScore); + + // load in the values associated with a previous game + if(savedGame != null){ + level = Math.max(1, savedGame.length-1); + score = prevScore; + moveCount = savedGame[savedGame.length-1].value; + dizzyBirdMeter = 0; + } + } + + } + + + // functions that implement the game's protocols. + + @Override + public AbstractMinigame instantiate() { + return new GameDizzywingDispatch(); + } + + @Override + public boolean canHandle(int levelID) { + return levelID == 8192; + } + + @Override + public void onJoin(Player plr) { + } + + @Override + public void onExit(Player plr) { + } + + @MinigameMessage("startGame") + public void startGame(Player player, XtReader rd){ + + // save score and number of moves made on this level + saveGame(player, rd); + + puzzleObjectives = new PuzzleObjectives(); + puzzleObjectives.resetSavedGameUserVar(player); + + // The GUID is used as the seed for the random number generator. + currentGameUUID = UUID.randomUUID().toString(); + gameState = new GameState(player); + + // the format of the minigame message response packet + XtWriter mmData = new XtWriter(); + mmData.writeString(currentGameUUID); + mmData.writeInt(gameState.calculateBoardChecksum()); + mmData.writeInt(level); + mmData.writeInt(0); // not sure + mmData.writeInt(score); + mmData.writeInt(0); // not sure + + // send response packet + MinigameMessagePacket mm = new MinigameMessagePacket(); + mm.command = "startGame"; + mm.data = mmData.encode().substring(4); + player.client.sendPacket(mm); + + // load in server-generated game board + syncClient(player, rd); + + } + + @MinigameMessage("move") + public void move(Player player, XtReader rd) { + + int clientChecksum = rd.readInt(); + + // process move sent by client + gameState.calculateMove(new Vector2i(rd.readInt(), rd.readInt()), new Vector2i(rd.readInt(), rd.readInt())); + // sync client if sever checksum differs from client checksum (always occurs at current) + if (clientChecksum != gameState.calculateBoardChecksum()) { + syncClient(player, rd); + } + + saveGame(player, rd); + + // check if level should increase + if (gameState.isNextLevel()) { + moveCount = 0; + goToLevel(player); + } + + } + + private void goToLevel(Player player) { + + // packet data + XtWriter mmData = new XtWriter(); + mmData.writeInt(level); + mmData.writeInt(0); // not sure + mmData.writeInt(score); + mmData.writeInt(0); // not sure + + // send packet + MinigameMessagePacket mm = new MinigameMessagePacket(); + mm.command = "goToLevel"; + mm.data = mmData.encode().substring(4); + player.client.sendPacket(mm); + + } + + @MinigameMessage("dizzyBird") + public void dizzyBird(Player player, XtReader rd) { + dizzyBirdMeter = 0; // reset the meter at the right-hand side of the screen + gameState.scrambleTiles(); + syncClient(player, rd); + } + + @MinigameMessage("continueGame") + public void continueGame(Player player, XtReader rd) { + + // send start game client with previous values + puzzleObjectives = new PuzzleObjectives(); + puzzleObjectives.loadSavedGameUserVar(player); + + // The GUID is used as the seed for the random number generator. + currentGameUUID = UUID.randomUUID().toString(); + gameState = new GameState(player); + + // the format of the minigame message response packet + XtWriter mmData = new XtWriter(); + mmData.writeString(currentGameUUID); + mmData.writeInt(gameState.calculateBoardChecksum()); + // it seems the game cannot start unless these specific values are used + mmData.writeInt(1); + mmData.writeInt(0); + mmData.writeInt(500); + mmData.writeInt(0); + + // send response packet + MinigameMessagePacket mm = new MinigameMessagePacket(); + mm.command = "startGame"; + mm.data = mmData.encode().substring(4); + player.client.sendPacket(mm); + + // load in server-generated game board + syncClient(player, rd); + } + + + @MinigameMessage("saveGame") + public void saveGame(Player player, XtReader rd) { + + if(gameState != null){ + + puzzleObjectives.saveSavedGameUserVar(player); + + if (player.client != null && player.client.isConnected()) { + // Send to client + InventoryItemPacket pk = new InventoryItemPacket(); + pk.item = player.account.getSaveSpecificInventory().getItem(Integer.toString(UserVarIDs.userVarInventory.getVal())); + player.client.sendPacket(pk); + } + } + } + + + + @MinigameMessage("redeemPiece") + public void redeemPiece(Player player, XtReader rd) { // need to keep track of what has already been redeemed + int packetPaintingIndex = rd.readInt(); // 0 - 6 + int packetPieceIndex = rd.readInt(); // 0 - 16 + int packetOverallIndex = packetPaintingIndex*16+packetPieceIndex; + + Centuria.logger.debug("Painting " + Integer.toString(packetPaintingIndex) + " Piece " + Integer.toString(packetPieceIndex) + " Index " + Integer.toString(packetOverallIndex)); + + // if no puzzle piece data has ever been written into the player inventory + if(player.account.getSaveSpecificInventory().getUserVarAccesor().getPlayerVarValue( + UserVarIDs.puzzlePieceRedemptionStatusUserVarDefId.getVal()) == null){ + + for(int i = 0; i < 6*16; i++){ + player.account.getSaveSpecificInventory().getUserVarAccesor().setPlayerVarValue( + UserVarIDs.puzzlePieceRedemptionStatusUserVarDefId.getVal(), i, 0); + } + } + if(player.account.getSaveSpecificInventory().getUserVarAccesor().getPlayerVarValue( + UserVarIDs.puzzleRedemptionStatusUserVarDefId.getVal()) == null){ + + for(int i = 0; i < 6; i++){ + player.account.getSaveSpecificInventory().getUserVarAccesor().setPlayerVarValue( + UserVarIDs.puzzleRedemptionStatusUserVarDefId.getVal(), i, 0); + } + } + + // write the piece data sent by the client packet into the player inventory + player.account.getSaveSpecificInventory().getUserVarAccesor().setPlayerVarValue( + UserVarIDs.puzzlePieceRedemptionStatusUserVarDefId.getVal(), packetOverallIndex, 1); + + // loop through all pieces to see if a painting should be given to the player + for(int currPaintingIndex = 0; currPaintingIndex < 6; currPaintingIndex++){ + + Boolean fullPainting = true; + for(int currPieceIndex = 0; currPieceIndex < 16; currPieceIndex++){ + + UserVarValue puzzleRedemptionStatus = player.account.getSaveSpecificInventory().getUserVarAccesor().getPlayerVarValue( + UserVarIDs.puzzlePieceRedemptionStatusUserVarDefId.getVal(), currPaintingIndex*16+currPieceIndex); + fullPainting &= (puzzleRedemptionStatus != null && puzzleRedemptionStatus.value == 1); + } + + UserVarValue paintingRedemptionStatus = player.account.getSaveSpecificInventory().getUserVarAccesor().getPlayerVarValue( + UserVarIDs.puzzleRedemptionStatusUserVarDefId.getVal(), currPaintingIndex); + if(fullPainting && paintingRedemptionStatus.value == 0){ + + // give the painting + + Integer rewardID = puzzleRewards.get(currPaintingIndex).getAsInt(); + + // give player the item + player.account.getSaveSpecificInventory().getItemAccessor(player) + .add(rewardID, 1); + + // send player a notification + MinigamePrizePacket p1 = new MinigamePrizePacket(); + p1.given = true; + p1.itemDefId = Integer.toString(rewardID); + p1.itemCount = 1; + p1.prizeIndex1 = 1; + p1.prizeIndex2 = 0; + player.client.sendPacket(p1); + + player.account.getSaveSpecificInventory().getUserVarAccesor().setPlayerVarValue( + UserVarIDs.puzzleRedemptionStatusUserVarDefId.getVal(), currPaintingIndex, 1); + } + } + if (player.client != null && player.client.isConnected()) { + // Send to client + InventoryItemPacket pk = new InventoryItemPacket(); + pk.item = player.account.getSaveSpecificInventory().getItem(Integer.toString( + UserVarIDs.userVarInventory.getVal())); + player.client.sendPacket(pk); + } + } + + @MinigameMessage("syncClient") + public void syncClient(Player player, XtReader rd) { + XtWriter mmData = new XtWriter(); + mmData.writeInt(moveCount); + mmData.writeInt(level); + mmData.writeInt(score); + mmData.writeInt(dizzyBirdMeter); + mmData.writeString(gameState.toBase64String()); + + + List objectiveProgress = gameState.getObjectiveProgress(); + + if(!gameState.hasRunOutOfMoves()){ + mmData.writeInt(objectiveProgress.size()); + for(int[] objectiveData : objectiveProgress){ + mmData.writeInt(objectiveData[2]); // objective type + mmData.writeInt(objectiveData[0]); // objective requirement + mmData.writeInt(objectiveData[1]); // objective current progress + } + } else { + mmData.writeInt(2); + + mmData.writeInt(objectiveProgress.get(0)[2]); + mmData.writeInt(objectiveProgress.get(0)[0]); + mmData.writeInt(objectiveProgress.get(0)[1]); + + mmData.writeInt(1); + mmData.writeInt(-1); + mmData.writeInt(-1); + } + + MinigameMessagePacket mm = new MinigameMessagePacket(); + mm.command = "syncClient"; + mm.data = mmData.encode().substring(4); + player.client.sendPacket(mm); + } + +} diff --git a/src/main/java/org/asf/centuria/minigames/games/GameKinoParlor.java b/src/main/java/org/asf/centuria/minigames/games/GameKinoParlor.java new file mode 100644 index 00000000..66519801 --- /dev/null +++ b/src/main/java/org/asf/centuria/minigames/games/GameKinoParlor.java @@ -0,0 +1,1217 @@ +package org.asf.centuria.minigames.games; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.BitSet; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import org.asf.centuria.Centuria; +import org.asf.centuria.accounts.highlevel.ItemAccessor; +import org.asf.centuria.data.XtReader; +import org.asf.centuria.data.XtWriter; +import org.asf.centuria.entities.players.Player; +import org.asf.centuria.levelevents.LevelEvent; +import org.asf.centuria.levelevents.LevelEventBus; +import org.asf.centuria.minigames.AbstractMinigame; +import org.asf.centuria.minigames.MinigameMessage; +import org.asf.centuria.packets.xt.gameserver.minigame.MinigameMessagePacket; + +public class GameKinoParlor extends AbstractMinigame { + + private enum GameType { + QueensDuel, FourCrows, MothsAndFlames, LunarPhases + } + + Random random; + GameType gameType; + int wager; + boolean doubleUpTaken; + List additionalParameters; + static final int likesID = 2327; + + QueensDuel queensDuel; + FourCrows fourCrows; + MothsAndFlames mothsAndFlames; + LunarPhases lunarPhases; + + public GameKinoParlor() { + random = new Random(); + additionalParameters = new ArrayList<>(); + + queensDuel = new QueensDuel(); + fourCrows = new FourCrows(); + mothsAndFlames = new MothsAndFlames(); + lunarPhases = new LunarPhases(); + } + + private void sendGameCommand(String command, String argument, Player player) { + String[] oneElement = new String[1]; + oneElement[0] = argument; + sendGameCommand(command, oneElement, player); + } + + private void sendGameCommand(String command, String[] arguments, Player player) { + if (command == "multiplierResults") { + ItemAccessor acc = player.account.getSaveSpecificInventory().getItemAccessor(player); + acc.add(likesID, wager * Integer.parseInt(arguments[0])); + + LevelEventBus.dispatch( + new LevelEvent("levelevents.minigames.kinoparlor", new String[] { "won a game" }, player)); + } + + Centuria.logger.debug("from server - " + command); + XtWriter xw = new XtWriter(); + + for (String argument : arguments) { + xw.writeString(argument); + Centuria.logger.debug("from server - " + argument); + } + + MinigameMessagePacket p = new MinigameMessagePacket(); + p.command = command; + p.data = xw.encode().substring(4); + player.client.sendPacket(p); + } + + @Override + public AbstractMinigame instantiate() { + return new GameKinoParlor(); + } + + @Override + public boolean canHandle(int levelID) { + return levelID == 7789 || levelID == 12174; + } + + @Override + public void onJoin(Player player) { + } + + @Override + public void onExit(Player player) { + } + + @MinigameMessage("loadGame") + public void loadGame(Player player, XtReader rd) { + gameType = GameType.values()[rd.readInt() - 1]; + Centuria.logger.debug(gameType.toString()); + + int maximumWager = 1; + + // generate additional parameters + switch (gameType) { + case QueensDuel: + additionalParameters = queensDuel.getParams(); + maximumWager = 100; + break; + case FourCrows: + additionalParameters = fourCrows.getParams(); + maximumWager = 200; + break; + case MothsAndFlames: + additionalParameters = mothsAndFlames.getParams(); + maximumWager = 100; + break; + case LunarPhases: + additionalParameters = lunarPhases.getParams(); + maximumWager = 200; + break; + default: + break; + } + + // load game + List loadGameArgs = new ArrayList<>(); + loadGameArgs.add(maximumWager); + loadGameArgs.add(additionalParameters.size()); + loadGameArgs.addAll(additionalParameters); + List loadGameArgsStrings = loadGameArgs.stream().map(Object::toString) + .collect(Collectors.toUnmodifiableList()); + + sendGameCommand("loadGame", loadGameArgsStrings.toArray(new String[0]), player); + } + + @MinigameMessage("leaveGame") + public void leaveGame(Player player, XtReader rd) { + additionalParameters = new ArrayList<>(); + + queensDuel = new QueensDuel(); + fourCrows = new FourCrows(); + mothsAndFlames = new MothsAndFlames(); + lunarPhases = new LunarPhases(); + } + + @MinigameMessage("placeWager") + public void placeWager(Player player, XtReader rd) { + + wager = rd.readInt(); + + ItemAccessor acc = player.account.getSaveSpecificInventory().getItemAccessor(player); + acc.remove(likesID, wager); + + // accept wager + sendGameCommand("wagerAccepted", String.valueOf(wager), player); + } + + @MinigameMessage("doubleUp") + public void doubleUp(Player player, XtReader rd) { + doubleUpTaken = rd.readInt() == 1; + if (doubleUpTaken) { + wager *= 2; + sendGameCommand("doubleUpResults", String.valueOf(1), player); + } else { + LevelEventBus.dispatch( + new LevelEvent("levelevents.minigames.kinoparlor", new String[] { "won a game" }, player)); + + ItemAccessor acc = player.account.getSaveSpecificInventory().getItemAccessor(player); + acc.add(likesID, wager * 2); + sendGameCommand("doubleUpResults", String.valueOf(0), player); + } + } + + /* + * @MinigameMessage("multiplier") public void multiplier(Player player, XtReader + * rd){ multiplier = rd.readInt(); sendGameCommand("multiplierResults", + * String.valueOf(multiplier), player); } + */ + @MinigameMessage("gameCommand") + public void gameCommand(Player player, XtReader rd) { + + String command = rd.read(); + String[] gameCommandParameters = rd.readRemaining().split("%"); + + Centuria.logger.debug(command); + for (String parameter : gameCommandParameters) { + Centuria.logger.debug(parameter); + } + + switch (gameType) { + case QueensDuel: + queensDuel.command(player, rd, command, gameCommandParameters); + break; + case FourCrows: + fourCrows.command(player, rd, command, gameCommandParameters); + break; + case MothsAndFlames: + mothsAndFlames.command(player, rd, command, gameCommandParameters); + break; + case LunarPhases: + lunarPhases.command(player, rd, command, gameCommandParameters); + break; + default: + break; + } + } + + private class QueensDuel { + + int[] playersRoll; + int[] kinosRoll; + int[][] kinosLanes; + + int turn; + int tieCount; + int winCount; + int loseCount; + + static QueensDuelDiceFace[] diceFaces = { QueensDuelDiceFace.Sword, QueensDuelDiceFace.Mask, + QueensDuelDiceFace.Helmet, QueensDuelDiceFace.Mask, QueensDuelDiceFace.Crown, + QueensDuelDiceFace.Sword }; + + static final float[] binomcdf = { 0.0156f, 0.1094f, 0.3438f, 0.6562f, 0.8906f, 0.9844f, 1f }; // binomial + // cumulative + // distribution + // function, 6 + // trials, + // probability + // 0.5 + + private enum QueensDuelDiceFace { + Sword, Mask, Helmet, Crown + } + + public QueensDuel() { + playersRoll = new int[6]; // each element is a digit representing a dice face + kinosRoll = new int[6]; + kinosLanes = new int[][] { { 9, 9, 9, 9 }, { 9, 9, 9, 9 }, { 9, 9, 9, 9 } }; // each element is a dice from + // the roll + turn = 0; + tieCount = 0; + winCount = 0; + loseCount = 0; + } + + private List getParams() { + return Arrays.asList(0); + } + + private int getDiceFaceScore(QueensDuelDiceFace face) { + switch (face) { + case Crown: + return 3; + case Helmet: + return 2; + case Mask: + return 1; + case Sword: + return 1; + default: + return 0; + } + } + + private int getLaneScore(int[] lane, int[] rolls) { + List laneToFaces = new ArrayList<>(); + for (int face : lane) { + if (face != 9) { + laneToFaces.add(diceFaces[rolls[face] - 1]); + } + } + + return getLaneScore(laneToFaces); + } + + private int getLaneScore(List lane) { + int score = 0; + for (QueensDuelDiceFace face : lane) { + score += getDiceFaceScore(face); + } + return score; + } + + private float getBinomcdfDiff(List lane, QueensDuelDiceFace newFace) { + int prevScore = getLaneScore(lane); + int newScore = prevScore + getDiceFaceScore(newFace); + + return binomcdf[newScore] - binomcdf[prevScore]; + } + + private boolean canAddToLane(List lane, QueensDuelDiceFace newFace) { + if (newFace != QueensDuelDiceFace.Sword) { + return !(lane.contains(QueensDuelDiceFace.Crown) || lane.contains(QueensDuelDiceFace.Helmet) + || lane.contains(QueensDuelDiceFace.Mask)); + } else { + return !lane.isEmpty() && lane.size() < 4; + } + } + + private float absoluteWinRate(int[] rolls) { + List> bestLanes = bestLaneConfiguration(rolls); + + float totalWinRate = 0; + for (List bestLane : bestLanes) { + totalWinRate += binomcdf[getLaneScore(bestLane)]; + } + + return totalWinRate / bestLanes.size(); + } + + private List> bestLaneConfiguration(int[] rolls) { + List rolledFaces = new ArrayList<>(); + for (int roll : rolls) { + rolledFaces.add(diceFaces[roll - 1]); + } + + List> lanes = new ArrayList<>(); + lanes.add(new ArrayList<>()); + lanes.add(new ArrayList<>()); + lanes.add(new ArrayList<>()); + + while (true) { + + float highestBinomcdfDiff = -1f; + int faceIndex = -1; + int laneIndex = -1; + + boolean allDiceDeployed = true; + + for (int f = 0; f < rolledFaces.size(); f++) { + for (int l = 0; l < lanes.size(); l++) { + + List lane = lanes.get(l); + QueensDuelDiceFace rolledFace = rolledFaces.get(f); + + if (rolledFace != null && canAddToLane(lane, rolledFace) + && getBinomcdfDiff(lane, rolledFace) > highestBinomcdfDiff) { + + highestBinomcdfDiff = getBinomcdfDiff(lane, rolledFace); + faceIndex = f; + laneIndex = l; + allDiceDeployed = false; + + } + } + } + + if (allDiceDeployed) { + break; + } + + if (faceIndex != -1 && laneIndex != -1) { + Centuria.logger.debug(Integer.toString(laneIndex) + " " + rolledFaces.get(faceIndex).toString() + + " " + Float.toString(highestBinomcdfDiff)); + + lanes.get(laneIndex).add(rolledFaces.get(faceIndex)); + rolledFaces.set(faceIndex, null); + } + + } + + return lanes; + } + + private void generateKinosMove() { + + kinosRoll = new int[6]; + kinosLanes = new int[][] { { 9, 9, 9, 9 }, { 9, 9, 9, 9 }, { 9, 9, 9, 9 } }; + + int[] kinosFirstRoll = new int[6]; + int[] kinosSecondRoll = new int[6]; + + // Kino's first roll + for (int i = 0; i < 6; i++) { + kinosFirstRoll[i] = (char) random.nextInt(1, 7); + } + + // Kino's second roll + for (int i = 0; i < 6; i++) { + kinosSecondRoll[i] = (char) random.nextInt(1, 7); + } + + // I have to determine how Kino chooses to reroll their dice + // pretending they have knowledge on what the reroll will be, + // because the alternative is a complex game algorithm with + // exponential time complexity + float highestAbsWinRate = 0; + for (int i = 0; i < 64; i++) { // 2^6 + BitSet reroll = BitSet.valueOf(new long[i]); + int[] kinosCurrChoice = new int[6]; + + for (int j = 0; j < 6; j++) { + if (reroll.get(j)) { + kinosCurrChoice[j] = kinosSecondRoll[j]; + } else { + kinosCurrChoice[j] = kinosFirstRoll[j]; + } + } + + float absWinRate = absoluteWinRate(kinosCurrChoice); + if (absWinRate > highestAbsWinRate) { + kinosRoll = kinosCurrChoice; + highestAbsWinRate = absWinRate; + } + } + + // convert the lane configuration into a string containing dice indices + List> laneConfig = bestLaneConfiguration(kinosRoll); + for (List lane : laneConfig) { + Centuria.logger.debug("new lane"); + for (QueensDuelDiceFace face : lane) { + Centuria.logger.debug(face.toString()); + } + } + + int[] remainingKinoDice = kinosRoll.clone(); + + for (int i = 0; i < laneConfig.size(); i++) { + + List lane = laneConfig.get(i); + Collections.sort(lane, Collections.reverseOrder()); + + for (int j = 0; j < lane.size(); j++) { + + QueensDuelDiceFace selectedFace = lane.get(j); + + for (int k = 0; k < remainingKinoDice.length; k++) { + if (remainingKinoDice[k] != -1 && diceFaces[remainingKinoDice[k] - 1] == selectedFace) { + kinosLanes[i][j] = k; + remainingKinoDice[k] = -1; + break; + } + } + } + } + + } + + private String intArrayToString(int[] arr) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < arr.length; i++) { + sb.append(arr[i]); + } + return sb.toString(); + } + + private int[] stringToIntArray(String str) { + int[] arr = new int[str.length()]; + for (int i = 0; i < str.length(); i++) { + arr[i] = Character.getNumericValue(str.charAt(i)); + } + return arr; + } + + private void command(Player player, XtReader rd, String command, String[] gameCommandParameters) { + switch (command) { + case "requestRoll": + + String reroll = gameCommandParameters[0]; + turn = Integer.parseInt(gameCommandParameters[1]); + + // generate the player's and kino's roll + for (int i = 0; i < 6; i++) { + if (reroll.charAt(i) == '1') { + playersRoll[i] = random.nextInt(1, 7); + } + } + + if (turn == 1) { // Kino's reroll is pregenerated + generateKinosMove(); + } + + // convert rolls to string + String roll = intArrayToString(playersRoll) + intArrayToString(kinosRoll); + String[] rollResults = { "rollResults", roll }; + sendGameCommand("gameResponse", rollResults, player); + + // convert lanes to string + String[] opponentSelection = new String[4]; + opponentSelection[0] = "opponentSelection"; + for (int i = 0; i < 3; i++) { + opponentSelection[i + 1] = intArrayToString(kinosLanes[i]); + } + sendGameCommand("gameResponse", opponentSelection, player); + + break; + case "requestCompare": + + tieCount = 0; + winCount = 0; // from the player's persepctive + loseCount = 0; + for (int i = 0; i < 3; i++) { + if (getLaneScore(stringToIntArray(gameCommandParameters[i]), + playersRoll) > getLaneScore(kinosLanes[i], kinosRoll)) { + winCount++; + } else if (getLaneScore(stringToIntArray(gameCommandParameters[i]), + playersRoll) < getLaneScore(kinosLanes[i], kinosRoll)) { + loseCount++; + } else { + tieCount++; + } + } + + String result; + if (tieCount > 1 || winCount == loseCount) { + result = "tie"; + } else if (winCount > loseCount) { + result = "win"; + } else { + result = "lose"; + } + + String[] compareResults = { "compareResults", result }; // "win", "lose" or "tie" + sendGameCommand("gameResponse", compareResults, player); + break; + case "requestTieRoll": + tieCount = 1; + + int[] kinosTieBreakingRolls = new int[tieCount]; + int[] playersTieBreakingRolls = new int[tieCount]; + + int newTieCount = 0; + + for (int i = 0; i < tieCount; i++) { + kinosTieBreakingRolls[i] = random.nextInt(1, 7); + playersTieBreakingRolls[i] = random.nextInt(1, 7); + + if (diceFaces[playersTieBreakingRolls[i] - 1].ordinal() > diceFaces[kinosTieBreakingRolls[i] - 1] + .ordinal()) { + + winCount++; + } else if (diceFaces[playersTieBreakingRolls[i] - 1] + .ordinal() < diceFaces[kinosTieBreakingRolls[i] - 1].ordinal()) { + + loseCount++; + } else { + newTieCount++; + } + } + + String tieResult; + if (winCount > loseCount) { + tieResult = "win"; + } else { + tieResult = "lose"; + } + + String[] tieResponse = new String[tieCount * 2 + 3]; + tieResponse[0] = "tieResponse"; + tieResponse[1] = Integer.toString(tieCount); + tieResponse[tieCount * 2 + 2] = tieResult; + + for (int i = 0; i < tieCount; i++) { + tieResponse[2 * (i + 1)] = Integer.toString(playersTieBreakingRolls[i]); + tieResponse[2 * (i + 1) + 1] = Integer.toString(kinosTieBreakingRolls[i]); + } + sendGameCommand("gameResponse", tieResponse, player); + + tieCount = newTieCount; + + break; + default: + break; + } + } + } + + private class FourCrows { + + List playersHand; // array of indices from the card deck + List kinosHand; + List playersPlayedCards; + List kinosPlayedCards; + + static FourCrowsCardType[] deck; + static final float[] binompdf = { 0.07776f, 0.2592f, 0.3456f, 0.2304f, 0.0768f, 0f }; // binomial point + // distribution + // function, 5 trials, + // probability 0.4, last + // value set to 0 + static final int[] suitOfCards = { 2, 4, 4, 2, 2 }; + static final int numCardsPerPlayer = 5; + static int totalNumCards; + + int turn = 0; + + static { + totalNumCards = 0; + + List deckList = new ArrayList<>(); + for (int i = 0; i < suitOfCards.length; i++) { + totalNumCards += suitOfCards[i]; + for (int j = 0; j < suitOfCards[i]; j++) { + deckList.add(FourCrowsCardType.values()[i]); + Centuria.logger.debug(FourCrowsCardType.values()[i].toString()); + } + } + deck = deckList.toArray(new FourCrowsCardType[0]); + } + + enum FourCrowsCardType { + Positive2(2), Positive1(1), Negative1(-1), Negative2(-2), Skip(0); + + private final int value; + + private FourCrowsCardType(int value) { + this.value = value; + } + + public int getVal() { + return value; + } + } + + public FourCrows() { + playersHand = new ArrayList<>(); + kinosHand = new ArrayList<>(); + playersPlayedCards = new ArrayList<>(); + kinosPlayedCards = new ArrayList<>(); + } + + private List listOfUndealtPlayerCards() { + List undealtCards = new ArrayList<>(Arrays.asList(deck)); + + for (int card : kinosHand) { + undealtCards.remove(deck[card]); + } + for (FourCrowsCardType card : playersPlayedCards) { + undealtCards.remove(card); + } + for (FourCrowsCardType card : kinosPlayedCards) { + undealtCards.remove(card); + } + + return undealtCards; + } + + private float getChangeInPlayerWinProb(FourCrowsCardType deal) { + int prevCrows = getNumCrows(); + int nextCrows = getNumCrows() + deal.getVal(); + nextCrows = Math.max(0, Math.min(nextCrows, 5)); + + return binompdf[nextCrows] - binompdf[prevCrows]; + } + + private float getChangeInKinoWinProb(FourCrowsCardType playerDeal, FourCrowsCardType kinoDeal) { + int prevCrows = getNumCrows() + playerDeal.getVal(); + prevCrows = Math.max(0, Math.min(prevCrows, 5)); + int nextCrows = prevCrows + kinoDeal.getVal(); + nextCrows = Math.max(0, Math.min(nextCrows, 5)); + + return binompdf[prevCrows] - binompdf[nextCrows]; + } + + private FourCrowsCardType playersMostLikelyDeal() { + List undealtCards = listOfUndealtPlayerCards(); + Map cardFreq = new HashMap<>(); + for (FourCrowsCardType card : undealtCards) { + cardFreq.put(card, cardFreq.getOrDefault(card, 0f) + 1f); + } + + float highestWinProb = -1f; + FourCrowsCardType mostLikelyDeal = null; + for (FourCrowsCardType card : cardFreq.keySet()) { + float currProb = cardFreq.get(card) * getChangeInPlayerWinProb(card); + if (currProb > highestWinProb) { + highestWinProb = currProb; + mostLikelyDeal = card; + } + } + + return mostLikelyDeal; + } + + private int getKinosPlay() { + FourCrowsCardType mostLikelyDeal = playersMostLikelyDeal(); + float highestWinProb = -1f; + int kinosPlay = -1; // index of card in hand + + for (int i = 0; i < kinosHand.size(); i++) { + FourCrowsCardType currCard = deck[kinosHand.get(i)]; + float currWinProb = getChangeInKinoWinProb(mostLikelyDeal, currCard) + * (float) Math.pow(Math.abs(currCard.getVal()) + 1f, -1f / turn); + if (currWinProb > highestWinProb) { + highestWinProb = currWinProb; + kinosPlay = i; + } + } + + kinosPlayedCards.add(deck[kinosHand.get(kinosPlay)]); + kinosHand.remove(kinosPlay); + + return kinosPlay; + } + + private int getNumCrows() { + int numCrows = 0; + for (int i = 0; i < playersPlayedCards.size(); i++) { + Centuria.logger.debug(playersPlayedCards.get(i).toString() + " " + kinosPlayedCards.get(i).toString()); + numCrows += playersPlayedCards.get(i).getVal(); + numCrows += kinosPlayedCards.get(i).getVal(); + if (numCrows < 0) { + numCrows = 0; + } + } + return numCrows; + // return numCrows < 5 ? numCrows : 0; + } + + private List getParams() { + // [0] Positive2 cards - 2 + // [1] Positive1 cards - 4 + // [2] Negative1 cards - 4 + // [3] Negative2 cards - 2 + // [4] Skip cards - 2 + + return IntStream.of(suitOfCards).boxed().collect(Collectors.toList()); + } + + private void command(Player player, XtReader rd, String command, String[] gameCommandParameters) { + switch (command) { + case "requestDeal": + + playersHand = new ArrayList<>(); + kinosHand = new ArrayList<>(); + playersPlayedCards = new ArrayList<>(); + kinosPlayedCards = new ArrayList<>(); + + String[] dealCardsResponse = new String[2 * numCardsPerPlayer + 3]; + dealCardsResponse[0] = "dealCardsResponse"; + + // dealIndexes + dealCardsResponse[1] = Integer.toString(2 * numCardsPerPlayer); + + // generate the player's and Kino's decks of cards + List cardIndices = new ArrayList<>(); + for (int i = 0; i < totalNumCards; i++) { + cardIndices.add(i); + } + Collections.shuffle(cardIndices); + cardIndices = cardIndices.subList(0, 2 * numCardsPerPlayer); + for (int i = 0; i < numCardsPerPlayer; i++) { + playersHand.add(cardIndices.get(i)); + kinosHand.add(cardIndices.get(numCardsPerPlayer + i)); + } + + // write the decks to the packet + for (int i = 0; i < 2 * numCardsPerPlayer; i++) { + dealCardsResponse[i + 2] = Integer.toString(cardIndices.get(i)); + } + + // dealerPlay + dealCardsResponse[2 * numCardsPerPlayer + 2] = Integer.toString(getKinosPlay()); + + sendGameCommand("gameResponse", dealCardsResponse, player); + + turn = 0; + break; + case "playerPlay": + + turn++; + + int playersPlay = Integer.parseInt(gameCommandParameters[0]); // index of card in hand + playersPlayedCards.add(deck[playersHand.get(playersPlay)]); + playersHand.remove(playersPlay); + + String[] dealerResponse = new String[2]; + dealerResponse[0] = "dealerResponse"; + dealerResponse[1] = Integer.toString(getKinosPlay()); // dealerPlay + sendGameCommand("gameResponse", dealerResponse, player); + + Centuria.logger.debug(Integer.toString(getNumCrows()) + " crows"); + if (getNumCrows() > 4 || kinosHand.size() == 0) { + sendGameCommand("multiplierResults", Integer.toString(getNumCrows()), player); + } + + break; + default: + break; + } + } + } + + private class MothsAndFlames { + + int[] roll; + int moths; + int fires; + // 1 - fire + // 2 - moth + // 3 - moth + // 4 - moth + // 5 - fire + // 6 - fire + + public MothsAndFlames() { + roll = new int[3]; + moths = 0; + fires = 0; + } + + private List getParams() { + return Arrays.asList(0); + } + + private void command(Player player, XtReader rd, String command, String[] gameCommandParameters) { + switch (command) { + case "requestRoll": + roll = new int[3]; + moths = 0; + fires = 0; + + StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < 3; i++) { + roll[i] = random.nextInt(6) + 1; // Generate a random number between 1 and 6 (inclusive) + sb.append(roll[i]); + switch (roll[i]) { + case 1: + fires++; + break; + case 5: + fires++; + break; + case 6: + fires++; + break; + case 2: + moths++; + break; + case 3: + moths++; + break; + case 4: + moths++; + break; + } + } + + String[] rollResults = { "rollResults", sb.toString() }; + sendGameCommand("gameResponse", rollResults, player); + + break; + case "playerPlay": + // 0 - 3 moths + // 1 - 2 moths + // 2 - 2 fire + // 3 - 3 fire + + int guess = Integer.parseInt(gameCommandParameters[0]); + Centuria.logger + .debug(Integer.toString(moths) + " " + Integer.toString(fires) + " " + Integer.toString(guess)); + if ((guess == 0 && moths == 3) || (guess == 2 && fires == 3)) { + sendGameCommand("multiplierResults", "3", player); + } else if ((guess == 1 && moths >= 2) || (guess == 3 && fires >= 2)) { + sendGameCommand("multiplierResults", "2", player); + } else { + sendGameCommand("multiplierResults", "0", player); + } + + break; + default: + break; + } + } + } + + private class LunarPhases { + + List playersHand; // array of indices from the card deck + List kinosHand; + List playersPlayedCards; + List kinosPlayedCards; + List removeDeck; // cards that are withdrawn from the deck are temporarily placed here + List deck; + + static final int[] suitOfCards = { 4, 4, 4, 4, 4, 4, 4, 4 }; + static final int numCardsPerPlayer = 5; + static int totalNumCards; + + class LunarPhasesTile { + final private LunarPhasesTileType tileType; + final private int value; + + public LunarPhasesTile(int Value) { + switch (Value) { + case 0: + tileType = LunarPhasesTileType.New; + break; + case 1: + tileType = LunarPhasesTileType.Crescent; + break; + case 2: + tileType = LunarPhasesTileType.Quarter; + break; + case 3: + tileType = LunarPhasesTileType.Gibbous; + break; + case 4: + tileType = LunarPhasesTileType.Full; + break; + case 5: + tileType = LunarPhasesTileType.Gibbous; + break; + case 6: + tileType = LunarPhasesTileType.Quarter; + break; + case 7: + tileType = LunarPhasesTileType.Crescent; + break; + default: + tileType = null; + break; + } + value = Value; + } + + public LunarPhasesTileType getTileType() { + return tileType; + } + + public int getValue() { + return value; + } + } + + enum LunarPhasesTileType { + New, Crescent, Quarter, Gibbous, Full + } + + public LunarPhases() { + playersHand = new ArrayList<>(); + kinosHand = new ArrayList<>(); + playersPlayedCards = new ArrayList<>(); + kinosPlayedCards = new ArrayList<>(); + removeDeck = new ArrayList<>(); + deck = new ArrayList<>(); + + totalNumCards = 0; + } + + private void removeCardsFromDeck() { + for (LunarPhasesTile removeCard : removeDeck) { + deck.remove(removeCard); + } + } + + private int kinoPlayRandCard() { + int kinoDraw = random.nextInt(deck.size()); + LunarPhasesTile kinoCard = deck.get(kinoDraw); + + while (removeDeck.contains(kinoCard)) { + kinoDraw = random.nextInt(deck.size()); + kinoCard = deck.get(kinoDraw); + } + + kinosPlayedCards.add(kinoCard); + removeDeck.add(kinoCard); + return kinoDraw; + } + + private int kinoGiveRandCard() { + int kinoDraw = random.nextInt(deck.size()); + LunarPhasesTile kinoCard = deck.get(kinoDraw); + + while (removeDeck.contains(kinoCard)) { + kinoDraw = random.nextInt(deck.size()); + kinoCard = deck.get(kinoDraw); + } + + kinosHand.add(kinoCard); + removeDeck.add(kinoCard); + return kinoDraw; + } + + private int playerPlayRandCard() { + int playerDraw = random.nextInt(deck.size()); + LunarPhasesTile playerCard = deck.get(playerDraw); + + while (removeDeck.contains(playerCard)) { + playerDraw = random.nextInt(deck.size()); + playerCard = deck.get(playerDraw); + } + + playersPlayedCards.add(playerCard); + removeDeck.add(playerCard); + return playerDraw; + } + + private int playerGiveRandCard() { + int playerDraw = random.nextInt(deck.size()); + LunarPhasesTile playerCard = deck.get(playerDraw); + + while (removeDeck.contains(playerCard)) { + playerDraw = random.nextInt(deck.size()); + playerCard = deck.get(playerDraw); + } + + playersHand.add(playerCard); + removeDeck.add(playerCard); + return playerDraw; + } + + private int getKinosFirstPick() { + + LunarPhasesTile bestCard = null; + int bestCardIdx = -1; + int highestDistFromPlayer = -100; + int playerPos = getPlayerPos(); + + for (int i = 0; i < 3; i++) { + + LunarPhasesTile currCard = kinosHand.get(i); + int currDist = getNewKinoPos(currCard) - playerPos; + + if (currDist > highestDistFromPlayer) { + currDist = highestDistFromPlayer; + bestCard = currCard; + bestCardIdx = i; + } + } + + kinosPlayedCards.add(bestCard); + removeDeck.add(bestCard); + return bestCardIdx; + } + + private int getNewKinoPos(LunarPhasesTile newTile) { + int pos = 0; + int[] cardFreq = new int[8]; + List kinosPlayedCardsAfter = new ArrayList<>(kinosPlayedCards); + kinosPlayedCardsAfter.add(newTile); + + for (LunarPhasesTile card : kinosPlayedCardsAfter) { + cardFreq[card.getValue()]++; + pos = (pos + card.getValue()) % 8; + } + + if (cardFreq[4] >= 3) { + return 9; + } else if ((cardFreq[1] > 0 && cardFreq[7] > 0) || (cardFreq[3] > 0 && cardFreq[5] > 0)) { + return 8; + } else { + return pos; + } + } + + private int getKinoPos() { + int pos = 0; + int[] cardFreq = new int[8]; + for (LunarPhasesTile card : kinosPlayedCards) { + // Centuria.logger.debug(card.getTileType().toString() + " -kino card- " + + // Integer.toString(card.getValue())); + + cardFreq[card.getValue()]++; + pos = (pos + card.getValue()) % 8; + } + + if (cardFreq[4] >= 3) { + return 9; + } else if ((cardFreq[1] > 0 && cardFreq[7] > 0) || (cardFreq[3] > 0 && cardFreq[5] > 0)) { + return 8; + } else { + return pos; + } + } + + private int getPlayerPos() { + int pos = 0; + int[] cardFreq = new int[8]; + for (LunarPhasesTile card : playersPlayedCards) { + // Centuria.logger.debug(card.getTileType().toString() + " -player card- " + + // Integer.toString(card.getValue())); + + cardFreq[card.getValue()]++; + pos = (pos + card.getValue()) % 8; + } + + if (cardFreq[4] >= 3) { + return 9; + } else if ((cardFreq[1] > 0 && cardFreq[7] > 0) || (cardFreq[3] > 0 && cardFreq[5] > 0)) { + return 8; + } else { + return pos; + } + } + + private int getKinosNextPick() { + LunarPhasesTile kinoCard = kinosHand.get(3); + + if (getNewKinoPos(kinoCard) > getKinoPos()) { // simpler than actually doing the math + kinosPlayedCards.add(kinoCard); + return 1; + } else { + return 0; + } + } + + private List getParams() { + + return Arrays.asList(0); + } + + private void command(Player player, XtReader rd, String command, String[] gameCommandParameters) { + switch (command) { + case "requestDeal": + + playersHand = new ArrayList<>(); + kinosHand = new ArrayList<>(); + playersPlayedCards = new ArrayList<>(); + kinosPlayedCards = new ArrayList<>(); + removeDeck = new ArrayList<>(); + + // generate the deck. + deck = new ArrayList<>(); + for (int i = 0; i < suitOfCards.length; i++) { + totalNumCards += suitOfCards[i]; + for (int j = 0; j < suitOfCards[i]; j++) { + deck.add(new LunarPhasesTile(i)); + } + } + + // generate the packet + String[] dealCardsResponse = new String[2 * numCardsPerPlayer + 4]; + dealCardsResponse[0] = "dealCardsResponse"; + + dealCardsResponse[1] = Integer.toString(2 * numCardsPerPlayer); // number of elements + dealCardsResponse[2] = Integer.toString(playerPlayRandCard()); + + for (int i = 1; i < numCardsPerPlayer; i++) { + dealCardsResponse[i + 2] = Integer.toString(playerGiveRandCard()); + } + + dealCardsResponse[7] = Integer.toString(kinoPlayRandCard()); + + for (int i = 1; i < numCardsPerPlayer; i++) { + dealCardsResponse[i + 7] = Integer.toString(kinoGiveRandCard()); + } + + // dealerPlay + dealCardsResponse[2 * numCardsPerPlayer + 2] = "0"; + dealCardsResponse[2 * numCardsPerPlayer + 3] = "0"; + + sendGameCommand("gameResponse", dealCardsResponse, player); + removeCardsFromDeck(); + break; + case "playerPlay": + + int playersPlay = Integer.parseInt(gameCommandParameters[0]); // index of card in hand + playersPlayedCards.add(playersHand.get(playersPlay)); // do not remove card from deck + + String[] playerResponse = new String[3]; + playerResponse[0] = "playerResponse"; + playerResponse[1] = Integer.toString(playersPlay); + playerResponse[2] = Integer.toString(getKinosFirstPick()); // dealerPlay + sendGameCommand("gameResponse", playerResponse, player); + removeCardsFromDeck(); + + Centuria.logger.debug(Integer.toString(getPlayerPos()) + " is the player's position"); + Centuria.logger.debug(Integer.toString(getKinoPos()) + " is Kino's position"); + + break; + case "drawTile": + int drawOrStand = Integer.parseInt(gameCommandParameters[0]); + if (drawOrStand == 1) { // draw tile + playersPlayedCards.add(playersHand.get(3)); + } + + String[] drawResponse = new String[3]; + drawResponse[0] = "drawResponse"; + drawResponse[1] = gameCommandParameters[0]; + drawResponse[2] = Integer.toString(getKinosNextPick()); + sendGameCommand("gameResponse", drawResponse, player); + + break; + case "requestCompare": + if (getPlayerPos() == 9) { + sendGameCommand("multiplierResults", "5", player); + } else if (getPlayerPos() > getKinoPos()) { + if (getPlayerPos() == 8) { + sendGameCommand("multiplierResults", "3", player); + } else { + sendGameCommand("multiplierResults", "2", player); + } + } else if (getPlayerPos() == getKinoPos()) { + String[] compareResults = { "compareResults", "tie" }; // "win", "lose" or "tie" + sendGameCommand("gameResponse", compareResults, player); + } else if (getPlayerPos() < getKinoPos()) { + String[] compareResults = { "compareResults", "lose" }; // "win", "lose" or "tie" + sendGameCommand("gameResponse", compareResults, player); + } + break; + case "requestTieRoll": + String[] tieResponse = new String[5]; + tieResponse[0] = "tieResponse"; + tieResponse[1] = "1"; + tieResponse[2] = Integer.toString(playerPlayRandCard()); + tieResponse[3] = Integer.toString(kinoPlayRandCard()); + if (getPlayerPos() > getKinoPos()) { + tieResponse[4] = "win"; + } else { + tieResponse[4] = "lose"; + } + + sendGameCommand("gameResponse", tieResponse, player); + break; + default: + break; + } + } + } + +} diff --git a/src/main/java/org/asf/centuria/modules/ModuleManager.java b/src/main/java/org/asf/centuria/modules/ModuleManager.java index ba15a274..8d7b11b4 100644 --- a/src/main/java/org/asf/centuria/modules/ModuleManager.java +++ b/src/main/java/org/asf/centuria/modules/ModuleManager.java @@ -151,11 +151,13 @@ public void initializeComponents() throws IllegalStateException, IOException { Centuria.logger.info("Loading Centuria modules..."); for (Class mod : moduleClasses) { try { - ICenturiaModule module = mod.getConstructor().newInstance(); - module.preInit(); - Centuria.logger.info("Loading module: " + module.id()); - this.modules.put(module.id(), module); - EventBus.getInstance().addEventReceiver(module); + if (!java.lang.reflect.Modifier.isAbstract(mod.getModifiers())) { + ICenturiaModule module = mod.getConstructor().newInstance(); + module.preInit(); + Centuria.logger.info("Loading module: " + module.id()); + this.modules.put(module.id(), module); + EventBus.getInstance().addEventReceiver(module); + } } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) { Centuria.logger.error("Module loading failure: " + mod.getTypeName(), e); diff --git a/src/main/java/org/asf/centuria/modules/events/servers/APIServerStartupEvent.java b/src/main/java/org/asf/centuria/modules/events/servers/APIServerStartupEvent.java index 7de74ace..2dae8286 100644 --- a/src/main/java/org/asf/centuria/modules/events/servers/APIServerStartupEvent.java +++ b/src/main/java/org/asf/centuria/modules/events/servers/APIServerStartupEvent.java @@ -2,7 +2,7 @@ import org.asf.centuria.modules.eventbus.EventObject; import org.asf.centuria.modules.eventbus.EventPath; -import org.asf.rats.ConnectiveHTTPServer; +import org.asf.connective.ConnectiveHttpServer; /** * @@ -15,9 +15,9 @@ @EventPath("api.startup") public class APIServerStartupEvent extends EventObject { - private ConnectiveHTTPServer server; + private ConnectiveHttpServer server; - public APIServerStartupEvent(ConnectiveHTTPServer server) { + public APIServerStartupEvent(ConnectiveHttpServer server) { this.server = server; } @@ -29,9 +29,9 @@ public String eventPath() { /** * Retrieves the API server * - * @return ConnectiveHTTPServer instance + * @return ConnectiveHttpServer instance */ - public ConnectiveHTTPServer getServer() { + public ConnectiveHttpServer getServer() { return server; } diff --git a/src/main/java/org/asf/centuria/modules/events/servers/DirectorServerStartupEvent.java b/src/main/java/org/asf/centuria/modules/events/servers/DirectorServerStartupEvent.java index da5f5ade..be631c1d 100644 --- a/src/main/java/org/asf/centuria/modules/events/servers/DirectorServerStartupEvent.java +++ b/src/main/java/org/asf/centuria/modules/events/servers/DirectorServerStartupEvent.java @@ -2,7 +2,7 @@ import org.asf.centuria.modules.eventbus.EventObject; import org.asf.centuria.modules.eventbus.EventPath; -import org.asf.rats.ConnectiveHTTPServer; +import org.asf.connective.ConnectiveHttpServer; /** * @@ -15,9 +15,9 @@ @EventPath("director.startup") public class DirectorServerStartupEvent extends EventObject { - private ConnectiveHTTPServer server; + private ConnectiveHttpServer server; - public DirectorServerStartupEvent(ConnectiveHTTPServer server) { + public DirectorServerStartupEvent(ConnectiveHttpServer server) { this.server = server; } @@ -29,9 +29,9 @@ public String eventPath() { /** * Retrieves the Director server * - * @return ConnectiveHTTPServer instance + * @return ConnectiveHttpServer instance */ - public ConnectiveHTTPServer getServer() { + public ConnectiveHttpServer getServer() { return server; } diff --git a/src/main/java/org/asf/centuria/networking/chatserver/ChatClient.java b/src/main/java/org/asf/centuria/networking/chatserver/ChatClient.java index 36238dde..3f658f6b 100644 --- a/src/main/java/org/asf/centuria/networking/chatserver/ChatClient.java +++ b/src/main/java/org/asf/centuria/networking/chatserver/ChatClient.java @@ -14,6 +14,7 @@ import org.asf.centuria.Centuria; import org.asf.centuria.accounts.AccountManager; import org.asf.centuria.accounts.CenturiaAccount; +import org.asf.centuria.dms.DMManager; import org.asf.centuria.entities.players.Player; import org.asf.centuria.modules.eventbus.EventBus; import org.asf.centuria.modules.events.chat.ChatLoginEvent; @@ -167,6 +168,51 @@ void runClient() throws IOException { return; } + // Load DMs into memory + if (acc.getSaveSharedInventory().containsItem("dms")) { + // Load and sanitize dms + JsonObject dms = acc.getSaveSharedInventory().getItem("dms").getAsJsonObject(); + ArrayList toRemove = new ArrayList(); + for (String user : dms.keySet()) { + // Clean DM participants + String dmID = dms.get(user).getAsString(); + int participantC = 0; + if (DMManager.getInstance().dmExists(dmID)) { + String[] participants = DMManager.getInstance().getDMParticipants(dmID); + participantC = participants.length; + for (String participant : participants) { + if (!participant.startsWith("plaintext:")) { + // Check account + if (AccountManager.getInstance().getAccount(participant) == null) { + participantC--; + DMManager.getInstance().removeParticipant(dmID, participant); + } + } + } + } + + // Check validity + if (AccountManager.getInstance().getAccount(user) == null || participantC <= 1) { + toRemove.add(user); + continue; + } + + // Join room + joinRoom(dms.get(user).getAsString(), true); + } + + // Remove nonexistent and invalid dms + for (String user : toRemove) { + if (DMManager.getInstance().dmExists(dms.get(user).getAsString())) + DMManager.getInstance().deleteDM(dms.get(user).getAsString()); + dms.remove(user); + } + + // Save if needed + if (toRemove.size() != 0) + acc.getSaveSharedInventory().setItem("dms", dms); + } + // Remove sensitive info and fire event handshakeStart.remove("auth_token"); ChatLoginEvent evt = new ChatLoginEvent(server, acc, this, handshakeStart); @@ -184,7 +230,7 @@ void runClient() throws IOException { if (acc.getSaveSharedInventory().containsItem("permissions")) { String permLevel = acc.getSaveSharedInventory().getItem("permissions").getAsJsonObject() .get("permissionLevel").getAsString(); - if (GameServer.hasPerm(permLevel, "moderator")) { + if (GameServer.hasPerm(permLevel, "admin")) { lockout = false; } } @@ -235,14 +281,6 @@ void runClient() throws IOException { player = acc; Centuria.logger.info("Player " + getPlayer().getDisplayName() + " connected to the chat server."); - // Load DMs into memory - if (getPlayer().getSaveSharedInventory().containsItem("dms")) { - JsonObject dms = getPlayer().getSaveSharedInventory().getItem("dms").getAsJsonObject(); - for (String user : dms.keySet()) { - joinRoom(dms.get(user).getAsString(), true); - } - } - // Send success JsonObject res = new JsonObject(); res.addProperty("eventId", "sessions.start"); @@ -324,8 +362,6 @@ public void sendPacket(JsonObject packet) { for (char ch : d) { client.getOutputStream().write((byte) ch); } - client.getOutputStream().write(0x0d); - client.getOutputStream().write(0x0a); client.getOutputStream().write(0); client.getOutputStream().flush(); Centuria.logger.debug(MarkerManager.getMarker("CHAT"), diff --git a/src/main/java/org/asf/centuria/networking/chatserver/ChatServer.java b/src/main/java/org/asf/centuria/networking/chatserver/ChatServer.java index dd1ffb90..9b8fcf03 100644 --- a/src/main/java/org/asf/centuria/networking/chatserver/ChatServer.java +++ b/src/main/java/org/asf/centuria/networking/chatserver/ChatServer.java @@ -6,8 +6,10 @@ import java.net.SocketException; import java.util.ArrayList; import java.util.ConcurrentModificationException; +import java.util.stream.Stream; import org.asf.centuria.Centuria; +import org.asf.centuria.accounts.AccountManager; import org.asf.centuria.dms.DMManager; import org.asf.centuria.dms.PrivateChatMessage; import org.asf.centuria.modules.eventbus.EventBus; @@ -38,6 +40,10 @@ public ChatServer(ServerSocket socket) { } protected void registerPackets() { + // Allow modules to register packets and to override existing packets + ChatServerStartupEvent ev = new ChatServerStartupEvent(this, t -> registerPacket(t)); + EventBus.getInstance().dispatchEvent(ev); + // Packet registry registerPacket(new PingPacket()); registerPacket(new JoinRoomPacket()); @@ -47,10 +53,6 @@ protected void registerPackets() { registerPacket(new SendMessage()); registerPacket(new OpenDMPacket()); registerPacket(new CreateConversationPacket()); - - // Allow modules to register packets - ChatServerStartupEvent ev = new ChatServerStartupEvent(this, t -> registerPacket(t)); - EventBus.getInstance().dispatchEvent(ev); } public ChatClient[] getClients() { @@ -218,9 +220,19 @@ public JsonObject roomObject(String room, boolean isPrivate, String requester) { } else { // Build participants object JsonArray members = new JsonArray(); - for (String participant : manager.getDMParticipants(room)) { - members.add(participant); + String[] participants = manager.getDMParticipants(room); + if (!Stream.of(participants).anyMatch(t -> t.equalsIgnoreCase(requester))) + return null; + for (String participant : participants) { + if (participant.startsWith("plaintext:") + || AccountManager.getInstance().getAccount(participant) != null) + members.add(participant); } + + // if its only one, the client will bug, bc if its only one its likely only the + // person thats requesting the dm + if (members.size() <= 1) + return null; roomData.add("participants", members); // Find recent message diff --git a/src/main/java/org/asf/centuria/networking/chatserver/networking/SendMessage.java b/src/main/java/org/asf/centuria/networking/chatserver/networking/SendMessage.java index 2f8c3647..cc5a7fa9 100644 --- a/src/main/java/org/asf/centuria/networking/chatserver/networking/SendMessage.java +++ b/src/main/java/org/asf/centuria/networking/chatserver/networking/SendMessage.java @@ -28,6 +28,7 @@ import org.asf.centuria.dms.PrivateChatMessage; import org.asf.centuria.entities.players.Player; import org.asf.centuria.entities.uservars.UserVarValue; +import org.asf.centuria.enums.objects.WorldObjectMoverNodeType; import org.asf.centuria.interactions.modules.QuestManager; import org.asf.centuria.ipbans.IpBanManager; import org.asf.centuria.modules.eventbus.EventBus; @@ -573,6 +574,7 @@ private boolean handleCommand(String cmd, ChatClient client) { commandMessages.add("removeperms \"\""); commandMessages.add("startmaintenance"); commandMessages.add("endmaintenance"); + commandMessages.add("stopserver"); commandMessages.add("updatewarning "); commandMessages.add("updateshutdown"); commandMessages.add("update <60|30|15|10|5|3|1>"); @@ -585,6 +587,7 @@ private boolean handleCommand(String cmd, ChatClient client) { commandMessages.add("staffroom"); commandMessages.add("listplayers"); } + commandMessages.add("stafflist"); if (client.getPlayer().getSaveSpecificInventory().getSaveSettings().giveAllResources || client.getPlayer().getSaveSpecificInventory().getSaveSettings().giveAllCurrency || client.getPlayer().getSaveSpecificInventory().getSaveSettings().giveAllFurnitureItems @@ -730,6 +733,91 @@ private boolean handleCommand(String cmd, ChatClient client) { + "' is now your active quest! Please log out and log back in to complete the process.", cmd, client); + return true; + } else if (cmdId.equalsIgnoreCase("stafflist")) { + // Staff list + HashMap staffAccounts = new HashMap(); + AccountManager.getInstance().runForAllAccounts(t -> { + String lvl = "member"; + if (t.getSaveSharedInventory().containsItem("permissions")) { + lvl = t.getSaveSharedInventory().getItem("permissions").getAsJsonObject() + .get("permissionLevel").getAsString(); + } + if (GameServer.hasPerm(lvl, "moderator")) + staffAccounts.put(t, lvl); + }); + + // Create message + String msg = ""; + + // Go through developers + boolean foundAny = false; + for (CenturiaAccount acc : staffAccounts.keySet()) { + String lvl = staffAccounts.get(acc); + if (lvl.equals("developer")) { + // Check + if (!foundAny) { + foundAny = true; + if (msg.isEmpty()) + msg += "Staff list:\n\n"; + else + msg += "\n\n"; + msg += "List of developers:"; + } + + // Add + msg += "\n - " + acc.getDisplayName() + " [" + + (acc.getOnlinePlayerInstance() == null ? "OFFLINE" : "ONLINE") + "]"; + } + } + + // Go through admins + foundAny = false; + for (CenturiaAccount acc : staffAccounts.keySet()) { + String lvl = staffAccounts.get(acc); + if (lvl.equals("admin")) { + // Check + if (!foundAny) { + foundAny = true; + if (msg.isEmpty()) + msg += "Staff list:\n\n"; + else + msg += "\n\n"; + msg += "List of administrators:"; + } + + // Add + msg += "\n - " + acc.getDisplayName() + " [" + + (acc.getOnlinePlayerInstance() == null ? "OFFLINE" : "ONLINE") + "]"; + } + } + + // Go through moderators + foundAny = false; + for (CenturiaAccount acc : staffAccounts.keySet()) { + String lvl = staffAccounts.get(acc); + if (lvl.equals("moderator")) { + // Check + if (!foundAny) { + foundAny = true; + if (msg.isEmpty()) + msg += "Staff list:\n\n"; + else + msg += "\n\n"; + msg += "List of moderators:"; + } + + // Add + msg += "\n - " + acc.getDisplayName() + " [" + + (acc.getOnlinePlayerInstance() == null ? "OFFLINE" : "ONLINE") + "]"; + } + } + + // Default + if (msg.isEmpty()) + msg = "There are no staff users."; + systemMessage(msg, cmd, client); + return true; } @@ -1364,7 +1452,7 @@ else if (helper.has(Integer.toString(plr.levelID))) for (Player player : server.getPlayers()) { if (plr.room != null && player.room != null && player.room.equals(plr.room) && player != plr) { - plr.syncTo(player); + plr.syncTo(player, WorldObjectMoverNodeType.InitPosition); Centuria.logger.debug(MarkerManager.getMarker("WorldReadyPacket"), "Syncing player " + player.account.getDisplayName() + " to " + plr.account.getDisplayName()); } @@ -1375,34 +1463,10 @@ else if (helper.has(Integer.toString(plr.levelID))) .dispatchEvent(new MiscModerationEvent("ghostmode.disabled", "Ghost Mode Disabled", Map.of("Ghost mode status", "Disabled"), plr.account.getAccountID(), null)); } else { - // Check clearance - if (!GameServer.hasPerm(permLevel, "admin")) { - // Check arguments - if (args.size() < 1) { - systemMessage( - "Error: clearance code required, please add a admin-issued clearance code to the command.", - cmd, client); - return true; - } - - // Check code - while (true) { - try { - if (clearanceCodes.contains(args.get(0))) { - clearanceCodes.remove(args.get(0)); - } else { - systemMessage("Error: invalid clearance code.", cmd, client); - return true; - } - break; - } catch (ConcurrentModificationException e) { - } - } - } - + // Enable ghost mode plr.ghostMode = true; - // Spawn for everyone in room + // Despawn for everyone in room GameServer server = (GameServer) plr.client.getServer(); for (Player player : server.getPlayers()) { if (plr.room != null && player.room != null && player.room.equals(plr.room) @@ -1748,6 +1812,22 @@ else if (helper.has(Integer.toString(plr.levelID))) break; } } + case "stopserver": { + // Check perms + if (GameServer.hasPerm(permLevel, "admin")) { + // Shut down the server + for (Player plr : Centuria.gameServer.getPlayers()) { + // Dispatch event + EventBus.getInstance().dispatchEvent(new AccountDisconnectEvent(plr.account, + "Server has been shut down.", DisconnectType.SERVER_SHUTDOWN)); + } + Centuria.disconnectPlayersForShutdown(); + System.exit(0); + return true; + } else { + break; + } + } case "startmaintenance": { // Check perms if (GameServer.hasPerm(permLevel, "admin")) { @@ -1766,7 +1846,7 @@ else if (helper.has(Integer.toString(plr.levelID))) || !GameServer.hasPerm( plr.account.getSaveSharedInventory().getItem("permissions") .getAsJsonObject().get("permissionLevel").getAsString(), - "moderator")) { + "admin")) { // Dispatch event EventBus.getInstance().dispatchEvent( new AccountDisconnectEvent(plr.account, null, DisconnectType.MAINTENANCE)); @@ -1782,7 +1862,7 @@ else if (helper.has(Integer.toString(plr.levelID))) || !GameServer.hasPerm( plr.account.getSaveSharedInventory().getItem("permissions") .getAsJsonObject().get("permissionLevel").getAsString(), - "moderator")) + "admin")) .findFirst().isPresent()) { i++; if (i == 30) @@ -1798,7 +1878,7 @@ else if (helper.has(Integer.toString(plr.levelID))) || !GameServer.hasPerm( plr.account.getSaveSharedInventory().getItem("permissions") .getAsJsonObject().get("permissionLevel").getAsString(), - "moderator")) { + "admin")) { // Disconnect from the game server plr.client.disconnect(); diff --git a/src/main/java/org/asf/centuria/networking/chatserver/networking/UserConversations.java b/src/main/java/org/asf/centuria/networking/chatserver/networking/UserConversations.java index 825eac4c..7b35bfaa 100644 --- a/src/main/java/org/asf/centuria/networking/chatserver/networking/UserConversations.java +++ b/src/main/java/org/asf/centuria/networking/chatserver/networking/UserConversations.java @@ -33,8 +33,11 @@ public boolean handle(ChatClient client) { // Add room objects for (String room : client.getRooms()) { - if (client.isRoomPrivate(room)) - convos.add(client.getServer().roomObject(room, true, client.getPlayer().getAccountID())); + if (client.isRoomPrivate(room)) { + JsonObject obj = client.getServer().roomObject(room, true, client.getPlayer().getAccountID()); + if (obj != null) + convos.add(obj); + } } res.add("conversations", convos); diff --git a/src/main/java/org/asf/centuria/networking/gameserver/GameServer.java b/src/main/java/org/asf/centuria/networking/gameserver/GameServer.java index aa91415e..7b064a23 100644 --- a/src/main/java/org/asf/centuria/networking/gameserver/GameServer.java +++ b/src/main/java/org/asf/centuria/networking/gameserver/GameServer.java @@ -20,6 +20,8 @@ import org.asf.centuria.Centuria; import org.asf.centuria.accounts.AccountManager; import org.asf.centuria.accounts.CenturiaAccount; +import org.asf.centuria.accounts.SaveMode; +import org.asf.centuria.accounts.SaveSettings; import org.asf.centuria.data.XtWriter; import org.asf.centuria.entities.players.Player; import org.asf.centuria.enums.players.OnlineStatus; @@ -29,7 +31,6 @@ import org.asf.centuria.modules.eventbus.EventBus; import org.asf.centuria.modules.events.accounts.AccountLoginEvent; import org.asf.centuria.modules.events.accounts.AccountPreloginEvent; -import org.asf.centuria.modules.events.maintenance.MaintenanceEndEvent; import org.asf.centuria.modules.events.players.PlayerJoinEvent; import org.asf.centuria.modules.events.players.PlayerLeaveEvent; import org.asf.centuria.modules.events.servers.GameServerStartupEvent; @@ -117,6 +118,10 @@ protected void registerPackets() { registerPacket(new ClientToServerHandshake(mapper)); registerPacket(new ClientToServerAuthPacket(mapper)); + // Allow modules to register packets and override existing packets + GameServerStartupEvent ev = new GameServerStartupEvent(this, t -> registerPacket(t)); + EventBus.getInstance().dispatchEvent(ev); + // Game registerPacket(new KeepAlive()); registerPacket(new PrefixedPacket()); @@ -170,10 +175,6 @@ protected void registerPackets() { registerPacket(new TradeReadyPacket()); registerPacket(new TradeReadyRejectPacket()); registerPacket(new TradeReadyAcceptPacket()); - - // Allow modules to register packets - GameServerStartupEvent ev = new GameServerStartupEvent(this, t -> registerPacket(t)); - EventBus.getInstance().dispatchEvent(ev); } @Override @@ -258,7 +259,7 @@ protected void startClient(SmartfoxClient client) throws IOException { if (acc.getSaveSharedInventory().containsItem("permissions")) { String permLevel = acc.getSaveSharedInventory().getItem("permissions").getAsJsonObject() .get("permissionLevel").getAsString(); - if (hasPerm(permLevel, "moderator")) { + if (hasPerm(permLevel, "admin")) { lockout = false; } } @@ -600,17 +601,6 @@ protected void clientDisconnect(SmartfoxClient client) { if (client.container != null && client.container instanceof Player) { Player plr = (Player) client.container; playerLeft(plr); - - // Check maintenance, exit server if noone is online during maintenance - if (maintenance && players.size() == 0) { - if (!shutdown) { - // Dispatch maintenance end event - EventBus.getInstance().dispatchEvent(new MaintenanceEndEvent()); - } - - // Exit - System.exit(0); - } } } @@ -618,7 +608,7 @@ protected void clientDisconnect(SmartfoxClient client) { protected void onStart() { // Anti-expiry (kicks players who go past token expiry) Thread th = new Thread(() -> { - while (Centuria.directorServer.isActive()) { + while (Centuria.directorServer.isRunning()) { // Find players who are logged in for longer than two days for (Player plr : getPlayers()) { long loginTimestamp = plr.account.getLastLoginTime(); @@ -638,7 +628,7 @@ protected void onStart() { // Resource respawn th = new Thread(() -> { - while (Centuria.directorServer.isActive()) { + while (Centuria.directorServer.isRunning()) { // Loop through all players for (Player plr : getPlayers()) { if (!plr.roomReady) @@ -741,6 +731,71 @@ public static boolean hasPerm(String level, String perm) { return false; } + // Used to generate names with permission/save prefixes + public static String getPlayerNameWithPrefix(CenturiaAccount account) { + // Load permission level + String permLevel = "member"; + if (account.getSaveSharedInventory().containsItem("permissions")) { + permLevel = account.getSaveSharedInventory().getItem("permissions").getAsJsonObject().get("permissionLevel") + .getAsString(); + } + + // Build prefix + String prefix = ""; + + // Load save color settings + String color = "default"; + if (GameServer.hasPerm(permLevel, "developer")) + color = "#ff00e6"; + else if (GameServer.hasPerm(permLevel, "admin")) + color = "red"; + else if (GameServer.hasPerm(permLevel, "moderator")) + color = "orange"; + + SaveSettings saveSettings = account.getSaveSpecificInventory().getSaveSettings(); + if (saveSettings != null && saveSettings.saveColors != null) { + if (GameServer.hasPerm(permLevel, "developer") && saveSettings.saveColors.has("developer")) + color = saveSettings.saveColors.get("developer").getAsString(); + else if (GameServer.hasPerm(permLevel, "admin") && saveSettings.saveColors.has("admin")) + color = saveSettings.saveColors.get("admin").getAsString(); + else if (GameServer.hasPerm(permLevel, "moderator") && saveSettings.saveColors.has("moderator")) + color = saveSettings.saveColors.get("moderator").getAsString(); + else if (GameServer.hasPerm(permLevel, "player") && saveSettings.saveColors.has("player")) + color = saveSettings.saveColors.get("player").getAsString(); + } + + // Check color + if (color.equals("default") && account.getSaveMode() == SaveMode.MANAGED) { + if (saveSettings.giveAllAvatars && saveSettings.giveAllMods && saveSettings.giveAllWings + && saveSettings.giveAllSanctuaryTypes) { + // Creative + color = "yellow"; + } else if (!saveSettings.giveAllAvatars && !saveSettings.giveAllMods && !saveSettings.giveAllClothes + && !saveSettings.giveAllWings && !saveSettings.giveAllSanctuaryTypes + && !saveSettings.giveAllFurnitureItems && !saveSettings.giveAllResources + && !saveSettings.giveAllCurrency) { + // Experience + color = "green"; + } + } + + // Add color to prefix + if (!color.equals("default")) { + prefix = ""; + } + + // Build prefix + if (GameServer.hasPerm(permLevel, "developer")) + prefix += "[dev] "; + else if (GameServer.hasPerm(permLevel, "admin")) + prefix += "[admin] "; + else if (GameServer.hasPerm(permLevel, "moderator")) + prefix += "[mod] "; + + // Return + return prefix + account.getDisplayName(); + } + /** * Retrieves a online player by ID * diff --git a/src/main/java/org/asf/centuria/networking/http/api/AuthenticateHandler.java b/src/main/java/org/asf/centuria/networking/http/api/AuthenticateHandler.java index dce1447f..aba868ca 100644 --- a/src/main/java/org/asf/centuria/networking/http/api/AuthenticateHandler.java +++ b/src/main/java/org/asf/centuria/networking/http/api/AuthenticateHandler.java @@ -1,26 +1,26 @@ package org.asf.centuria.networking.http.api; import java.io.ByteArrayOutputStream; -import java.net.Socket; +import java.io.IOException; import java.util.Base64; import java.util.UUID; import org.asf.centuria.Centuria; import org.asf.centuria.accounts.AccountManager; import org.asf.centuria.accounts.CenturiaAccount; -import org.asf.rats.ConnectiveHTTPServer; -import org.asf.rats.processors.HttpUploadProcessor; +import org.asf.connective.RemoteClient; +import org.asf.connective.processors.HttpPushProcessor; import com.google.gson.JsonObject; import com.google.gson.JsonParser; -public class AuthenticateHandler extends HttpUploadProcessor { +public class AuthenticateHandler extends HttpPushProcessor { @Override - public void process(String contentType, Socket client, String method) { + public void process(String path, String method, RemoteClient client, String contentType) throws IOException { try { // Parse body ByteArrayOutputStream strm = new ByteArrayOutputStream(); - ConnectiveHTTPServer.transferRequestBody(getHeaders(), getRequestBodyStream(), strm); + getRequest().transferRequestBody(strm); byte[] body = strm.toByteArray(); strm.close(); @@ -37,8 +37,7 @@ public void process(String contentType, Socket client, String method) { // Parse token if (token.isBlank()) { - this.setResponseCode(403); - this.setResponseMessage("Access denied"); + this.setResponseStatus(403, "Access denied"); return; } @@ -46,8 +45,7 @@ public void process(String contentType, Socket client, String method) { String verifyD = token.split("\\.")[0] + "." + token.split("\\.")[1]; String sig = token.split("\\.")[2]; if (!Centuria.verify(verifyD.getBytes("UTF-8"), Base64.getUrlDecoder().decode(sig))) { - this.setResponseCode(403); - this.setResponseMessage("Access denied"); + this.setResponseStatus(403, "Access denied"); return; } @@ -56,8 +54,7 @@ public void process(String contentType, Socket client, String method) { .parseString(new String(Base64.getUrlDecoder().decode(token.split("\\.")[1]), "UTF-8")) .getAsJsonObject(); if (!jwtPl.has("exp") || jwtPl.get("exp").getAsLong() < System.currentTimeMillis() / 1000) { - this.setResponseCode(403); - this.setResponseMessage("Access denied"); + this.setResponseStatus(403, "Access denied"); return; } @@ -77,10 +74,11 @@ public void process(String contentType, Socket client, String method) { // Check existence if (id == null) { // Check if registration is enabled, if not, prevent login - if (!Centuria.allowRegistration || Centuria.gameServer.maintenance) { + if (!Centuria.allowRegistration || Centuria.gameServer.maintenance || AccountManager.getInstance() + .getUserByLoginName(login.get("username").getAsString()) != null) { // Invalid details - this.setBody("text/json", "{\"error\":\"invalid_credential\"}"); - this.setResponseCode(422); + this.setResponseContent("text/json", "{\"error\":\"invalid_credential\"}"); + this.setResponseStatus(401, "Unauthorized"); return; } @@ -88,8 +86,8 @@ public void process(String contentType, Socket client, String method) { id = manager.register(login.get("username").getAsString()); if (id == null) { // Invalid details - this.setBody("text/json", "{\"error\":\"invalid_credential\"}"); - this.setResponseCode(422); + this.setResponseContent("text/json", "{\"error\":\"invalid_credential\"}"); + this.setResponseStatus(401, "Unauthorized"); return; } } @@ -103,8 +101,7 @@ public void process(String contentType, Socket client, String method) { // Find account CenturiaAccount acc = manager.getAccount(id); if (acc == null) { - this.setResponseCode(401); - this.setResponseMessage("Access denied"); + this.setResponseStatus(422, "Unauthorized"); return; } @@ -158,16 +155,15 @@ public void process(String contentType, Socket client, String method) { response.addProperty("rename_required_key", ""); response.addProperty("email_update_required", false); response.addProperty("email_update_required_key", ""); - setBody("text/json", response.toString()); + setResponseContent("text/json", response.toString()); } catch (Exception e) { - setResponseCode(500); - setResponseMessage("Internal Server Error"); - Centuria.logger.error(getRequest().path + " failed: 500: Internal Server Error", e); + setResponseStatus(500, "Internal Server Error"); + Centuria.logger.error(getRequest().getRequestPath() + " failed: 500: Internal Server Error", e); } } @Override - public HttpUploadProcessor createNewInstance() { + public HttpPushProcessor createNewInstance() { return new AuthenticateHandler(); } diff --git a/src/main/java/org/asf/centuria/networking/http/api/DisplayNameValidationHandler.java b/src/main/java/org/asf/centuria/networking/http/api/DisplayNameValidationHandler.java index aefc4e0a..bc2bc4d7 100644 --- a/src/main/java/org/asf/centuria/networking/http/api/DisplayNameValidationHandler.java +++ b/src/main/java/org/asf/centuria/networking/http/api/DisplayNameValidationHandler.java @@ -1,39 +1,40 @@ package org.asf.centuria.networking.http.api; -import java.net.Socket; +import java.io.IOException; import java.net.URLDecoder; import java.util.stream.Stream; import org.asf.centuria.Centuria; import org.asf.centuria.accounts.AccountManager; import org.asf.centuria.networking.chatserver.networking.SendMessage; -import org.asf.rats.processors.HttpUploadProcessor; +import org.asf.connective.RemoteClient; +import org.asf.connective.processors.HttpPushProcessor; import com.google.gson.JsonObject; -public class DisplayNameValidationHandler extends HttpUploadProcessor { +public class DisplayNameValidationHandler extends HttpPushProcessor { private static String[] nameBlacklist = new String[] { "kit", "kitsendragn", "kitsendragon", "fera", "fero", "wwadmin", "ayli", "komodorihero", "wwsam", "blinky", "fer.ocity" }; @Override - public void process(String contentType, Socket client, String method) { + public void process(String path, String method, RemoteClient client, String contentType) throws IOException { try { // Get name - String name = URLDecoder.decode(getRequest().path.substring(path().length() + 1), "UTF-8"); + String name = URLDecoder.decode(getRequest().getRequestPath().substring(path().length() + 1), "UTF-8"); AccountManager manager = AccountManager.getInstance(); // Check validity JsonObject response = new JsonObject(); if (manager.getUserByDisplayName(name) != null) { response.addProperty("error", "display_name_already_taken"); - setBody("text/json", response.toString()); + setResponseContent("text/json", response.toString()); return; } if (!name.matches("^[0-9A-Za-z\\-_. ]+") || name.length() > 16 || name.length() < 2) { response.addProperty("error", "display_name_invalid_format"); - setBody("text/json", response.toString()); + setResponseContent("text/json", response.toString()); return; } @@ -41,7 +42,7 @@ public void process(String contentType, Socket client, String method) { for (String nm : nameBlacklist) { if (name.equalsIgnoreCase(nm)) { response.addProperty("error", "display_name_sift_rejected"); - setBody("text/json", response.toString()); + setResponseContent("text/json", response.toString()); return; } } @@ -51,23 +52,22 @@ public void process(String contentType, Socket client, String method) { if (Stream.of(SendMessage.getInvalidWords()) .anyMatch(t -> t.toLowerCase().equals(word.replaceAll("[^A-Za-z0-9]", "").toLowerCase()))) { response.addProperty("error", "display_name_sift_rejected"); - setBody("text/json", response.toString()); + setResponseContent("text/json", response.toString()); return; } } // Send response response.addProperty("success", true); - setBody("text/json", response.toString()); + setResponseContent("text/json", response.toString()); } catch (Exception e) { - setResponseCode(500); - setResponseMessage("Internal Server Error"); - Centuria.logger.error(getRequest().path + " failed: 500: Internal Server Error", e); + setResponseStatus(500, "Internal Server Error"); + Centuria.logger.error(getRequest().getRequestPath() + " failed: 500: Internal Server Error", e); } } @Override - public HttpUploadProcessor createNewInstance() { + public HttpPushProcessor createNewInstance() { return new DisplayNameValidationHandler(); } diff --git a/src/main/java/org/asf/centuria/networking/http/api/DisplayNamesRequestHandler.java b/src/main/java/org/asf/centuria/networking/http/api/DisplayNamesRequestHandler.java index 22ec62ce..a6e0875a 100644 --- a/src/main/java/org/asf/centuria/networking/http/api/DisplayNamesRequestHandler.java +++ b/src/main/java/org/asf/centuria/networking/http/api/DisplayNamesRequestHandler.java @@ -1,31 +1,32 @@ package org.asf.centuria.networking.http.api; import java.io.ByteArrayOutputStream; -import java.net.Socket; +import java.io.IOException; import java.util.Base64; import java.util.UUID; import org.asf.centuria.Centuria; import org.asf.centuria.accounts.AccountManager; import org.asf.centuria.accounts.CenturiaAccount; -import org.asf.rats.ConnectiveHTTPServer; -import org.asf.rats.processors.HttpUploadProcessor; +import org.asf.centuria.networking.gameserver.GameServer; +import org.asf.connective.RemoteClient; +import org.asf.connective.processors.HttpPushProcessor; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; -public class DisplayNamesRequestHandler extends HttpUploadProcessor { +public class DisplayNamesRequestHandler extends HttpPushProcessor { private static String NIL_UUID = new UUID(0, 0).toString(); @Override - public void process(String contentType, Socket client, String method) { + public void process(String path, String method, RemoteClient client, String contentType) throws IOException { try { // Parse body ByteArrayOutputStream strm = new ByteArrayOutputStream(); - ConnectiveHTTPServer.transferRequestBody(getHeaders(), getRequestBodyStream(), strm); + getRequest().transferRequestBody(strm); byte[] body = strm.toByteArray(); strm.close(); @@ -39,8 +40,7 @@ public void process(String contentType, Socket client, String method) { String verifyD = token.split("\\.")[0] + "." + token.split("\\.")[1]; String sig = token.split("\\.")[2]; if (!Centuria.verify(verifyD.getBytes("UTF-8"), Base64.getUrlDecoder().decode(sig))) { - this.setResponseCode(401); - this.setResponseMessage("Access denied"); + this.setResponseStatus(401, "Unauthorized"); return; } @@ -49,8 +49,7 @@ public void process(String contentType, Socket client, String method) { .parseString(new String(Base64.getUrlDecoder().decode(token.split("\\.")[1]), "UTF-8")) .getAsJsonObject(); if (!jwtPl.has("exp") || jwtPl.get("exp").getAsLong() < System.currentTimeMillis() / 1000) { - this.setResponseCode(401); - this.setResponseMessage("Access denied"); + this.setResponseStatus(401, "Unauthorized"); return; } @@ -73,11 +72,13 @@ public void process(String contentType, Socket client, String method) { } CenturiaAccount acc = manager.getAccount(id); if (acc != null) { + // Add user entry JsonObject d = new JsonObject(); - d.addProperty("display_name", acc.getDisplayName()); + d.addProperty("display_name", GameServer.getPlayerNameWithPrefix(acc)); d.addProperty("uuid", id); found.add(d); } else if (id.startsWith("plaintext:")) { + // Add plain entry JsonObject d = new JsonObject(); d.addProperty("display_name", id.substring("plaintext:".length())); d.addProperty("uuid", id); @@ -89,16 +90,15 @@ public void process(String contentType, Socket client, String method) { response.add("found", found); response.add("not_found", (unrecognized.size() == 0 ? null : unrecognized)); - setBody("text/json", response.toString()); + setResponseContent("text/json", response.toString()); } catch (Exception e) { - setResponseCode(500); - setResponseMessage("Internal Server Error"); - Centuria.logger.error(getRequest().path + " failed: 500: Internal Server Error", e); + setResponseStatus(500, "Internal Server Error"); + Centuria.logger.error(getRequest().getRequestPath() + " failed: 500: Internal Server Error", e); } } @Override - public HttpUploadProcessor createNewInstance() { + public HttpPushProcessor createNewInstance() { return new DisplayNamesRequestHandler(); } diff --git a/src/main/java/org/asf/centuria/networking/http/api/FallbackAPIProcessor.java b/src/main/java/org/asf/centuria/networking/http/api/FallbackAPIProcessor.java index d6d978c7..d3ebd295 100644 --- a/src/main/java/org/asf/centuria/networking/http/api/FallbackAPIProcessor.java +++ b/src/main/java/org/asf/centuria/networking/http/api/FallbackAPIProcessor.java @@ -1,14 +1,14 @@ package org.asf.centuria.networking.http.api; import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.io.UnsupportedEncodingException; -import java.net.Socket; import java.text.SimpleDateFormat; import java.util.Base64; import java.util.Date; import java.util.TimeZone; import java.util.UUID; -import java.util.HashMap; +import java.util.Map; import org.asf.centuria.Centuria; import org.asf.centuria.accounts.AccountManager; @@ -17,20 +17,20 @@ import org.asf.centuria.packets.xt.gameserver.room.RoomJoinPacket; import org.asf.centuria.social.SocialEntry; import org.asf.centuria.social.SocialManager; -import org.asf.rats.ConnectiveHTTPServer; -import org.asf.rats.processors.HttpUploadProcessor; +import org.asf.connective.RemoteClient; +import org.asf.connective.processors.HttpPushProcessor; import com.google.gson.JsonArray; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.google.gson.JsonSyntaxException; -public class FallbackAPIProcessor extends HttpUploadProcessor { +public class FallbackAPIProcessor extends HttpPushProcessor { public static String KeyID = UUID.randomUUID().toString(); @Override - public void process(String contentType, Socket client, String method) { + public void process(String pth, String method, RemoteClient client, String contentType) throws IOException { String path = this.getRequestPath(); AccountManager manager = AccountManager.getInstance(); @@ -39,7 +39,7 @@ public void process(String contentType, Socket client, String method) { byte[] body = new byte[0]; if (method.toUpperCase().equals("POST")) { ByteArrayOutputStream strm = new ByteArrayOutputStream(); - ConnectiveHTTPServer.transferRequestBody(getHeaders(), getRequestBodyStream(), strm); + getRequest().transferRequestBody(strm); body = strm.toByteArray(); strm.close(); } @@ -65,13 +65,12 @@ public void process(String contentType, Socket client, String method) { JsonObject response = new JsonObject(); response.addProperty("autorization_key", headerD + "." + payloadD + "." + Base64.getUrlEncoder() .withoutPadding().encodeToString(Centuria.sign((headerD + "." + payloadD).getBytes("UTF-8")))); - setBody("text/json", response.toString()); + setResponseContent("text/json", response.toString()); } else if (path.startsWith("/r/block/")) { // Find account CenturiaAccount acc = verifyAndGetAcc(manager); if (acc == null) { - this.setResponseCode(401); - this.setResponseMessage("Access denied"); + this.setResponseStatus(401, "Unauthorized"); return; } @@ -112,9 +111,8 @@ public void process(String contentType, Socket client, String method) { // check existing block if (socialListManager.getPlayerIsBlocked(sourcePlayerID, targetPlayerID)) { // error - setResponseCode(200); - setResponseMessage("No Content"); - setBody("text/json", "{\"error\":\"already_blocked\"}"); + setResponseStatus(200, "OK"); + setResponseContent("text/json", "{\"error\":\"already_blocked\"}"); return; } @@ -138,9 +136,8 @@ public void process(String contentType, Socket client, String method) { System.out.println("[API] [r/block] [" + method + "] | Processed block Request "); } - setResponseCode(201); - setResponseMessage("No Content"); - setBody(""); + setResponseStatus(201, "No Content"); + setResponseContent(""); break; } @@ -161,9 +158,8 @@ public void process(String contentType, Socket client, String method) { // check block if (!socialListManager.getPlayerIsBlocked(sourcePlayerID, targetPlayerID)) { // error - setResponseCode(200); - setResponseMessage("No Content"); - setBody("text/json", "{\"error\":\"not_blocked\"}"); + setResponseStatus(200, "OK"); + setResponseContent("text/json", "{\"error\":\"not_blocked\"}"); return; } @@ -180,14 +176,13 @@ public void process(String contentType, Socket client, String method) { } } - setBody("text/json", response.toString()); + setResponseContent("text/json", response.toString()); } else if (path.startsWith("/r/follow/")) { // Find account CenturiaAccount acc = verifyAndGetAcc(manager); if (acc == null) { - this.setResponseCode(401); - this.setResponseMessage("Access denied"); + this.setResponseStatus(401, "Unauthorized"); return; } @@ -214,26 +209,23 @@ public void process(String contentType, Socket client, String method) { // check follow state if (SocialManager.getInstance().getPlayerIsFollowing(sourcePlayerID, targetPlayerID)) { // error - setResponseCode(200); - setResponseMessage("No Content"); - setBody("text/json", "{\"error\":\"already_following\"}"); + setResponseStatus(200, "OK"); + setResponseContent("text/json", "{\"error\":\"already_following\"}"); return; } // check follow count if (SocialManager.getInstance().getFollowingPlayers(sourcePlayerID).length > 1000) { // error - setResponseCode(200); - setResponseMessage("No Content"); - setBody("text/json", "{\"error\":\"limit_reached\"}"); + setResponseStatus(200, "OK"); + setResponseContent("text/json", "{\"error\":\"limit_reached\"}"); return; } SocialManager.getInstance().setFollowingPlayer(sourcePlayerID, targetPlayerID, true); SocialManager.getInstance().setFollowerPlayer(targetPlayerID, sourcePlayerID, true); - setResponseCode(201); - setResponseMessage("No Content"); + setResponseStatus(201, "No content"); // inform the client if possible Player plr = acc.getOnlinePlayerInstance(); @@ -247,9 +239,8 @@ public void process(String contentType, Socket client, String method) { // check follow state if (!SocialManager.getInstance().getPlayerIsFollowing(sourcePlayerID, targetPlayerID)) { // error - setResponseCode(200); - setResponseMessage("No Content"); - setBody("text/json", "{\"error\":\"not_following\"}"); + setResponseStatus(200, "OK"); + setResponseContent("text/json", "{\"error\":\"not_following\"}"); return; } @@ -270,8 +261,7 @@ public void process(String contentType, Socket client, String method) { // verify token and get the account. var acc = verifyAndGetAcc(manager); if (acc == null) { - this.setResponseCode(401); - this.setResponseMessage("Access denied"); + this.setResponseStatus(401, "Unauthorized"); return; } @@ -295,14 +285,7 @@ public void process(String contentType, Socket client, String method) { JsonArray jsonArray = new JsonArray(); // parse query - HashMap query = new HashMap(); - for (String querySegment : getRequest().query.split("&")) { - if (querySegment.isEmpty()) - continue; - String key = querySegment.substring(0, querySegment.indexOf("=")); - String value = querySegment.substring(querySegment.indexOf("=") + 1); - query.put(key, value); - } + Map query = getRequest().getRequestQueryParameters(); // find entries int page = Integer.parseInt(query.getOrDefault("page", "1")); @@ -330,7 +313,7 @@ public void process(String contentType, Socket client, String method) { } // send response packet - setBody("text/json", jsonArray.toString()); + setResponseContent("text/json", jsonArray.toString()); // log interaction details if (Centuria.debugMode) { @@ -343,8 +326,7 @@ public void process(String contentType, Socket client, String method) { var acc = verifyAndGetAcc(manager); if (acc == null) { - this.setResponseCode(401); - this.setResponseMessage("Access denied"); + this.setResponseStatus(401, "Unauthorized"); return; } @@ -366,14 +348,7 @@ public void process(String contentType, Socket client, String method) { JsonArray jsonArray = new JsonArray(); // parse query - HashMap query = new HashMap(); - for (String querySegment : getRequest().query.split("&")) { - if (querySegment.isEmpty()) - continue; - String key = querySegment.substring(0, querySegment.indexOf("=")); - String value = querySegment.substring(querySegment.indexOf("=") + 1); - query.put(key, value); - } + Map query = getRequest().getRequestQueryParameters(); // find entries int page = Integer.parseInt(query.getOrDefault("page", "1")); @@ -398,7 +373,7 @@ public void process(String contentType, Socket client, String method) { } // send response packet - setBody("text/json", jsonArray.toString()); + setResponseContent("text/json", jsonArray.toString()); // log interaction details if (Centuria.debugMode) { @@ -410,8 +385,7 @@ public void process(String contentType, Socket client, String method) { var acc = verifyAndGetAcc(manager); if (acc == null) { - this.setResponseCode(401); - this.setResponseMessage("Access denied"); + this.setResponseStatus(401, "Unauthorized"); return; } @@ -432,8 +406,7 @@ public void process(String contentType, Socket client, String method) { case "post": { friendListManager.setFavoritePlayer(sourcePlayerID, targetPlayerID, true); - setResponseCode(201); - setResponseMessage("No Content"); + setResponseStatus(201, "No content"); // log details if (Centuria.debugMode) { @@ -475,17 +448,16 @@ public void process(String contentType, Socket client, String method) { + " ) ( body: " + new String(body, "UTF-8") + " )"); } - setResponseCode(400); - setResponseMessage("Bad Request"); - setBody("text/json", "{}"); + setResponseStatus(400, "Bad request"); + setResponseContent("text/json", "{}"); } } catch (Exception e) { - Centuria.logger.error(getRequest().path + " failed", e); + Centuria.logger.error(getRequest().getRequestPath() + " failed", e); } } @Override - public HttpUploadProcessor createNewInstance() { + public HttpPushProcessor createNewInstance() { return new FallbackAPIProcessor(); } @@ -495,7 +467,7 @@ public String path() { } @Override - public boolean supportsGet() { + public boolean supportsNonPush() { return true; } diff --git a/src/main/java/org/asf/centuria/networking/http/api/GameRegistrationHandler.java b/src/main/java/org/asf/centuria/networking/http/api/GameRegistrationHandler.java index c332c429..b5b2ed50 100644 --- a/src/main/java/org/asf/centuria/networking/http/api/GameRegistrationHandler.java +++ b/src/main/java/org/asf/centuria/networking/http/api/GameRegistrationHandler.java @@ -4,7 +4,6 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.net.Socket; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; @@ -17,13 +16,13 @@ import org.asf.centuria.accounts.registration.RegistrationVerificationResult; import org.asf.centuria.accounts.registration.RegistrationVerificationStatus; import org.asf.centuria.packets.xt.gameserver.inventory.InventoryItemDownloadPacket; -import org.asf.rats.ConnectiveHTTPServer; -import org.asf.rats.processors.HttpUploadProcessor; +import org.asf.connective.RemoteClient; +import org.asf.connective.processors.HttpPushProcessor; import com.google.gson.JsonObject; import com.google.gson.JsonParser; -public class GameRegistrationHandler extends HttpUploadProcessor { +public class GameRegistrationHandler extends HttpPushProcessor { private static String[] nameBlacklist = new String[] { "kit", "kitsendragn", "kitsendragon", "fera", "fero", "wwadmin", "ayli", "komodorihero", "wwsam", "blinky", "fer.ocity" }; @@ -74,11 +73,11 @@ public class GameRegistrationHandler extends HttpUploadProcessor { } @Override - public void process(String contentType, Socket client, String method) { + public void process(String path, String method, RemoteClient client, String contentType) throws IOException { try { // Parse body ByteArrayOutputStream strm = new ByteArrayOutputStream(); - ConnectiveHTTPServer.transferRequestBody(getHeaders(), getRequestBodyStream(), strm); + getRequest().transferRequestBody(strm); byte[] body = strm.toByteArray(); strm.close(); @@ -101,9 +100,8 @@ public void process(String contentType, Socket client, String method) { if (accountName.equalsIgnoreCase(name)) { // Reply with error response.addProperty("error", "invalid_username"); - setBody("text/json", response.toString()); - this.setResponseCode(400); - this.setResponseMessage("Bad request"); + setResponseContent("text/json", response.toString()); + this.setResponseStatus(400, "Bad request"); return; } } @@ -113,9 +111,8 @@ public void process(String contentType, Socket client, String method) { if (displayName.equalsIgnoreCase(name)) { // Reply with error response.addProperty("error", "display_name_sift_rejected"); - setBody("text/json", response.toString()); - this.setResponseCode(400); - this.setResponseMessage("Bad request"); + setResponseContent("text/json", response.toString()); + this.setResponseStatus(400, "Bad request"); return; } } @@ -125,18 +122,16 @@ public void process(String contentType, Socket client, String method) { if (muteWords.contains(word.replaceAll("[^A-Za-z0-9]", "").toLowerCase())) { // Reply with error response.addProperty("error", "invalid_username"); - setBody("text/json", response.toString()); - this.setResponseCode(400); - this.setResponseMessage("Bad request"); + setResponseContent("text/json", response.toString()); + this.setResponseStatus(400, "Bad request"); return; } if (filterWords.contains(word.replaceAll("[^A-Za-z0-9]", "").toLowerCase())) { // Reply with error response.addProperty("error", "invalid_username"); - setBody("text/json", response.toString()); - this.setResponseCode(400); - this.setResponseMessage("Bad request"); + setResponseContent("text/json", response.toString()); + this.setResponseStatus(400, "Bad request"); return; } } @@ -146,18 +141,16 @@ public void process(String contentType, Socket client, String method) { if (muteWords.contains(word.replaceAll("[^A-Za-z0-9]", "").toLowerCase())) { // Reply with error response.addProperty("error", "display_name_sift_rejected"); - setBody("text/json", response.toString()); - this.setResponseCode(400); - this.setResponseMessage("Bad request"); + setResponseContent("text/json", response.toString()); + this.setResponseStatus(400, "Bad request"); return; } if (filterWords.contains(word.replaceAll("[^A-Za-z0-9]", "").toLowerCase())) { // Reply with error response.addProperty("error", "display_name_sift_rejected"); - setBody("text/json", response.toString()); - this.setResponseCode(400); - this.setResponseMessage("Bad request"); + setResponseContent("text/json", response.toString()); + this.setResponseStatus(400, "Bad request"); return; } } @@ -168,9 +161,8 @@ public void process(String contentType, Socket client, String method) { || accountName.length() > 320) { // Reply with error response.addProperty("error", "invalid_username"); - setBody("text/json", response.toString()); - this.setResponseCode(400); - this.setResponseMessage("Bad request"); + setResponseContent("text/json", response.toString()); + this.setResponseStatus(400, "Bad request"); return; } @@ -178,9 +170,8 @@ public void process(String contentType, Socket client, String method) { if (!displayName.matches("^[0-9A-Za-z\\-_. ]+") || displayName.length() > 16 || displayName.length() < 2) { // Reply with error response.addProperty("error", "display_name_invalid_format"); - setBody("text/json", response.toString()); - this.setResponseCode(400); - this.setResponseMessage("Bad request"); + setResponseContent("text/json", response.toString()); + this.setResponseStatus(400, "Bad request"); return; } @@ -188,9 +179,8 @@ public void process(String contentType, Socket client, String method) { if (manager.getUserByLoginName(accountName) != null) { // Reply with error response.addProperty("error", "username_already_exists"); - setBody("text/json", response.toString()); - this.setResponseCode(400); - this.setResponseMessage("Bad request"); + setResponseContent("text/json", response.toString()); + this.setResponseStatus(400, "Bad request"); return; } @@ -198,9 +188,8 @@ public void process(String contentType, Socket client, String method) { if (manager.isDisplayNameInUse(displayName)) { // Reply with error response.addProperty("error", "display_name_already_taken"); - setBody("text/json", response.toString()); - this.setResponseCode(400); - this.setResponseMessage("Bad request"); + setResponseContent("text/json", response.toString()); + this.setResponseStatus(400, "Bad request"); return; } @@ -285,9 +274,8 @@ public void process(String contentType, Socket client, String method) { // Reply with error response.addProperty("error", "Unable to verify with that name. Please use a different login name format."); - setBody("text/json", response.toString()); - this.setResponseCode(400); - this.setResponseMessage("Bad request"); + setResponseContent("text/json", response.toString()); + this.setResponseStatus(400, "Bad request"); return; } @@ -335,7 +323,7 @@ public void process(String contentType, Socket client, String method) { // Call helper post registration helper.postRegistration(manager.getAccount(accountID), accountID, displayName, verifyPayload); - setBody("text/json", response.toString()); + setResponseContent("text/json", response.toString()); return; } else { // Set error @@ -362,32 +350,29 @@ public void process(String contentType, Socket client, String method) { } response.addProperty("error", result.error); response.addProperty("error_message", result.errorMessage); - this.setResponseCode(400); - this.setResponseMessage("Bad request"); - setBody("text/json", response.toString()); + this.setResponseStatus(400, "Bad request"); + setResponseContent("text/json", response.toString()); return; } } // Reply with error response.addProperty("error", "Unable to verify with that name. Please use a different login name format."); - setBody("text/json", response.toString()); - this.setResponseCode(400); - this.setResponseMessage("Bad request"); + setResponseContent("text/json", response.toString()); + this.setResponseStatus(400, "Bad request"); return; } // Send response - setBody("text/json", response.toString()); + setResponseContent("text/json", response.toString()); } catch (Exception e) { - setResponseCode(500); - setResponseMessage("Internal Server Error"); - Centuria.logger.error(getRequest().path + " failed: 500: Internal Server Error", e); + setResponseStatus(500, "Internal Server Error"); + Centuria.logger.error(getRequest().getRequestPath() + " failed: 500: Internal Server Error", e); } } @Override - public HttpUploadProcessor createNewInstance() { + public HttpPushProcessor createNewInstance() { return new GameRegistrationHandler(); } diff --git a/src/main/java/org/asf/centuria/networking/http/api/RequestTokenHandler.java b/src/main/java/org/asf/centuria/networking/http/api/RequestTokenHandler.java index 05d6bc12..25785c60 100644 --- a/src/main/java/org/asf/centuria/networking/http/api/RequestTokenHandler.java +++ b/src/main/java/org/asf/centuria/networking/http/api/RequestTokenHandler.java @@ -1,14 +1,15 @@ package org.asf.centuria.networking.http.api; -import java.net.Socket; -import org.asf.rats.processors.HttpUploadProcessor; +import java.io.IOException; +import org.asf.connective.RemoteClient; +import org.asf.connective.processors.HttpPushProcessor; import com.google.gson.JsonObject; -public class RequestTokenHandler extends HttpUploadProcessor { +public class RequestTokenHandler extends HttpPushProcessor { @Override - public void process(String contentType, Socket client, String method) { + public void process(String path, String method, RemoteClient client, String contentType) throws IOException { // Hardcoded response as i have no clue how to do this String challenge = "kOLl8r71tG1343qobkIvdJSGuXxUZBQUtHTq7Npe91l51TrpaGLZf4nPIjSCNxniUdpdHvOfcCzV2TQRn5MXab08vwGizt0NiDmzAdWrzQMYDjgTYz7Xqbzqds2LaYTa"; String iv = "03KJ2tNeasisn7vI42W49IJpObpQirvu"; @@ -17,16 +18,16 @@ public void process(String contentType, Socket client, String method) { JsonObject res = new JsonObject(); res.addProperty("challenge", challenge); res.addProperty("iv", iv); - this.setBody("text/json", res.toString()); + this.setResponseContent("text/json", res.toString()); } @Override - public boolean supportsGet() { + public boolean supportsNonPush() { return true; } @Override - public HttpUploadProcessor createNewInstance() { + public HttpPushProcessor createNewInstance() { return new RequestTokenHandler(); } diff --git a/src/main/java/org/asf/centuria/networking/http/api/SeasonPassRequestHandler.java b/src/main/java/org/asf/centuria/networking/http/api/SeasonPassRequestHandler.java index 15d8cd6d..663faf30 100644 --- a/src/main/java/org/asf/centuria/networking/http/api/SeasonPassRequestHandler.java +++ b/src/main/java/org/asf/centuria/networking/http/api/SeasonPassRequestHandler.java @@ -1,6 +1,6 @@ package org.asf.centuria.networking.http.api; -import java.net.Socket; +import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Base64; import java.util.Date; @@ -11,16 +11,17 @@ import org.asf.centuria.accounts.CenturiaAccount; import org.asf.centuria.seasonpasses.SeasonPassDefinition; import org.asf.centuria.seasonpasses.SeasonPassManager; -import org.asf.rats.processors.HttpUploadProcessor; +import org.asf.connective.RemoteClient; +import org.asf.connective.processors.HttpPushProcessor; import com.google.gson.JsonArray; import com.google.gson.JsonObject; import com.google.gson.JsonParser; -public class SeasonPassRequestHandler extends HttpUploadProcessor { +public class SeasonPassRequestHandler extends HttpPushProcessor { @Override - public void process(String contentType, Socket client, String method) { + public void process(String pth, String method, RemoteClient client, String contentType) throws IOException { try { // Parse JWT payload String token = this.getHeader("Authorization").substring("Bearer ".length()); @@ -29,8 +30,7 @@ public void process(String contentType, Socket client, String method) { String verifyD = token.split("\\.")[0] + "." + token.split("\\.")[1]; String sig = token.split("\\.")[2]; if (!Centuria.verify(verifyD.getBytes("UTF-8"), Base64.getUrlDecoder().decode(sig))) { - this.setResponseCode(401); - this.setResponseMessage("Access denied"); + this.setResponseStatus(401, "Unauthorized"); return; } @@ -39,8 +39,7 @@ public void process(String contentType, Socket client, String method) { .parseString(new String(Base64.getUrlDecoder().decode(token.split("\\.")[1]), "UTF-8")) .getAsJsonObject(); if (!jwtPl.has("exp") || jwtPl.get("exp").getAsLong() < System.currentTimeMillis() / 1000) { - this.setResponseCode(401); - this.setResponseMessage("Access denied"); + this.setResponseStatus(401, "Unauthorized"); return; } @@ -92,7 +91,7 @@ public void process(String contentType, Socket client, String method) { // Add ID res.addProperty("uuid", acc.getAccountID()); - this.setBody("text/json", res.toString()); + this.setResponseContent("text/json", res.toString()); return; } else if (path.equals("/challenges/" + acc.getAccountID() + "/player-challenges")) { // Challenge request @@ -111,7 +110,7 @@ public void process(String contentType, Socket client, String method) { } res.add("challenges", challenges); - this.setBody("text/json", res.toString()); + this.setResponseContent("text/json", res.toString()); return; } else if (path.equals("/challenges/" + acc.getAccountID() + "/player-challenges/completed")) { // Completed challenge request @@ -123,7 +122,7 @@ public void process(String contentType, Socket client, String method) { // TODO: populate res.add("challenges", completedChallenges); - this.setBody("text/json", res.toString()); + this.setResponseContent("text/json", res.toString()); return; } } else { @@ -131,22 +130,21 @@ public void process(String contentType, Socket client, String method) { getResponse().setResponseStatus(404, "Not Found"); JsonObject res = new JsonObject(); res.addProperty("error", "none_active"); - this.setBody("text/json", res.toString()); + this.setResponseContent("text/json", res.toString()); return; } // Set response getResponse().setResponseStatus(400, "Bad Reqeust"); - this.setBody("text/json", "{}"); + this.setResponseContent("text/json", "{}"); } catch (Exception e) { - setResponseCode(500); - setResponseMessage("Internal Server Error"); - Centuria.logger.error(getRequest().path + " failed: 500: Internal Server Error", e); + setResponseStatus(500, "Internal Server Error"); + Centuria.logger.error(getRequest().getRequestPath() + " failed: 500: Internal Server Error", e); } } @Override - public boolean supportsGet() { + public boolean supportsNonPush() { return true; } @@ -156,7 +154,7 @@ public boolean supportsChildPaths() { } @Override - public HttpUploadProcessor createNewInstance() { + public HttpPushProcessor createNewInstance() { return new SeasonPassRequestHandler(); } diff --git a/src/main/java/org/asf/centuria/networking/http/api/UpdateDisplayNameHandler.java b/src/main/java/org/asf/centuria/networking/http/api/UpdateDisplayNameHandler.java index cb9edc6c..21bc70e5 100644 --- a/src/main/java/org/asf/centuria/networking/http/api/UpdateDisplayNameHandler.java +++ b/src/main/java/org/asf/centuria/networking/http/api/UpdateDisplayNameHandler.java @@ -1,7 +1,7 @@ package org.asf.centuria.networking.http.api; import java.io.ByteArrayOutputStream; -import java.net.Socket; +import java.io.IOException; import java.util.Base64; import java.util.stream.Stream; @@ -9,23 +9,23 @@ import org.asf.centuria.accounts.AccountManager; import org.asf.centuria.accounts.CenturiaAccount; import org.asf.centuria.networking.chatserver.networking.SendMessage; -import org.asf.rats.ConnectiveHTTPServer; -import org.asf.rats.processors.HttpUploadProcessor; +import org.asf.connective.RemoteClient; +import org.asf.connective.processors.HttpPushProcessor; import com.google.gson.JsonObject; import com.google.gson.JsonParser; -public class UpdateDisplayNameHandler extends HttpUploadProcessor { +public class UpdateDisplayNameHandler extends HttpPushProcessor { private static String[] nameBlacklist = new String[] { "kit", "kitsendragn", "kitsendragon", "fera", "fero", "wwadmin", "ayli", "komodorihero", "wwsam", "blinky", "fer.ocity" }; @Override - public void process(String contentType, Socket client, String method) { + public void process(String path, String method, RemoteClient client, String contentType) throws IOException { try { // Parse body ByteArrayOutputStream strm = new ByteArrayOutputStream(); - ConnectiveHTTPServer.transferRequestBody(getHeaders(), getRequestBodyStream(), strm); + getRequest().transferRequestBody(strm); byte[] body = strm.toByteArray(); strm.close(); @@ -51,8 +51,7 @@ public void process(String contentType, Socket client, String method) { .parseString(new String(Base64.getUrlDecoder().decode(token.split("\\.")[1]), "UTF-8")) .getAsJsonObject(); if (!jwtPl.has("exp") || jwtPl.get("exp").getAsLong() < System.currentTimeMillis() / 1000) { - this.setResponseCode(401); - this.setResponseMessage("Access denied"); + this.setResponseStatus(401, "Unauthorized"); return; } @@ -63,15 +62,14 @@ public void process(String contentType, Socket client, String method) { // Find account CenturiaAccount acc = manager.getAccount(jP.get("uuid").getAsString()); if (acc == null) { - this.setResponseCode(401); - this.setResponseMessage("Access denied"); + this.setResponseStatus(401, "Unauthorized"); return; } // Check if the name is in use if (manager.isDisplayNameInUse(newName) && !manager.getUserByDisplayName(newName).equals(acc.getAccountID()) || (manager.isDisplayNameInUse(newName) && acc.isRenameRequired())) { - setBody("text/json", "{\"error\":\"display_name_already_taken\"}"); + setResponseContent("text/json", "{\"error\":\"display_name_already_taken\"}"); return; // Name is in use } @@ -94,7 +92,7 @@ public void process(String contentType, Socket client, String method) { JsonObject response = new JsonObject(); if (!newName.matches("^[0-9A-Za-z\\-_. ]+") || newName.length() > 16 || newName.length() < 2) { response.addProperty("error", "display_name_invalid_format"); - setBody("text/json", response.toString()); + setResponseContent("text/json", response.toString()); return; } @@ -102,7 +100,7 @@ public void process(String contentType, Socket client, String method) { for (String nm : nameBlacklist) { if (newName.equalsIgnoreCase(nm)) { response.addProperty("error", "display_name_sift_rejected"); - setBody("text/json", response.toString()); + setResponseContent("text/json", response.toString()); return; } } @@ -112,20 +110,19 @@ public void process(String contentType, Socket client, String method) { if (Stream.of(SendMessage.getInvalidWords()) .anyMatch(t -> t.toLowerCase().equals(word.replaceAll("[^A-Za-z0-9]", "").toLowerCase()))) { response.addProperty("error", "display_name_sift_rejected"); - setBody("text/json", response.toString()); + setResponseContent("text/json", response.toString()); return; } } } } catch (Exception e) { - setResponseCode(500); - setResponseMessage("Internal Server Error"); - Centuria.logger.error(getRequest().path + " failed: 500: Internal Server Error", e); + setResponseStatus(500, "Internal Server Error"); + Centuria.logger.error(getRequest().getRequestPath() + " failed: 500: Internal Server Error", e); } } @Override - public HttpUploadProcessor createNewInstance() { + public HttpPushProcessor createNewInstance() { return new UpdateDisplayNameHandler(); } diff --git a/src/main/java/org/asf/centuria/networking/http/api/UserHandler.java b/src/main/java/org/asf/centuria/networking/http/api/UserHandler.java index 570df946..ecfb95ec 100644 --- a/src/main/java/org/asf/centuria/networking/http/api/UserHandler.java +++ b/src/main/java/org/asf/centuria/networking/http/api/UserHandler.java @@ -1,19 +1,21 @@ package org.asf.centuria.networking.http.api; -import java.net.Socket; +import java.io.IOException; import java.util.Base64; import org.asf.centuria.Centuria; import org.asf.centuria.accounts.AccountManager; import org.asf.centuria.accounts.CenturiaAccount; -import org.asf.rats.processors.HttpUploadProcessor; +import org.asf.centuria.networking.gameserver.GameServer; +import org.asf.connective.RemoteClient; +import org.asf.connective.processors.HttpPushProcessor; import com.google.gson.JsonObject; import com.google.gson.JsonParser; -public class UserHandler extends HttpUploadProcessor { +public class UserHandler extends HttpPushProcessor { @Override - public void process(String contentType, Socket client, String method) { + public void process(String path, String method, RemoteClient client, String contentType) throws IOException { try { // Load manager AccountManager manager = AccountManager.getInstance(); @@ -21,8 +23,7 @@ public void process(String contentType, Socket client, String method) { // Parse JWT payload String token = this.getHeader("Authorization").substring("Bearer ".length()); if (token.isBlank()) { - this.setResponseCode(403); - this.setResponseMessage("Access denied"); + this.setResponseStatus(403, "Access denied"); return; } @@ -30,8 +31,7 @@ public void process(String contentType, Socket client, String method) { String verifyD = token.split("\\.")[0] + "." + token.split("\\.")[1]; String sig = token.split("\\.")[2]; if (!Centuria.verify(verifyD.getBytes("UTF-8"), Base64.getUrlDecoder().decode(sig))) { - this.setResponseCode(403); - this.setResponseMessage("Access denied"); + this.setResponseStatus(403, "Access denied"); return; } @@ -40,8 +40,7 @@ public void process(String contentType, Socket client, String method) { .parseString(new String(Base64.getUrlDecoder().decode(token.split("\\.")[1]), "UTF-8")) .getAsJsonObject(); if (!jwtPl.has("exp") || jwtPl.get("exp").getAsLong() < System.currentTimeMillis() / 1000) { - this.setResponseCode(403); - this.setResponseMessage("Access denied"); + this.setResponseStatus(403, "Access denied"); return; } @@ -52,8 +51,7 @@ public void process(String contentType, Socket client, String method) { // Find account CenturiaAccount acc = manager.getAccount(payload.get("uuid").getAsString()); if (acc == null) { - this.setResponseCode(401); - this.setResponseMessage("Access denied"); + this.setResponseStatus(401, "Unauthorized"); return; } @@ -61,27 +59,26 @@ public void process(String contentType, Socket client, String method) { JsonObject privacy = acc.getPrivacySettings(); JsonObject response = new JsonObject(); response.addProperty("country_code", "US"); - response.addProperty("display_name", acc.getDisplayName()); + response.addProperty("display_name", GameServer.getPlayerNameWithPrefix(acc)); response.addProperty("enhanced_customization", true); response.addProperty("language", "en"); response.add("privacy", privacy); response.addProperty("username", acc.getLoginName()); response.addProperty("uuid", acc.getAccountID()); - setBody("text/json", response.toString()); + setResponseContent("text/json", response.toString()); } catch (Exception e) { - setResponseCode(500); - setResponseMessage("Internal Server Error"); - Centuria.logger.error(getRequest().path + " failed: 500: Internal Server Error", e); + setResponseStatus(500, "Internal Server Error"); + Centuria.logger.error(getRequest().getRequestPath() + " failed: 500: Internal Server Error", e); } } @Override - public boolean supportsGet() { + public boolean supportsNonPush() { return true; } @Override - public HttpUploadProcessor createNewInstance() { + public HttpPushProcessor createNewInstance() { return new UserHandler(); } diff --git a/src/main/java/org/asf/centuria/networking/http/api/XPDetailsHandler.java b/src/main/java/org/asf/centuria/networking/http/api/XPDetailsHandler.java index 661da226..b135627d 100644 --- a/src/main/java/org/asf/centuria/networking/http/api/XPDetailsHandler.java +++ b/src/main/java/org/asf/centuria/networking/http/api/XPDetailsHandler.java @@ -1,28 +1,28 @@ package org.asf.centuria.networking.http.api; import java.io.ByteArrayOutputStream; -import java.net.Socket; +import java.io.IOException; import java.util.Base64; import org.asf.centuria.Centuria; import org.asf.centuria.accounts.AccountManager; import org.asf.centuria.accounts.CenturiaAccount; -import org.asf.rats.ConnectiveHTTPServer; -import org.asf.rats.processors.HttpUploadProcessor; +import org.asf.connective.RemoteClient; +import org.asf.connective.processors.HttpPushProcessor; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; -public class XPDetailsHandler extends HttpUploadProcessor { +public class XPDetailsHandler extends HttpPushProcessor { @Override - public void process(String contentType, Socket client, String method) { + public void process(String path, String method, RemoteClient client, String contentType) throws IOException { try { // Parse body ByteArrayOutputStream strm = new ByteArrayOutputStream(); - ConnectiveHTTPServer.transferRequestBody(getHeaders(), getRequestBodyStream(), strm); + getRequest().transferRequestBody(strm); byte[] body = strm.toByteArray(); strm.close(); @@ -33,8 +33,7 @@ public void process(String contentType, Socket client, String method) { String verifyD = token.split("\\.")[0] + "." + token.split("\\.")[1]; String sig = token.split("\\.")[2]; if (!Centuria.verify(verifyD.getBytes("UTF-8"), Base64.getUrlDecoder().decode(sig))) { - this.setResponseCode(401); - this.setResponseMessage("Access denied"); + this.setResponseStatus(401, "Unauthorized"); return; } @@ -43,8 +42,7 @@ public void process(String contentType, Socket client, String method) { .parseString(new String(Base64.getUrlDecoder().decode(token.split("\\.")[1]), "UTF-8")) .getAsJsonObject(); if (!jwtPl.has("exp") || jwtPl.get("exp").getAsLong() < System.currentTimeMillis() / 1000) { - this.setResponseCode(401); - this.setResponseMessage("Access denied"); + this.setResponseStatus(401, "Unauthorized"); return; } @@ -75,16 +73,15 @@ public void process(String contentType, Socket client, String method) { // Send response response.add("found", found); response.add("not_found", req); - setBody("text/json", response.toString()); + setResponseContent("text/json", response.toString()); } catch (Exception e) { - setResponseCode(500); - setResponseMessage("Internal Server Error"); - Centuria.logger.error(getRequest().path + " failed: 500: Internal Server Error", e); + setResponseStatus(500, "Internal Server Error"); + Centuria.logger.error(getRequest().getRequestPath() + " failed: 500: Internal Server Error", e); } } @Override - public HttpUploadProcessor createNewInstance() { + public HttpPushProcessor createNewInstance() { return new XPDetailsHandler(); } diff --git a/src/main/java/org/asf/centuria/networking/http/api/custom/ChangeDisplayNameHandler.java b/src/main/java/org/asf/centuria/networking/http/api/custom/ChangeDisplayNameHandler.java index 5ca5c428..5ddfe9af 100644 --- a/src/main/java/org/asf/centuria/networking/http/api/custom/ChangeDisplayNameHandler.java +++ b/src/main/java/org/asf/centuria/networking/http/api/custom/ChangeDisplayNameHandler.java @@ -1,7 +1,7 @@ package org.asf.centuria.networking.http.api.custom; import java.io.ByteArrayOutputStream; -import java.net.Socket; +import java.io.IOException; import java.util.Base64; import java.util.stream.Stream; @@ -9,37 +9,35 @@ import org.asf.centuria.accounts.AccountManager; import org.asf.centuria.accounts.CenturiaAccount; import org.asf.centuria.networking.chatserver.networking.SendMessage; -import org.asf.rats.ConnectiveHTTPServer; -import org.asf.rats.processors.HttpUploadProcessor; +import org.asf.connective.RemoteClient; +import org.asf.connective.processors.HttpPushProcessor; import com.google.gson.JsonObject; import com.google.gson.JsonParser; -public class ChangeDisplayNameHandler extends HttpUploadProcessor { +public class ChangeDisplayNameHandler extends HttpPushProcessor { private static String[] nameBlacklist = new String[] { "kit", "kitsendragn", "kitsendragon", "fera", "fero", "wwadmin", "ayli", "komodorihero", "wwsam", "blinky", "fer.ocity" }; @Override - public void process(String contentType, Socket client, String method) { + public void process(String path, String method, RemoteClient client, String contentType) throws IOException { try { - if (!getRequest().method.equalsIgnoreCase("post")) { - this.setResponseCode(400); - this.setResponseMessage("Bad request"); + if (!method.equalsIgnoreCase("post")) { + this.setResponseStatus(400, "Bad request"); return; } // Parse body ByteArrayOutputStream strm = new ByteArrayOutputStream(); - ConnectiveHTTPServer.transferRequestBody(getHeaders(), getRequestBodyStream(), strm); + getRequest().transferRequestBody(strm); byte[] body = strm.toByteArray(); strm.close(); // Parse body JsonObject request = JsonParser.parseString(new String(body, "UTF-8")).getAsJsonObject(); if (!request.has("new_display_name")) { - this.setResponseCode(400); - this.setResponseMessage("Bad request"); + this.setResponseStatus(400, "Bad request"); return; } @@ -49,15 +47,13 @@ public void process(String contentType, Socket client, String method) { // Parse JWT payload String token = this.getHeader("Authorization").substring("Bearer ".length()); if (token.isBlank()) { - this.setResponseCode(403); - this.setResponseMessage("Access denied"); + this.setResponseStatus(403, "Access denied"); return; } // Parse token if (token.isBlank()) { - this.setResponseCode(403); - this.setResponseMessage("Access denied"); + this.setResponseStatus(403, "Access denied"); return; } @@ -65,8 +61,7 @@ public void process(String contentType, Socket client, String method) { String verifyD = token.split("\\.")[0] + "." + token.split("\\.")[1]; String sig = token.split("\\.")[2]; if (!Centuria.verify(verifyD.getBytes("UTF-8"), Base64.getUrlDecoder().decode(sig))) { - this.setResponseCode(403); - this.setResponseMessage("Access denied"); + this.setResponseStatus(403, "Access denied"); return; } @@ -75,8 +70,7 @@ public void process(String contentType, Socket client, String method) { .parseString(new String(Base64.getUrlDecoder().decode(token.split("\\.")[1]), "UTF-8")) .getAsJsonObject(); if (!jwtPl.has("exp") || jwtPl.get("exp").getAsLong() < System.currentTimeMillis() / 1000) { - this.setResponseCode(403); - this.setResponseMessage("Access denied"); + this.setResponseStatus(403, "Access denied"); return; } @@ -90,16 +84,15 @@ public void process(String contentType, Socket client, String method) { // Check existence if (id == null) { // Invalid details - this.setBody("text/json", "{\"error\":\"invalid_credential\"}"); - this.setResponseCode(422); + this.setResponseContent("text/json", "{\"error\":\"invalid_credential\"}"); + this.setResponseStatus(401, "Unauthorized"); return; } // Find account CenturiaAccount acc = manager.getAccount(id); if (acc == null) { - this.setResponseCode(401); - this.setResponseMessage("Access denied"); + this.setResponseStatus(401, "Unauthorized"); return; } @@ -133,7 +126,7 @@ public void process(String contentType, Socket client, String method) { response.addProperty("status", "failure"); response.addProperty("error", "invalid_display_name"); response.addProperty("error_message", "Invalid display name."); - setBody("text/json", response.toString()); + setResponseContent("text/json", response.toString()); return; } @@ -144,7 +137,7 @@ public void process(String contentType, Socket client, String method) { response.addProperty("error", "invalid_display_name"); response.addProperty("error_message", "Invalid display name: this name may not be used as it may not be appropriate."); - setBody("text/json", response.toString()); + setResponseContent("text/json", response.toString()); return; } } @@ -157,7 +150,7 @@ public void process(String contentType, Socket client, String method) { response.addProperty("error", "invalid_display_name"); response.addProperty("error_message", "Invalid display name: this name may not be used as it may not be appropriate."); - setBody("text/json", response.toString()); + setResponseContent("text/json", response.toString()); return; } } @@ -168,21 +161,20 @@ public void process(String contentType, Socket client, String method) { response.addProperty("name", acc.getDisplayName()); response.addProperty("uuid", id); response.addProperty("updated", true); - setBody("text/json", response.toString()); + setResponseContent("text/json", response.toString()); } catch (Exception e) { - setResponseCode(500); - setResponseMessage("Internal Server Error"); - Centuria.logger.error(getRequest().path + " failed: 500: Internal Server Error", e); + setResponseStatus(500, "Internal Server Error"); + Centuria.logger.error(getRequest().getRequestPath() + " failed: 500: Internal Server Error", e); } } @Override - public boolean supportsGet() { + public boolean supportsNonPush() { return true; } @Override - public HttpUploadProcessor createNewInstance() { + public HttpPushProcessor createNewInstance() { return new ChangeDisplayNameHandler(); } diff --git a/src/main/java/org/asf/centuria/networking/http/api/custom/ChangeLoginNameHandler.java b/src/main/java/org/asf/centuria/networking/http/api/custom/ChangeLoginNameHandler.java index e217660d..256a8e6e 100644 --- a/src/main/java/org/asf/centuria/networking/http/api/custom/ChangeLoginNameHandler.java +++ b/src/main/java/org/asf/centuria/networking/http/api/custom/ChangeLoginNameHandler.java @@ -1,7 +1,7 @@ package org.asf.centuria.networking.http.api.custom; import java.io.ByteArrayOutputStream; -import java.net.Socket; +import java.io.IOException; import java.util.Base64; import java.util.stream.Stream; @@ -9,37 +9,35 @@ import org.asf.centuria.accounts.AccountManager; import org.asf.centuria.accounts.CenturiaAccount; import org.asf.centuria.networking.chatserver.networking.SendMessage; -import org.asf.rats.ConnectiveHTTPServer; -import org.asf.rats.processors.HttpUploadProcessor; +import org.asf.connective.RemoteClient; +import org.asf.connective.processors.HttpPushProcessor; import com.google.gson.JsonObject; import com.google.gson.JsonParser; -public class ChangeLoginNameHandler extends HttpUploadProcessor { +public class ChangeLoginNameHandler extends HttpPushProcessor { private static String[] nameBlacklist = new String[] { "kit", "kitsendragn", "kitsendragon", "fera", "fero", "wwadmin", "ayli", "komodorihero", "wwsam", "blinky", "fer.ocity" }; @Override - public void process(String contentType, Socket client, String method) { + public void process(String path, String method, RemoteClient client, String contentType) throws IOException { try { - if (!getRequest().method.equalsIgnoreCase("post")) { - this.setResponseCode(400); - this.setResponseMessage("Bad request"); + if (!method.equalsIgnoreCase("post")) { + this.setResponseStatus(400, "Bad request"); return; } // Parse body ByteArrayOutputStream strm = new ByteArrayOutputStream(); - ConnectiveHTTPServer.transferRequestBody(getHeaders(), getRequestBodyStream(), strm); + getRequest().transferRequestBody(strm); byte[] body = strm.toByteArray(); strm.close(); // Parse body JsonObject request = JsonParser.parseString(new String(body, "UTF-8")).getAsJsonObject(); if (!request.has("new_login_name")) { - this.setResponseCode(400); - this.setResponseMessage("Bad request"); + this.setResponseStatus(400, "Bad request"); return; } @@ -49,15 +47,13 @@ public void process(String contentType, Socket client, String method) { // Parse JWT payload String token = this.getHeader("Authorization").substring("Bearer ".length()); if (token.isBlank()) { - this.setResponseCode(403); - this.setResponseMessage("Access denied"); + this.setResponseStatus(403, "Access denied"); return; } // Parse token if (token.isBlank()) { - this.setResponseCode(403); - this.setResponseMessage("Access denied"); + this.setResponseStatus(403, "Access denied"); return; } @@ -65,8 +61,7 @@ public void process(String contentType, Socket client, String method) { String verifyD = token.split("\\.")[0] + "." + token.split("\\.")[1]; String sig = token.split("\\.")[2]; if (!Centuria.verify(verifyD.getBytes("UTF-8"), Base64.getUrlDecoder().decode(sig))) { - this.setResponseCode(403); - this.setResponseMessage("Access denied"); + this.setResponseStatus(403, "Access denied"); return; } @@ -75,8 +70,7 @@ public void process(String contentType, Socket client, String method) { .parseString(new String(Base64.getUrlDecoder().decode(token.split("\\.")[1]), "UTF-8")) .getAsJsonObject(); if (!jwtPl.has("exp") || jwtPl.get("exp").getAsLong() < System.currentTimeMillis() / 1000) { - this.setResponseCode(403); - this.setResponseMessage("Access denied"); + this.setResponseStatus(403, "Access denied"); return; } @@ -90,16 +84,15 @@ public void process(String contentType, Socket client, String method) { // Check existence if (id == null) { // Invalid details - this.setBody("text/json", "{\"error\":\"invalid_credential\"}"); - this.setResponseCode(422); + this.setResponseContent("text/json", "{\"error\":\"invalid_credential\"}"); + this.setResponseStatus(401, "Unauthorized"); return; } // Find account CenturiaAccount acc = manager.getAccount(id); if (acc == null) { - this.setResponseCode(401); - this.setResponseMessage("Access denied"); + this.setResponseStatus(401, "Unauthorized"); return; } @@ -127,7 +120,7 @@ public void process(String contentType, Socket client, String method) { response.addProperty("status", "failure"); response.addProperty("error", "invalid_login_name"); response.addProperty("error_message", "Invalid login name."); - setBody("text/json", response.toString()); + setResponseContent("text/json", response.toString()); return; } @@ -138,7 +131,7 @@ public void process(String contentType, Socket client, String method) { response.addProperty("error", "invalid_login_name"); response.addProperty("error_message", "Invalid login name: this name may not be used as it may not be appropriate."); - setBody("text/json", response.toString()); + setResponseContent("text/json", response.toString()); return; } } @@ -151,7 +144,7 @@ public void process(String contentType, Socket client, String method) { response.addProperty("error", "invald_login_name"); response.addProperty("error_message", "Invalid login name: this name may not be used as it may not be appropriate."); - setBody("text/json", response.toString()); + setResponseContent("text/json", response.toString()); return; } } @@ -162,21 +155,20 @@ public void process(String contentType, Socket client, String method) { response.addProperty("login_name", acc.getLoginName()); response.addProperty("uuid", id); response.addProperty("updated", true); - setBody("text/json", response.toString()); + setResponseContent("text/json", response.toString()); } catch (Exception e) { - setResponseCode(500); - setResponseMessage("Internal Server Error"); - Centuria.logger.error(getRequest().path + " failed: 500: Internal Server Error", e); + setResponseStatus(500, "Internal Server Error"); + Centuria.logger.error(getRequest().getRequestPath() + " failed: 500: Internal Server Error", e); } } @Override - public boolean supportsGet() { + public boolean supportsNonPush() { return true; } @Override - public HttpUploadProcessor createNewInstance() { + public HttpPushProcessor createNewInstance() { return new ChangeLoginNameHandler(); } diff --git a/src/main/java/org/asf/centuria/networking/http/api/custom/ChangePasswordHandler.java b/src/main/java/org/asf/centuria/networking/http/api/custom/ChangePasswordHandler.java index eecf52e8..98a08728 100644 --- a/src/main/java/org/asf/centuria/networking/http/api/custom/ChangePasswordHandler.java +++ b/src/main/java/org/asf/centuria/networking/http/api/custom/ChangePasswordHandler.java @@ -1,39 +1,37 @@ package org.asf.centuria.networking.http.api.custom; import java.io.ByteArrayOutputStream; -import java.net.Socket; +import java.io.IOException; import java.util.Base64; import org.asf.centuria.Centuria; import org.asf.centuria.accounts.AccountManager; import org.asf.centuria.accounts.CenturiaAccount; -import org.asf.rats.ConnectiveHTTPServer; -import org.asf.rats.processors.HttpUploadProcessor; +import org.asf.connective.RemoteClient; +import org.asf.connective.processors.HttpPushProcessor; import com.google.gson.JsonObject; import com.google.gson.JsonParser; -public class ChangePasswordHandler extends HttpUploadProcessor { +public class ChangePasswordHandler extends HttpPushProcessor { @Override - public void process(String contentType, Socket client, String method) { + public void process(String path, String method, RemoteClient client, String contentType) throws IOException { try { - if (!getRequest().method.equalsIgnoreCase("post")) { - this.setResponseCode(400); - this.setResponseMessage("Bad request"); + if (!method.equalsIgnoreCase("post")) { + this.setResponseStatus(400, "Bad request"); return; } // Parse body ByteArrayOutputStream strm = new ByteArrayOutputStream(); - ConnectiveHTTPServer.transferRequestBody(getHeaders(), getRequestBodyStream(), strm); + getRequest().transferRequestBody(strm); byte[] body = strm.toByteArray(); strm.close(); // Parse body JsonObject request = JsonParser.parseString(new String(body, "UTF-8")).getAsJsonObject(); if (!request.has("password")) { - this.setResponseCode(400); - this.setResponseMessage("Bad request"); + this.setResponseStatus(400, "Bad request"); return; } @@ -43,15 +41,13 @@ public void process(String contentType, Socket client, String method) { // Parse JWT payload String token = this.getHeader("Authorization").substring("Bearer ".length()); if (token.isBlank()) { - this.setResponseCode(403); - this.setResponseMessage("Access denied"); + this.setResponseStatus(403, "Access denied"); return; } // Parse token if (token.isBlank()) { - this.setResponseCode(403); - this.setResponseMessage("Access denied"); + this.setResponseStatus(403, "Access denied"); return; } @@ -59,8 +55,7 @@ public void process(String contentType, Socket client, String method) { String verifyD = token.split("\\.")[0] + "." + token.split("\\.")[1]; String sig = token.split("\\.")[2]; if (!Centuria.verify(verifyD.getBytes("UTF-8"), Base64.getUrlDecoder().decode(sig))) { - this.setResponseCode(403); - this.setResponseMessage("Access denied"); + this.setResponseStatus(403, "Access denied"); return; } @@ -69,8 +64,7 @@ public void process(String contentType, Socket client, String method) { .parseString(new String(Base64.getUrlDecoder().decode(token.split("\\.")[1]), "UTF-8")) .getAsJsonObject(); if (!jwtPl.has("exp") || jwtPl.get("exp").getAsLong() < System.currentTimeMillis() / 1000) { - this.setResponseCode(403); - this.setResponseMessage("Access denied"); + this.setResponseStatus(403, "Access denied"); return; } @@ -84,16 +78,15 @@ public void process(String contentType, Socket client, String method) { // Check existence if (id == null) { // Invalid details - this.setBody("text/json", "{\"error\":\"invalid_credential\"}"); - this.setResponseCode(422); + this.setResponseContent("text/json", "{\"error\":\"invalid_credential\"}"); + this.setResponseStatus(401, "Unauthorized"); return; } // Find account CenturiaAccount acc = manager.getAccount(id); if (acc == null) { - this.setResponseCode(401); - this.setResponseMessage("Access denied"); + this.setResponseStatus(401, "Unauthorized"); return; } @@ -105,21 +98,20 @@ public void process(String contentType, Socket client, String method) { response.addProperty("status", "success"); response.addProperty("uuid", id); response.addProperty("updated", true); - setBody("text/json", response.toString()); + setResponseContent("text/json", response.toString()); } catch (Exception e) { - setResponseCode(500); - setResponseMessage("Internal Server Error"); - Centuria.logger.error(getRequest().path + " failed: 500: Internal Server Error", e); + setResponseStatus(500, "Internal Server Error"); + Centuria.logger.error(getRequest().getRequestPath() + " failed: 500: Internal Server Error", e); } } @Override - public boolean supportsGet() { + public boolean supportsNonPush() { return true; } @Override - public HttpUploadProcessor createNewInstance() { + public HttpPushProcessor createNewInstance() { return new ChangePasswordHandler(); } diff --git a/src/main/java/org/asf/centuria/networking/http/api/custom/DeleteAccountHandler.java b/src/main/java/org/asf/centuria/networking/http/api/custom/DeleteAccountHandler.java index 37c15b02..332a7c14 100644 --- a/src/main/java/org/asf/centuria/networking/http/api/custom/DeleteAccountHandler.java +++ b/src/main/java/org/asf/centuria/networking/http/api/custom/DeleteAccountHandler.java @@ -1,19 +1,20 @@ package org.asf.centuria.networking.http.api.custom; -import java.net.Socket; +import java.io.IOException; import java.util.Base64; import org.asf.centuria.Centuria; import org.asf.centuria.accounts.AccountManager; import org.asf.centuria.accounts.CenturiaAccount; -import org.asf.rats.processors.HttpUploadProcessor; +import org.asf.connective.RemoteClient; +import org.asf.connective.processors.HttpPushProcessor; import com.google.gson.JsonObject; import com.google.gson.JsonParser; -public class DeleteAccountHandler extends HttpUploadProcessor { +public class DeleteAccountHandler extends HttpPushProcessor { @Override - public void process(String contentType, Socket client, String method) { + public void process(String path, String method, RemoteClient client, String contentType) throws IOException { try { // Load manager AccountManager manager = AccountManager.getInstance(); @@ -21,15 +22,13 @@ public void process(String contentType, Socket client, String method) { // Parse JWT payload String token = this.getHeader("Authorization").substring("Bearer ".length()); if (token.isBlank()) { - this.setResponseCode(403); - this.setResponseMessage("Access denied"); + this.setResponseStatus(403, "Access denied"); return; } // Parse token if (token.isBlank()) { - this.setResponseCode(403); - this.setResponseMessage("Access denied"); + this.setResponseStatus(403, "Access denied"); return; } @@ -37,8 +36,7 @@ public void process(String contentType, Socket client, String method) { String verifyD = token.split("\\.")[0] + "." + token.split("\\.")[1]; String sig = token.split("\\.")[2]; if (!Centuria.verify(verifyD.getBytes("UTF-8"), Base64.getUrlDecoder().decode(sig))) { - this.setResponseCode(403); - this.setResponseMessage("Access denied"); + this.setResponseStatus(403, "Access denied"); return; } @@ -47,8 +45,7 @@ public void process(String contentType, Socket client, String method) { .parseString(new String(Base64.getUrlDecoder().decode(token.split("\\.")[1]), "UTF-8")) .getAsJsonObject(); if (!jwtPl.has("exp") || jwtPl.get("exp").getAsLong() < System.currentTimeMillis() / 1000) { - this.setResponseCode(403); - this.setResponseMessage("Access denied"); + this.setResponseStatus(403, "Access denied"); return; } @@ -62,16 +59,15 @@ public void process(String contentType, Socket client, String method) { // Check existence if (id == null) { // Invalid details - this.setBody("text/json", "{\"error\":\"invalid_credential\"}"); - this.setResponseCode(422); + this.setResponseContent("text/json", "{\"error\":\"invalid_credential\"}"); + this.setResponseStatus(401, "Unauthorized"); return; } // Find account CenturiaAccount acc = manager.getAccount(id); if (acc == null) { - this.setResponseCode(401); - this.setResponseMessage("Access denied"); + this.setResponseStatus(401, "Unauthorized"); return; } @@ -81,21 +77,20 @@ public void process(String contentType, Socket client, String method) { response.addProperty("status", "success"); response.addProperty("uuid", id); response.addProperty("deleted", true); - setBody("text/json", response.toString()); + setResponseContent("text/json", response.toString()); } catch (Exception e) { - setResponseCode(500); - setResponseMessage("Internal Server Error"); - Centuria.logger.error(getRequest().path + " failed: 500: Internal Server Error", e); + setResponseStatus(500, "Internal Server Error"); + Centuria.logger.error(getRequest().getRequestPath() + " failed: 500: Internal Server Error", e); } } @Override - public boolean supportsGet() { + public boolean supportsNonPush() { return true; } @Override - public HttpUploadProcessor createNewInstance() { + public HttpPushProcessor createNewInstance() { return new DeleteAccountHandler(); } diff --git a/src/main/java/org/asf/centuria/networking/http/api/custom/ListPlayersHandler.java b/src/main/java/org/asf/centuria/networking/http/api/custom/ListPlayersHandler.java index 45ac19fc..d10fab87 100644 --- a/src/main/java/org/asf/centuria/networking/http/api/custom/ListPlayersHandler.java +++ b/src/main/java/org/asf/centuria/networking/http/api/custom/ListPlayersHandler.java @@ -1,20 +1,21 @@ package org.asf.centuria.networking.http.api.custom; +import java.io.IOException; import java.io.InputStream; -import java.net.Socket; import java.util.HashMap; import org.asf.centuria.Centuria; import org.asf.centuria.entities.players.Player; import org.asf.centuria.packets.xt.gameserver.inventory.InventoryItemDownloadPacket; -import org.asf.rats.processors.HttpUploadProcessor; +import org.asf.connective.RemoteClient; +import org.asf.connective.processors.HttpPushProcessor; import com.google.gson.JsonObject; import com.google.gson.JsonParser; -public class ListPlayersHandler extends HttpUploadProcessor { +public class ListPlayersHandler extends HttpPushProcessor { @Override - public void process(String contentType, Socket client, String method) { + public void process(String path, String method, RemoteClient client, String contentType) throws IOException { try { // Load spawn helper JsonObject helper = null; @@ -45,22 +46,21 @@ else if (helper.has(Integer.toString(plr.levelID))) JsonObject mapData = new JsonObject(); maps.forEach((k, v) -> mapData.addProperty(k, v)); response.add("maps", mapData); - setBody("text/json", response.toString()); - getResponse().setHeader("Access-Control-Allow-Origin", "https://aerialworks.ddns.net"); + setResponseContent("text/json", response.toString()); + getResponse().addHeader("Access-Control-Allow-Origin", "https://emuferal.ddns.net"); } catch (Exception e) { - setResponseCode(500); - setResponseMessage("Internal Server Error"); - Centuria.logger.error(getRequest().path + " failed: 500: Internal Server Error", e); + setResponseStatus(500, "Internal Server Error"); + Centuria.logger.error(getRequest().getRequestPath() + " failed: 500: Internal Server Error", e); } } @Override - public boolean supportsGet() { + public boolean supportsNonPush() { return true; } @Override - public HttpUploadProcessor createNewInstance() { + public HttpPushProcessor createNewInstance() { return new ListPlayersHandler(); } diff --git a/src/main/java/org/asf/centuria/networking/http/api/custom/LoginRefreshHandler.java b/src/main/java/org/asf/centuria/networking/http/api/custom/LoginRefreshHandler.java index fe84e50d..d030625e 100644 --- a/src/main/java/org/asf/centuria/networking/http/api/custom/LoginRefreshHandler.java +++ b/src/main/java/org/asf/centuria/networking/http/api/custom/LoginRefreshHandler.java @@ -1,6 +1,6 @@ package org.asf.centuria.networking.http.api.custom; -import java.net.Socket; +import java.io.IOException; import java.util.Base64; import java.util.UUID; @@ -8,14 +8,15 @@ import org.asf.centuria.accounts.AccountManager; import org.asf.centuria.accounts.CenturiaAccount; import org.asf.centuria.networking.http.api.FallbackAPIProcessor; -import org.asf.rats.processors.HttpUploadProcessor; +import org.asf.connective.RemoteClient; +import org.asf.connective.processors.HttpPushProcessor; import com.google.gson.JsonObject; import com.google.gson.JsonParser; -public class LoginRefreshHandler extends HttpUploadProcessor { +public class LoginRefreshHandler extends HttpPushProcessor { @Override - public void process(String contentType, Socket client, String method) { + public void process(String path, String method, RemoteClient client, String contentType) throws IOException { try { // Load manager AccountManager manager = AccountManager.getInstance(); @@ -23,15 +24,13 @@ public void process(String contentType, Socket client, String method) { // Parse JWT payload String token = this.getHeader("Authorization").substring("Bearer ".length()); if (token.isBlank()) { - this.setResponseCode(403); - this.setResponseMessage("Access denied"); + this.setResponseStatus(403, "Access denied"); return; } // Parse token if (token.isBlank()) { - this.setResponseCode(403); - this.setResponseMessage("Access denied"); + this.setResponseStatus(403, "Access denied"); return; } @@ -39,8 +38,7 @@ public void process(String contentType, Socket client, String method) { String verifyD = token.split("\\.")[0] + "." + token.split("\\.")[1]; String sig = token.split("\\.")[2]; if (!Centuria.verify(verifyD.getBytes("UTF-8"), Base64.getUrlDecoder().decode(sig))) { - this.setResponseCode(403); - this.setResponseMessage("Access denied"); + this.setResponseStatus(403, "Access denied"); return; } @@ -49,8 +47,7 @@ public void process(String contentType, Socket client, String method) { .parseString(new String(Base64.getUrlDecoder().decode(token.split("\\.")[1]), "UTF-8")) .getAsJsonObject(); if (!jwtPl.has("exp") || jwtPl.get("exp").getAsLong() < System.currentTimeMillis() / 1000) { - this.setResponseCode(403); - this.setResponseMessage("Access denied"); + this.setResponseStatus(403, "Access denied"); return; } @@ -64,16 +61,15 @@ public void process(String contentType, Socket client, String method) { // Check existence if (id == null) { // Invalid details - this.setBody("text/json", "{\"error\":\"invalid_credential\"}"); - this.setResponseCode(422); + this.setResponseContent("text/json", "{\"error\":\"invalid_credential\"}"); + this.setResponseStatus(401, "Unauthorized"); return; } // Find account CenturiaAccount acc = manager.getAccount(id); if (acc == null) { - this.setResponseCode(401); - this.setResponseMessage("Access denied"); + this.setResponseStatus(401, "Unauthorized"); return; } @@ -124,21 +120,20 @@ public void process(String contentType, Socket client, String method) { // Add other fields response.addProperty("rename_required", !manager.hasPassword(id) || changeName || acc.isRenameRequired()); - setBody("text/json", response.toString()); + setResponseContent("text/json", response.toString()); } catch (Exception e) { - setResponseCode(500); - setResponseMessage("Internal Server Error"); - Centuria.logger.error(getRequest().path + " failed: 500: Internal Server Error", e); + setResponseStatus(500, "Internal Server Error"); + Centuria.logger.error(getRequest().getRequestPath() + " failed: 500: Internal Server Error", e); } } @Override - public boolean supportsGet() { + public boolean supportsNonPush() { return true; } @Override - public HttpUploadProcessor createNewInstance() { + public HttpPushProcessor createNewInstance() { return new LoginRefreshHandler(); } diff --git a/src/main/java/org/asf/centuria/networking/http/api/custom/PlayerDataDownloadHandler.java b/src/main/java/org/asf/centuria/networking/http/api/custom/PlayerDataDownloadHandler.java index 9eecb728..7a965b16 100644 --- a/src/main/java/org/asf/centuria/networking/http/api/custom/PlayerDataDownloadHandler.java +++ b/src/main/java/org/asf/centuria/networking/http/api/custom/PlayerDataDownloadHandler.java @@ -4,7 +4,6 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; -import java.net.Socket; import java.util.Base64; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; @@ -13,14 +12,15 @@ import org.asf.centuria.accounts.AccountManager; import org.asf.centuria.accounts.CenturiaAccount; import org.asf.centuria.accounts.PlayerInventory; -import org.asf.rats.processors.HttpUploadProcessor; +import org.asf.connective.RemoteClient; +import org.asf.connective.processors.HttpPushProcessor; import com.google.gson.JsonObject; import com.google.gson.JsonParser; -public class PlayerDataDownloadHandler extends HttpUploadProcessor { +public class PlayerDataDownloadHandler extends HttpPushProcessor { @Override - public void process(String contentType, Socket client, String method) { + public void process(String pth, String method, RemoteClient client, String contentType) throws IOException { try { // Load manager AccountManager manager = AccountManager.getInstance(); @@ -28,15 +28,13 @@ public void process(String contentType, Socket client, String method) { // Parse JWT payload String token = this.getHeader("Authorization").substring("Bearer ".length()); if (token.isBlank()) { - this.setResponseCode(403); - this.setResponseMessage("Access denied"); + this.setResponseStatus(403, "Access denied"); return; } // Parse token if (token.isBlank()) { - this.setResponseCode(403); - this.setResponseMessage("Access denied"); + this.setResponseStatus(403, "Access denied"); return; } @@ -44,8 +42,7 @@ public void process(String contentType, Socket client, String method) { String verifyD = token.split("\\.")[0] + "." + token.split("\\.")[1]; String sig = token.split("\\.")[2]; if (!Centuria.verify(verifyD.getBytes("UTF-8"), Base64.getUrlDecoder().decode(sig))) { - this.setResponseCode(403); - this.setResponseMessage("Access denied"); + this.setResponseStatus(403, "Access denied"); return; } @@ -54,8 +51,7 @@ public void process(String contentType, Socket client, String method) { .parseString(new String(Base64.getUrlDecoder().decode(token.split("\\.")[1]), "UTF-8")) .getAsJsonObject(); if (!jwtPl.has("exp") || jwtPl.get("exp").getAsLong() < System.currentTimeMillis() / 1000) { - this.setResponseCode(403); - this.setResponseMessage("Access denied"); + this.setResponseStatus(403, "Access denied"); return; } @@ -69,26 +65,24 @@ public void process(String contentType, Socket client, String method) { // Check existence if (id == null) { // Invalid details - this.setBody("text/json", "{\"error\":\"invalid_account\"}"); - this.setResponseCode(422); + this.setResponseContent("text/json", "{\"error\":\"invalid_account\"}"); + this.setResponseStatus(401, "Unauthorized"); return; } // Find account CenturiaAccount acc = manager.getAccount(id); if (acc == null) { - this.setResponseCode(401); - this.setResponseMessage("Access denied"); + this.setResponseStatus(401, "Unauthorized"); return; } // Handle request - String path = getRequest().path.substring(path().length()); + String path = getRequest().getRequestPath().substring(path().length()); if (path.isEmpty()) { // Invalid request - this.setBody("text/json", "{\"error\":\"missing_item\"}"); - this.setResponseCode(400); - this.setResponseMessage("Bad request"); + this.setResponseContent("text/json", "{\"error\":\"missing_item\"}"); + this.setResponseStatus(400, "Bad request"); return; } path = path.substring(1); @@ -135,18 +129,16 @@ public void process(String contentType, Socket client, String method) { } else { if (!acc.getSaveSpecificInventory().containsItem(path)) { // Invalid request - this.setBody("text/json", "{\"error\":\"item_not_found\"}"); - this.setResponseCode(404); - this.setResponseMessage("Not found"); + this.setResponseContent("text/json", "{\"error\":\"item_not_found\"}"); + this.setResponseStatus(404, "Not found"); return; } else { - this.setBody("text/json", acc.getSaveSpecificInventory().getItem(path).toString()); + this.setResponseContent("text/json", acc.getSaveSpecificInventory().getItem(path).toString()); } } } catch (Exception e) { - setResponseCode(500); - setResponseMessage("Internal Server Error"); - Centuria.logger.error(getRequest().path + " failed: 500: Internal Server Error", e); + setResponseStatus(500, "Internal Server Error"); + Centuria.logger.error(getRequest().getRequestPath() + " failed: 500: Internal Server Error", e); } } @@ -163,7 +155,7 @@ private static void transferDataToZip(ZipOutputStream zip, String file, byte[] d } @Override - public boolean supportsGet() { + public boolean supportsNonPush() { return true; } @@ -173,7 +165,7 @@ public boolean supportsChildPaths() { } @Override - public HttpUploadProcessor createNewInstance() { + public HttpPushProcessor createNewInstance() { return new PlayerDataDownloadHandler(); } diff --git a/src/main/java/org/asf/centuria/networking/http/api/custom/RegistrationHandler.java b/src/main/java/org/asf/centuria/networking/http/api/custom/RegistrationHandler.java index bc409874..233e537d 100644 --- a/src/main/java/org/asf/centuria/networking/http/api/custom/RegistrationHandler.java +++ b/src/main/java/org/asf/centuria/networking/http/api/custom/RegistrationHandler.java @@ -3,7 +3,6 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; -import java.net.Socket; import java.util.ArrayList; import org.asf.centuria.Centuria; @@ -12,13 +11,13 @@ import org.asf.centuria.accounts.registration.RegistrationVerificationResult; import org.asf.centuria.accounts.registration.RegistrationVerificationStatus; import org.asf.centuria.packets.xt.gameserver.inventory.InventoryItemDownloadPacket; -import org.asf.rats.ConnectiveHTTPServer; -import org.asf.rats.processors.HttpUploadProcessor; +import org.asf.connective.RemoteClient; +import org.asf.connective.processors.HttpPushProcessor; import com.google.gson.JsonObject; import com.google.gson.JsonParser; -public class RegistrationHandler extends HttpUploadProcessor { +public class RegistrationHandler extends HttpPushProcessor { private static String[] nameBlacklist = new String[] { "kit", "kitsendragn", "kitsendragon", "fera", "fero", "wwadmin", "ayli", "komodorihero", "wwsam", "blinky", "fer.ocity" }; @@ -69,11 +68,10 @@ public class RegistrationHandler extends HttpUploadProcessor { } @Override - public void process(String contentType, Socket client, String method) { + public void process(String path, String method, RemoteClient client, String contentType) throws IOException { try { - if (!getRequest().method.equalsIgnoreCase("post")) { - this.setResponseCode(400); - this.setResponseMessage("Bad request"); + if (!method.equalsIgnoreCase("post")) { + this.setResponseStatus(400, "Bad request"); return; } @@ -82,15 +80,14 @@ public void process(String contentType, Socket client, String method) { // Parse body ByteArrayOutputStream strm = new ByteArrayOutputStream(); - ConnectiveHTTPServer.transferRequestBody(getHeaders(), getRequestBodyStream(), strm); + getRequest().transferRequestBody(strm); byte[] body = strm.toByteArray(); strm.close(); // Parse body JsonObject request = JsonParser.parseString(new String(body, "UTF-8")).getAsJsonObject(); if (!request.has("login_name") || !request.has("display_name") || !request.has("password")) { - this.setResponseCode(400); - this.setResponseMessage("Bad request"); + this.setResponseStatus(400, "Bad request"); return; } @@ -110,9 +107,8 @@ public void process(String contentType, Socket client, String method) { response.addProperty("error", "invalid_login_name"); response.addProperty("error_message", "Invalid login name: this name may not be used as it may not be appropriate."); - setBody("text/json", response.toString()); - this.setResponseCode(400); - this.setResponseMessage("Bad request"); + setResponseContent("text/json", response.toString()); + this.setResponseStatus(400, "Bad request"); return; } } @@ -125,9 +121,8 @@ public void process(String contentType, Socket client, String method) { response.addProperty("error", "invalid_display_name"); response.addProperty("error_message", "Invalid display name: this name may not be used as it may not be appropriate."); - setBody("text/json", response.toString()); - this.setResponseCode(400); - this.setResponseMessage("Bad request"); + setResponseContent("text/json", response.toString()); + this.setResponseStatus(400, "Bad request"); return; } } @@ -140,9 +135,8 @@ public void process(String contentType, Socket client, String method) { response.addProperty("error", "invalid_login_name"); response.addProperty("error_message", "Invalid login name: this name may not be used as it may not be appropriate."); - setBody("text/json", response.toString()); - this.setResponseCode(400); - this.setResponseMessage("Bad request"); + setResponseContent("text/json", response.toString()); + this.setResponseStatus(400, "Bad request"); return; } @@ -152,9 +146,8 @@ public void process(String contentType, Socket client, String method) { response.addProperty("error", "invalid_login_name"); response.addProperty("error_message", "Invalid login name: this name may not be used as it may not be appropriate."); - setBody("text/json", response.toString()); - this.setResponseCode(400); - this.setResponseMessage("Bad request"); + setResponseContent("text/json", response.toString()); + this.setResponseStatus(400, "Bad request"); return; } } @@ -167,9 +160,8 @@ public void process(String contentType, Socket client, String method) { response.addProperty("error", "invalid_display_name"); response.addProperty("error_message", "Invalid display name: this name may not be used as it may not be appropriate."); - setBody("text/json", response.toString()); - this.setResponseCode(400); - this.setResponseMessage("Bad request"); + setResponseContent("text/json", response.toString()); + this.setResponseStatus(400, "Bad request"); return; } @@ -179,9 +171,8 @@ public void process(String contentType, Socket client, String method) { response.addProperty("error", "invalid_display_name"); response.addProperty("error_message", "Invalid display name: this name may not be used as it may not be appropriate."); - setBody("text/json", response.toString()); - this.setResponseCode(400); - this.setResponseMessage("Bad request"); + setResponseContent("text/json", response.toString()); + this.setResponseStatus(400, "Bad request"); return; } } @@ -194,9 +185,8 @@ public void process(String contentType, Socket client, String method) { response.addProperty("status", "failure"); response.addProperty("error", "invalid_login_name"); response.addProperty("error_message", "Invalid login name."); - setBody("text/json", response.toString()); - this.setResponseCode(400); - this.setResponseMessage("Bad request"); + setResponseContent("text/json", response.toString()); + this.setResponseStatus(400, "Bad request"); return; } @@ -206,9 +196,8 @@ public void process(String contentType, Socket client, String method) { response.addProperty("status", "failure"); response.addProperty("error", "invalid_display_name"); response.addProperty("error_message", "Invalid display name."); - setBody("text/json", response.toString()); - this.setResponseCode(400); - this.setResponseMessage("Bad request"); + setResponseContent("text/json", response.toString()); + this.setResponseStatus(400, "Bad request"); return; } @@ -218,9 +207,8 @@ public void process(String contentType, Socket client, String method) { response.addProperty("status", "failure"); response.addProperty("error", "login_name_in_use"); response.addProperty("error_message", "Selected login name is already in use."); - setBody("text/json", response.toString()); - this.setResponseCode(400); - this.setResponseMessage("Bad request"); + setResponseContent("text/json", response.toString()); + this.setResponseStatus(400, "Bad request"); return; } @@ -230,9 +218,8 @@ public void process(String contentType, Socket client, String method) { response.addProperty("status", "failure"); response.addProperty("error", "display_name_in_use"); response.addProperty("error_message", "Selected display name is already in use."); - setBody("text/json", response.toString()); - this.setResponseCode(400); - this.setResponseMessage("Bad request"); + setResponseContent("text/json", response.toString()); + this.setResponseStatus(400, "Bad request"); return; } @@ -265,9 +252,8 @@ public void process(String contentType, Socket client, String method) { response.addProperty("status", "failure"); response.addProperty("error", "missing_verification"); response.addProperty("error_message", "This server requires registration verification."); - setBody("text/json", response.toString()); - this.setResponseCode(400); - this.setResponseMessage("Bad request"); + setResponseContent("text/json", response.toString()); + this.setResponseStatus(400, "Bad request"); return; } @@ -279,9 +265,8 @@ public void process(String contentType, Socket client, String method) { response.addProperty("status", "failure"); response.addProperty("error", "invalid_verification_method"); response.addProperty("error_message", "Verification method unsupported."); - setBody("text/json", response.toString()); - this.setResponseCode(400); - this.setResponseMessage("Bad request"); + setResponseContent("text/json", response.toString()); + this.setResponseStatus(400, "Bad request"); return; } @@ -344,27 +329,25 @@ public void process(String contentType, Socket client, String method) { response.addProperty("status", "failure"); response.addProperty("error", result.error); response.addProperty("error_message", result.errorMessage); - this.setResponseCode(400); - this.setResponseMessage("Bad request"); + this.setResponseStatus(400, "Bad request"); } } // Send response - setBody("text/json", response.toString()); + setResponseContent("text/json", response.toString()); } catch (Exception e) { - setResponseCode(500); - setResponseMessage("Internal Server Error"); - Centuria.logger.error(getRequest().path + " failed: 500: Internal Server Error", e); + setResponseStatus(500, "Internal Server Error"); + Centuria.logger.error(getRequest().getRequestPath() + " failed: 500: Internal Server Error", e); } } @Override - public boolean supportsGet() { + public boolean supportsNonPush() { return true; } @Override - public HttpUploadProcessor createNewInstance() { + public HttpPushProcessor createNewInstance() { return new RegistrationHandler(); } diff --git a/src/main/java/org/asf/centuria/networking/http/api/custom/SaveManagerHandler.java b/src/main/java/org/asf/centuria/networking/http/api/custom/SaveManagerHandler.java index 210ef24f..6c30ba53 100644 --- a/src/main/java/org/asf/centuria/networking/http/api/custom/SaveManagerHandler.java +++ b/src/main/java/org/asf/centuria/networking/http/api/custom/SaveManagerHandler.java @@ -1,41 +1,39 @@ package org.asf.centuria.networking.http.api.custom; import java.io.ByteArrayOutputStream; -import java.net.Socket; +import java.io.IOException; import java.util.Base64; import org.asf.centuria.Centuria; import org.asf.centuria.accounts.AccountManager; import org.asf.centuria.accounts.CenturiaAccount; import org.asf.centuria.accounts.SaveMode; -import org.asf.rats.ConnectiveHTTPServer; -import org.asf.rats.processors.HttpUploadProcessor; +import org.asf.connective.RemoteClient; +import org.asf.connective.processors.HttpPushProcessor; import com.google.gson.JsonArray; import com.google.gson.JsonObject; import com.google.gson.JsonParser; -public class SaveManagerHandler extends HttpUploadProcessor { +public class SaveManagerHandler extends HttpPushProcessor { @Override - public void process(String contentType, Socket client, String method) { + public void process(String path, String method, RemoteClient client, String contentType) throws IOException { try { - if (!getRequest().method.equalsIgnoreCase("post")) { - this.setResponseCode(400); - this.setResponseMessage("Bad request"); + if (!method.equalsIgnoreCase("post")) { + this.setResponseStatus(400, "Bad request"); return; } // Parse body ByteArrayOutputStream strm = new ByteArrayOutputStream(); - ConnectiveHTTPServer.transferRequestBody(getHeaders(), getRequestBodyStream(), strm); + getRequest().transferRequestBody(strm); byte[] body = strm.toByteArray(); strm.close(); // Parse body JsonObject request = JsonParser.parseString(new String(body, "UTF-8")).getAsJsonObject(); if (!request.has("command")) { - this.setResponseCode(400); - this.setResponseMessage("Bad request"); + this.setResponseStatus(400, "Bad request"); return; } String command = request.get("command").getAsString(); @@ -46,15 +44,13 @@ public void process(String contentType, Socket client, String method) { // Parse JWT payload String token = this.getHeader("Authorization").substring("Bearer ".length()); if (token.isBlank()) { - this.setResponseCode(403); - this.setResponseMessage("Access denied"); + this.setResponseStatus(403, "Access denied"); return; } // Parse token if (token.isBlank()) { - this.setResponseCode(403); - this.setResponseMessage("Access denied"); + this.setResponseStatus(403, "Access denied"); return; } @@ -62,8 +58,7 @@ public void process(String contentType, Socket client, String method) { String verifyD = token.split("\\.")[0] + "." + token.split("\\.")[1]; String sig = token.split("\\.")[2]; if (!Centuria.verify(verifyD.getBytes("UTF-8"), Base64.getUrlDecoder().decode(sig))) { - this.setResponseCode(403); - this.setResponseMessage("Access denied"); + this.setResponseStatus(403, "Access denied"); return; } @@ -72,8 +67,7 @@ public void process(String contentType, Socket client, String method) { .parseString(new String(Base64.getUrlDecoder().decode(token.split("\\.")[1]), "UTF-8")) .getAsJsonObject(); if (!jwtPl.has("exp") || jwtPl.get("exp").getAsLong() < System.currentTimeMillis() / 1000) { - this.setResponseCode(403); - this.setResponseMessage("Access denied"); + this.setResponseStatus(403, "Access denied"); return; } @@ -87,16 +81,15 @@ public void process(String contentType, Socket client, String method) { // Check existence if (id == null) { // Invalid details - this.setBody("text/json", "{\"error\":\"invalid_credential\"}"); - this.setResponseCode(422); + this.setResponseContent("text/json", "{\"error\":\"invalid_credential\"}"); + this.setResponseStatus(401, "Unauthorized"); return; } // Find account CenturiaAccount acc = manager.getAccount(id); if (acc == null) { - this.setResponseCode(401); - this.setResponseMessage("Access denied"); + this.setResponseStatus(401, "Unauthorized"); return; } @@ -111,16 +104,15 @@ public void process(String contentType, Socket client, String method) { acc.migrateSaveDataToManagedMode(); response.addProperty("status", "success"); response.addProperty("migrated", true); - setBody("text/json", response.toString()); + setResponseContent("text/json", response.toString()); return; } response.addProperty("status", "failure"); response.addProperty("error", "save_data_unmanaged"); response.addProperty("error_message", "Running in unmanaged save data mode, please migrate to managed mode before using the save manager."); - setBody("text/json", response.toString()); - this.setResponseCode(400); - this.setResponseMessage("Bad request"); + setResponseContent("text/json", response.toString()); + this.setResponseStatus(400, "Bad request"); return; } @@ -132,9 +124,8 @@ public void process(String contentType, Socket client, String method) { response.addProperty("status", "failure"); response.addProperty("error", "missing_argument"); response.addProperty("error_message", "Missing required argument."); - setBody("text/json", response.toString()); - this.setResponseCode(400); - this.setResponseMessage("Bad request"); + setResponseContent("text/json", response.toString()); + this.setResponseStatus(400, "Bad request"); break; } if (!acc.getSaveManager().switchSave(request.get("save").getAsString())) { @@ -142,9 +133,8 @@ public void process(String contentType, Socket client, String method) { response.addProperty("status", "failure"); response.addProperty("error", "invalid_save"); response.addProperty("error_message", "Invalid save name."); - setBody("text/json", response.toString()); - this.setResponseCode(400); - this.setResponseMessage("Bad request"); + setResponseContent("text/json", response.toString()); + this.setResponseStatus(400, "Bad request"); break; } response.addProperty("status", "success"); @@ -170,28 +160,26 @@ public void process(String contentType, Socket client, String method) { response.addProperty("status", "failure"); response.addProperty("error", "unknown_command"); response.addProperty("error_message", "Unknown command."); - setBody("text/json", response.toString()); - this.setResponseCode(400); - this.setResponseMessage("Bad request"); + setResponseContent("text/json", response.toString()); + this.setResponseStatus(400, "Bad request"); break; } } - setBody("text/json", response.toString()); + setResponseContent("text/json", response.toString()); } catch (Exception e) { - setResponseCode(500); - setResponseMessage("Internal Server Error"); - Centuria.logger.error(getRequest().path + " failed: 500: Internal Server Error", e); + setResponseStatus(500, "Internal Server Error"); + Centuria.logger.error(getRequest().getRequestPath() + " failed: 500: Internal Server Error", e); } } @Override - public boolean supportsGet() { + public boolean supportsNonPush() { return true; } @Override - public HttpUploadProcessor createNewInstance() { + public HttpPushProcessor createNewInstance() { return new SaveManagerHandler(); } diff --git a/src/main/java/org/asf/centuria/networking/http/api/custom/UserDetailsHandler.java b/src/main/java/org/asf/centuria/networking/http/api/custom/UserDetailsHandler.java index 83e269a9..2764b52a 100644 --- a/src/main/java/org/asf/centuria/networking/http/api/custom/UserDetailsHandler.java +++ b/src/main/java/org/asf/centuria/networking/http/api/custom/UserDetailsHandler.java @@ -3,7 +3,6 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; -import java.net.Socket; import java.util.Base64; import org.asf.centuria.Centuria; @@ -11,27 +10,26 @@ import org.asf.centuria.accounts.CenturiaAccount; import org.asf.centuria.accounts.SaveMode; import org.asf.centuria.packets.xt.gameserver.inventory.InventoryItemDownloadPacket; -import org.asf.rats.ConnectiveHTTPServer; -import org.asf.rats.processors.HttpUploadProcessor; +import org.asf.connective.RemoteClient; +import org.asf.connective.processors.HttpPushProcessor; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; -public class UserDetailsHandler extends HttpUploadProcessor { +public class UserDetailsHandler extends HttpPushProcessor { @Override - public void process(String contentType, Socket client, String method) { + public void process(String path, String method, RemoteClient client, String contentType) throws IOException { try { - if (!getRequest().method.equalsIgnoreCase("post")) { - this.setResponseCode(400); - this.setResponseMessage("Bad request"); + if (!method.equalsIgnoreCase("post")) { + this.setResponseStatus(400, "Bad request"); return; } // Parse body ByteArrayOutputStream strm = new ByteArrayOutputStream(); - ConnectiveHTTPServer.transferRequestBody(getHeaders(), getRequestBodyStream(), strm); + getRequest().transferRequestBody(strm); byte[] body = strm.toByteArray(); strm.close(); @@ -41,8 +39,7 @@ public void process(String contentType, Socket client, String method) { // Parse body JsonObject request = JsonParser.parseString(new String(body, "UTF-8")).getAsJsonObject(); if (!request.has("id") && !request.has("name")) { - this.setResponseCode(400); - this.setResponseMessage("Bad request"); + this.setResponseStatus(400, "Bad request"); return; } String id = null; @@ -53,14 +50,12 @@ public void process(String contentType, Socket client, String method) { // Find account if (id == null) { - this.setResponseCode(404); - this.setResponseMessage("Account not found"); + this.setResponseStatus(404, "Account not found"); return; } CenturiaAccount acc = manager.getAccount(id); if (acc == null) { - this.setResponseCode(404); - this.setResponseMessage("Account not found"); + this.setResponseStatus(404, "Account not found"); return; } @@ -73,7 +68,7 @@ public void process(String contentType, Socket client, String method) { // Sensitive fields // Check authorization boolean isSelf = false; - if (getRequest().headers.containsKey("Authorization")) { + if (getRequest().hasHeader("Authorization")) { // Parse JWT payload String token = this.getHeader("Authorization").substring("Bearer ".length()); if (!token.isBlank()) { @@ -81,8 +76,7 @@ public void process(String contentType, Socket client, String method) { String verifyD = token.split("\\.")[0] + "." + token.split("\\.")[1]; String sig = token.split("\\.")[2]; if (!Centuria.verify(verifyD.getBytes("UTF-8"), Base64.getUrlDecoder().decode(sig))) { - this.setResponseCode(403); - this.setResponseMessage("Access denied"); + this.setResponseStatus(401, "Unauthorized"); return; } @@ -176,21 +170,20 @@ public void process(String contentType, Socket client, String method) { if (obj != null) response.add("active_look", obj.get("components").getAsJsonObject().get("AvatarLook").getAsJsonObject()); - setBody("text/json", response.toString()); + setResponseContent("text/json", response.toString()); } catch (Exception e) { - setResponseCode(500); - setResponseMessage("Internal Server Error"); - Centuria.logger.error(getRequest().path + " failed: 500: Internal Server Error", e); + setResponseStatus(500, "Internal Server Error"); + Centuria.logger.error(getRequest().getRequestPath() + " failed: 500: Internal Server Error", e); } } @Override - public boolean supportsGet() { + public boolean supportsNonPush() { return true; } @Override - public HttpUploadProcessor createNewInstance() { + public HttpPushProcessor createNewInstance() { return new UserDetailsHandler(); } diff --git a/src/main/java/org/asf/centuria/networking/http/director/GameServerRequestHandler.java b/src/main/java/org/asf/centuria/networking/http/director/GameServerRequestHandler.java index 79fba88c..05b4f436 100644 --- a/src/main/java/org/asf/centuria/networking/http/director/GameServerRequestHandler.java +++ b/src/main/java/org/asf/centuria/networking/http/director/GameServerRequestHandler.java @@ -1,23 +1,25 @@ package org.asf.centuria.networking.http.director; -import java.net.Socket; +import java.io.IOException; import org.asf.centuria.Centuria; -import org.asf.rats.processors.HttpUploadProcessor; +import org.asf.connective.RemoteClient; +import org.asf.connective.processors.HttpPushProcessor; import com.google.gson.JsonObject; -public class GameServerRequestHandler extends HttpUploadProcessor { +public class GameServerRequestHandler extends HttpPushProcessor { - public void process(String contentType, Socket client, String method) { + @Override + public void process(String path, String method, RemoteClient client, String contentType) throws IOException { // Send response JsonObject response = new JsonObject(); response.addProperty("smartfoxServer", Centuria.discoveryAddress); // load discovery address - setBody(response.toString()); + setResponseContent("text/json", response.toString()); } @Override - public HttpUploadProcessor createNewInstance() { + public HttpPushProcessor createNewInstance() { return new GameServerRequestHandler(); } @@ -27,7 +29,7 @@ public String path() { } @Override - public boolean supportsGet() { + public boolean supportsNonPush() { return true; } diff --git a/src/main/java/org/asf/centuria/networking/smartfox/SocketSmartfoxClient.java b/src/main/java/org/asf/centuria/networking/smartfox/SocketSmartfoxClient.java index f60704c8..fe9575d4 100644 --- a/src/main/java/org/asf/centuria/networking/smartfox/SocketSmartfoxClient.java +++ b/src/main/java/org/asf/centuria/networking/smartfox/SocketSmartfoxClient.java @@ -12,24 +12,21 @@ import java.util.zip.GZIPInputStream; import org.asf.centuria.packets.smartfox.ISmartfoxPacket; -import org.asf.centuria.util.TaskThread; public class SocketSmartfoxClient extends SmartfoxClient { private Socket client; private BaseSmartfoxServer server; + + private Object sendLock = new Object(); + InputStream input; OutputStream output; - TaskThread taskThread; - public SocketSmartfoxClient(Socket client, BaseSmartfoxServer server) { this.client = client; this.server = server; - taskThread = new TaskThread(client.toString()); - taskThread.start(); - try { input = client.getInputStream(); output = client.getOutputStream(); @@ -46,7 +43,6 @@ public Socket getSocket() { @Override protected void stop() { - taskThread.stopCleanly(); client = null; } @@ -57,19 +53,20 @@ public boolean isConnected() { @Override public void disconnect() { - taskThread.flush(3); - try { - if (client != null) - client.close(); - } catch (IOException e) { + synchronized (sendLock) { + try { + if (client != null) + client.close(); + } catch (IOException e) { + } + server.clientDisconnect(this); + stop(); } - server.clientDisconnect(this); - stop(); } @Override public void sendPacket(ISmartfoxPacket packet) { - taskThread.schedule(() -> { + synchronized (sendLock) { try { // Instantiate the packet and build String content = packet.build(); @@ -81,12 +78,12 @@ public void sendPacket(ISmartfoxPacket packet) { output.flush(); } catch (Exception e) { } - }); + } } @Override public void sendPacket(String packet) { - taskThread.schedule(() -> { + synchronized (sendLock) { try { // Send packet byte[] payload = packet.getBytes("UTF-8"); @@ -97,7 +94,7 @@ public void sendPacket(String packet) { output.flush(); } catch (Exception e) { } - }); + } } @Override diff --git a/src/main/java/org/asf/centuria/packets/xt/gameserver/avatar/AvatarLookSavePacket.java b/src/main/java/org/asf/centuria/packets/xt/gameserver/avatar/AvatarLookSavePacket.java index 7558a079..e007503d 100644 --- a/src/main/java/org/asf/centuria/packets/xt/gameserver/avatar/AvatarLookSavePacket.java +++ b/src/main/java/org/asf/centuria/packets/xt/gameserver/avatar/AvatarLookSavePacket.java @@ -6,6 +6,7 @@ import org.asf.centuria.data.XtReader; import org.asf.centuria.data.XtWriter; import org.asf.centuria.entities.players.Player; +import org.asf.centuria.enums.objects.WorldObjectMoverNodeType; import org.asf.centuria.networking.gameserver.GameServer; import org.asf.centuria.networking.smartfox.SmartfoxClient; import org.asf.centuria.packets.xt.IXtPacket; @@ -117,7 +118,7 @@ public boolean handle(SmartfoxClient client) throws IOException { GameServer srv = (GameServer) client.getServer(); for (Player player : srv.getPlayers()) { if (plr.room != null && player.room != null && player.room.equals(plr.room) && player != plr) { - plr.syncTo(player); + plr.syncTo(player, WorldObjectMoverNodeType.Move); } } diff --git a/src/main/java/org/asf/centuria/packets/xt/gameserver/avatar/AvatarObjectInfoPacket.java b/src/main/java/org/asf/centuria/packets/xt/gameserver/avatar/AvatarObjectInfoPacket.java index c3b3f3d4..06caa3eb 100644 --- a/src/main/java/org/asf/centuria/packets/xt/gameserver/avatar/AvatarObjectInfoPacket.java +++ b/src/main/java/org/asf/centuria/packets/xt/gameserver/avatar/AvatarObjectInfoPacket.java @@ -8,49 +8,49 @@ import com.google.gson.JsonObject; public class AvatarObjectInfoPacket extends ObjectInfoPacket { - public JsonObject look; //TODO: make into a component (eventually) - public String displayName; - public int unknownValue; //TODO: what is this?? + public JsonObject look; // TODO: make into a component (eventually) + public String displayName; + public int unknownValue; // TODO: what is this?? - @Override + @Override public void build(XtWriter writer) throws IOException { - //I can't call super because super will finalize everything + // I can't call super because super will finalize everything writer.writeInt(DATA_PREFIX); - + writer.writeString(id); // World object ID - writer.writeInt(defId); // Def Id + writer.writeInt(defId); // Def Id writer.writeString(ownerId); // Owner ID - - writer.writeInt(lastMove.nodeType.value); //Node type - writer.writeLong(lastMove.serverTime); //server time - - //Position (X,Y,Z) + + writer.writeInt(lastMove.nodeType.value); // Node type + writer.writeLong(lastMove.serverTime); // server time + + // Position (X,Y,Z) writer.writeDouble(lastMove.positionInfo.position.x); writer.writeDouble(lastMove.positionInfo.position.y); writer.writeDouble(lastMove.positionInfo.position.z); - - //Rotation (X,Y,Z) + + // Rotation (X,Y,Z) writer.writeDouble(lastMove.positionInfo.rotation.x); writer.writeDouble(lastMove.positionInfo.rotation.y); writer.writeDouble(lastMove.positionInfo.rotation.z); writer.writeDouble(lastMove.positionInfo.rotation.w); - - //Likely Direction (X,Y,Z) + + // Likely Direction (X,Y,Z) writer.writeDouble(lastMove.velocity.direction.x); writer.writeDouble(lastMove.velocity.direction.y); writer.writeDouble(lastMove.velocity.direction.z); - - //Speed (Floats are always suffixed in .0, so i know this is speed) + + // Speed (Floats are always suffixed in .0, so i know this is speed) writer.writeFloat(lastMove.velocity.speed); - - //Action Type + + // Action Type writer.writeInt(lastMove.actorActionType); - - // Look and name - writer.writeString(look.toString()); - writer.writeString(displayName); - writer.writeInt(unknownValue); - - writer.writeString(DATA_SUFFIX); // data suffix + + // Look and name + writer.writeString(look.toString()); + writer.writeString(displayName); + writer.writeInt(unknownValue); + + writer.writeString(DATA_SUFFIX); // data suffix } } diff --git a/src/main/java/org/asf/centuria/packets/xt/gameserver/avatar/AvatarSelectLookPacket.java b/src/main/java/org/asf/centuria/packets/xt/gameserver/avatar/AvatarSelectLookPacket.java index 017ac486..d187e2b4 100644 --- a/src/main/java/org/asf/centuria/packets/xt/gameserver/avatar/AvatarSelectLookPacket.java +++ b/src/main/java/org/asf/centuria/packets/xt/gameserver/avatar/AvatarSelectLookPacket.java @@ -6,6 +6,7 @@ import org.asf.centuria.data.XtReader; import org.asf.centuria.data.XtWriter; import org.asf.centuria.entities.players.Player; +import org.asf.centuria.enums.objects.WorldObjectMoverNodeType; import org.asf.centuria.networking.gameserver.GameServer; import org.asf.centuria.networking.smartfox.SmartfoxClient; import org.asf.centuria.packets.xt.IXtPacket; @@ -87,7 +88,7 @@ public boolean handle(SmartfoxClient client) throws IOException { GameServer srv = (GameServer) client.getServer(); for (Player player : srv.getPlayers()) { if (plr.room != null && player.room != null && player.room.equals(plr.room) && player != plr) { - plr.syncTo(player); + plr.syncTo(player, WorldObjectMoverNodeType.Move); } } diff --git a/src/main/java/org/asf/centuria/packets/xt/gameserver/relationship/RelationshipJumpToPlayerPacket.java b/src/main/java/org/asf/centuria/packets/xt/gameserver/relationship/RelationshipJumpToPlayerPacket.java index 5cfc183a..cda5044a 100644 --- a/src/main/java/org/asf/centuria/packets/xt/gameserver/relationship/RelationshipJumpToPlayerPacket.java +++ b/src/main/java/org/asf/centuria/packets/xt/gameserver/relationship/RelationshipJumpToPlayerPacket.java @@ -13,10 +13,6 @@ public class RelationshipJumpToPlayerPacket implements IXtPacket 0 || removals.size() > 0) { var il = plr.account.getSaveSpecificInventory().getItem("201"); var ilPacket = new InventoryItemPacket(); diff --git a/src/main/java/org/asf/centuria/packets/xt/gameserver/world/WorldReadyPacket.java b/src/main/java/org/asf/centuria/packets/xt/gameserver/world/WorldReadyPacket.java index 1805fff7..a6c3e468 100644 --- a/src/main/java/org/asf/centuria/packets/xt/gameserver/world/WorldReadyPacket.java +++ b/src/main/java/org/asf/centuria/packets/xt/gameserver/world/WorldReadyPacket.java @@ -16,6 +16,7 @@ import org.asf.centuria.entities.objects.WorldObjectPositionInfo; import org.asf.centuria.entities.players.Player; import org.asf.centuria.entities.sanctuaries.SanctuaryObjectData; +import org.asf.centuria.enums.objects.WorldObjectMoverNodeType; import org.asf.centuria.enums.sanctuaries.SanctuaryObjectType; import org.asf.centuria.interactions.InteractionManager; import org.asf.centuria.modules.eventbus.EventBus; @@ -111,7 +112,7 @@ public boolean handle(SmartfoxClient client) throws IOException { GameServer server = (GameServer) client.getServer(); for (Player player : server.getPlayers()) { if (plr.room != null && player.room != null && player.room.equals(plr.room) && player != plr) { - player.syncTo(plr); + player.syncTo(plr, WorldObjectMoverNodeType.InitPosition); Centuria.logger.debug(MarkerManager.getMarker("WorldReadyPacket"), "Syncing player " + player.account.getDisplayName() + " to " + plr.account.getDisplayName()); } @@ -140,7 +141,7 @@ public boolean handle(SmartfoxClient client) throws IOException { // Sync spawn for (Player player : server.getPlayers()) { if (plr.room != null && player.room != null && player.room.equals(plr.room) && player != plr) { - plr.syncTo(player); + plr.syncTo(player, WorldObjectMoverNodeType.InitPosition); Centuria.logger.debug(MarkerManager.getMarker("WorldReadyPacket"), "Syncing spawn " + player.account.getDisplayName() + " to " + plr.account.getDisplayName()); } @@ -188,7 +189,7 @@ public boolean handle(SmartfoxClient client) throws IOException { // Sync spawn for (Player player : server.getPlayers()) { if (plr.room != null && player.room != null && player.room.equals(plr.room) && player != plr) { - plr.syncTo(player); + plr.syncTo(player, WorldObjectMoverNodeType.InitPosition); Centuria.logger.debug(MarkerManager.getMarker("WorldReadyPacket"), "Syncing spawn " + player.account.getDisplayName() + " to " + plr.account.getDisplayName()); } diff --git a/src/main/java/org/asf/centuria/tools/legacyclienttools/DirectorProcessor.java b/src/main/java/org/asf/centuria/tools/legacyclienttools/DirectorProcessor.java index 6fdb4cfb..9e961d0f 100644 --- a/src/main/java/org/asf/centuria/tools/legacyclienttools/DirectorProcessor.java +++ b/src/main/java/org/asf/centuria/tools/legacyclienttools/DirectorProcessor.java @@ -1,22 +1,24 @@ package org.asf.centuria.tools.legacyclienttools; -import java.net.Socket; +import java.io.IOException; -import org.asf.rats.processors.HttpUploadProcessor; +import org.asf.connective.RemoteClient; +import org.asf.connective.processors.HttpPushProcessor; import com.google.gson.JsonObject; -public class DirectorProcessor extends HttpUploadProcessor { +public class DirectorProcessor extends HttpPushProcessor { - public void process(String contentType, Socket client, String method) { + @Override + public void process(String path, String method, RemoteClient client, String contentType) throws IOException { // Send response JsonObject response = new JsonObject(); response.addProperty("smartfoxServer", "localhost"); - setBody(response.toString()); + setResponseContent("text/json", response.toString()); } @Override - public HttpUploadProcessor createNewInstance() { + public HttpPushProcessor createNewInstance() { return new DirectorProcessor(); } @@ -26,7 +28,7 @@ public String path() { } @Override - public boolean supportsGet() { + public boolean supportsNonPush() { return true; } diff --git a/src/main/java/org/asf/centuria/tools/legacyclienttools/LegacyClientPacketTranslatorWindow.java b/src/main/java/org/asf/centuria/tools/legacyclienttools/LegacyClientPacketTranslatorWindow.java index cf307cee..6cdbfe04 100644 --- a/src/main/java/org/asf/centuria/tools/legacyclienttools/LegacyClientPacketTranslatorWindow.java +++ b/src/main/java/org/asf/centuria/tools/legacyclienttools/LegacyClientPacketTranslatorWindow.java @@ -10,7 +10,7 @@ import javax.swing.JOptionPane; import javax.swing.JTextField; -import org.asf.rats.ConnectiveHTTPServer; +import org.asf.connective.ConnectiveHttpServer; import com.google.gson.JsonObject; import com.google.gson.JsonParser; @@ -20,15 +20,15 @@ import java.awt.event.ActionListener; import java.io.IOException; import java.io.InputStream; -import java.net.InetAddress; import java.net.URL; +import java.util.Map; import java.awt.event.ActionEvent; public class LegacyClientPacketTranslatorWindow { public BasicProxyServer chat; - public ConnectiveHTTPServer director; - public ConnectiveHTTPServer api; + public ConnectiveHttpServer director; + public ConnectiveHttpServer api; public TranslatorGameServer game; private JFrame frmLegacyClientPacket; @@ -84,139 +84,144 @@ private void initialize() { frmLegacyClientPacket.setBounds(100, 100, 648, 466); frmLegacyClientPacket.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frmLegacyClientPacket.getContentPane().setLayout(new FlowLayout(FlowLayout.CENTER, 5, 5)); - + JPanel panel = new JPanel(); panel.setPreferredSize(new Dimension(600, 400)); frmLegacyClientPacket.getContentPane().add(panel); panel.setLayout(null); - + JLabel lblNewLabel = new JLabel("Upstream Director"); lblNewLabel.setBounds(10, 11, 580, 14); panel.add(lblNewLabel); - + txtHttpaerialworksddnsnet = new JTextField(); - txtHttpaerialworksddnsnet.setText("http://aerialworks.ddns.net:6969"); + txtHttpaerialworksddnsnet.setText("http://emuferal.ddns.net:6969"); txtHttpaerialworksddnsnet.setBounds(10, 28, 580, 20); panel.add(txtHttpaerialworksddnsnet); txtHttpaerialworksddnsnet.setColumns(10); - + lblNewLabel_1 = new JLabel("Upstream API"); lblNewLabel_1.setBounds(10, 59, 580, 14); panel.add(lblNewLabel_1); - + txtHttpsaerialworksddnsnet = new JTextField(); - txtHttpsaerialworksddnsnet.setText("https://aerialworks.ddns.net:6970"); + txtHttpsaerialworksddnsnet.setText("https://emuferal.ddns.net:6970"); txtHttpsaerialworksddnsnet.setColumns(10); txtHttpsaerialworksddnsnet.setBounds(10, 75, 580, 20); panel.add(txtHttpsaerialworksddnsnet); - + txtAerialworksddnsnet = new JTextField(); - txtAerialworksddnsnet.setText("aerialworks.ddns.net"); + txtAerialworksddnsnet.setText("emuferal.ddns.net"); txtAerialworksddnsnet.setColumns(10); txtAerialworksddnsnet.setBounds(10, 122, 580, 20); panel.add(txtAerialworksddnsnet); - + lblNewLabel_2 = new JLabel("Upstream Chat Server"); lblNewLabel_2.setBounds(10, 106, 580, 14); panel.add(lblNewLabel_2); - + chckbxNewCheckBox = new JCheckBox("Encrypted"); chckbxNewCheckBox.setSelected(true); chckbxNewCheckBox.setBounds(10, 149, 580, 23); panel.add(chckbxNewCheckBox); - + lblNewLabel_3 = new JLabel("Upstream game port"); lblNewLabel_3.setBounds(10, 179, 256, 14); panel.add(lblNewLabel_3); - + textField = new JTextField(); textField.setText("6968"); textField.setColumns(10); textField.setBounds(10, 196, 281, 20); panel.add(textField); - + textField_1 = new JTextField(); textField_1.setText("6972"); textField_1.setColumns(10); textField_1.setBounds(301, 196, 289, 20); panel.add(textField_1); - + lblNewLabel_4 = new JLabel("Upstream chat port"); lblNewLabel_4.setBounds(301, 179, 256, 14); panel.add(lblNewLabel_4); - + lblNewLabel_5 = new JLabel("Local game port"); lblNewLabel_5.setBounds(10, 227, 256, 14); panel.add(lblNewLabel_5); - + textField_2 = new JTextField(); textField_2.setText("6968"); textField_2.setColumns(10); textField_2.setBounds(10, 244, 281, 20); panel.add(textField_2); - + lblNewLabel_6 = new JLabel("Local chat port"); lblNewLabel_6.setBounds(301, 227, 256, 14); panel.add(lblNewLabel_6); - + textField_3 = new JTextField(); textField_3.setText("6972"); textField_3.setColumns(10); textField_3.setBounds(301, 244, 289, 20); panel.add(textField_3); - + lblNewLabel_7 = new JLabel("Local director port"); lblNewLabel_7.setBounds(10, 276, 256, 14); panel.add(lblNewLabel_7); - + textField_4 = new JTextField(); textField_4.setText("6969"); textField_4.setColumns(10); textField_4.setBounds(10, 293, 281, 20); panel.add(textField_4); - + textField_5 = new JTextField(); textField_5.setText("6970"); textField_5.setColumns(10); textField_5.setBounds(301, 293, 281, 20); panel.add(textField_5); - + lblNewLabel_8 = new JLabel("Local API port"); lblNewLabel_8.setBounds(301, 276, 256, 14); panel.add(lblNewLabel_8); - + JButton btnNewButton = new JButton("Start"); btnNewButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if (btnNewButton.getText().equals("Start")) { // Start servers - director = new ConnectiveHTTPServer(); - api = new ConnectiveHTTPServer(); chat = new BasicProxyServer(); - + // Director - try { - director.setIp(InetAddress.getLoopbackAddress()); - director.setPort(Integer.parseInt(textField_4.getText())); + try { + director = ConnectiveHttpServer.createNetworked("HTTP/1.1", + Map.of("address", "127.0.0.1", "port", textField_4.getText())); director.registerProcessor(new DirectorProcessor()); director.start(); } catch (Exception e2) { - JOptionPane.showMessageDialog(frmLegacyClientPacket, "Failed to start the director server, please check the configuration.", "Error", JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(frmLegacyClientPacket, + "Failed to start the director server, please check the configuration.", "Error", + JOptionPane.ERROR_MESSAGE); return; } - + // API try { - api.setIp(InetAddress.getLoopbackAddress()); - api.setPort(Integer.parseInt(textField_5.getText())); + api = ConnectiveHttpServer.createNetworked("HTTP/1.1", + Map.of("address", "127.0.0.1", "port", textField_5.getText())); api.registerProcessor(new ProxyHttpProcessor(txtHttpsaerialworksddnsnet.getText())); api.start(); } catch (Exception e2) { - JOptionPane.showMessageDialog(frmLegacyClientPacket, "Failed to start the API server, please check the configuration.", "Error", JOptionPane.ERROR_MESSAGE); - director.stop(); + JOptionPane.showMessageDialog(frmLegacyClientPacket, + "Failed to start the API server, please check the configuration.", "Error", + JOptionPane.ERROR_MESSAGE); + try { + director.stop(); + } catch (IOException e1) { + } return; } - + // Chat try { chat.remoteAddr = txtAerialworksddnsnet.getText(); @@ -225,19 +230,28 @@ public void actionPerformed(ActionEvent e) { chat.encryptUpstream = chckbxNewCheckBox.isSelected(); chat.start(); } catch (Exception e2) { - JOptionPane.showMessageDialog(frmLegacyClientPacket, "Failed to start the chat server, please check the configuration.", "Error", JOptionPane.ERROR_MESSAGE); - director.stop(); - api.stop(); + JOptionPane.showMessageDialog(frmLegacyClientPacket, + "Failed to start the chat server, please check the configuration.", "Error", + JOptionPane.ERROR_MESSAGE); + try { + director.stop(); + } catch (IOException e1) { + } + try { + api.stop(); + } catch (IOException e1) { + } return; } - + // Game try { try { - InputStream strm = new URL(txtHttpaerialworksddnsnet.getText() + "/v1/bestGameServer").openStream(); + InputStream strm = new URL(txtHttpaerialworksddnsnet.getText() + "/v1/bestGameServer") + .openStream(); String resp = new String(strm.readAllBytes()); strm.close(); - + JsonObject obj = JsonParser.parseString(resp).getAsJsonObject(); if (!obj.has("smartfoxServer")) throw new IOException(); @@ -246,7 +260,9 @@ public void actionPerformed(ActionEvent e) { game.apiAddr = txtHttpsaerialworksddnsnet.getText(); game.remotePort = Integer.parseInt(textField.getText()); } catch (Exception e2) { - JOptionPane.showMessageDialog(frmLegacyClientPacket, "Failed to contact the remote director server, please check the configuration.", "Error", JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(frmLegacyClientPacket, + "Failed to contact the remote director server, please check the configuration.", + "Error", JOptionPane.ERROR_MESSAGE); director.stop(); api.stop(); chat.stop(); @@ -254,18 +270,32 @@ public void actionPerformed(ActionEvent e) { } game.start(); } catch (Exception e2) { - JOptionPane.showMessageDialog(frmLegacyClientPacket, "Failed to start the game server, please check the configuration.", "Error", JOptionPane.ERROR_MESSAGE); - director.stop(); - api.stop(); + JOptionPane.showMessageDialog(frmLegacyClientPacket, + "Failed to start the game server, please check the configuration.", "Error", + JOptionPane.ERROR_MESSAGE); + try { + director.stop(); + } catch (IOException e1) { + } + try { + api.stop(); + } catch (IOException e1) { + } chat.stop(); return; } - + btnNewButton.setText("Stop"); } else { // Stop - director.stop(); - api.stop(); + try { + director.stop(); + } catch (IOException e1) { + } + try { + api.stop(); + } catch (IOException e1) { + } chat.stop(); game.stop(); btnNewButton.setText("Start"); diff --git a/src/main/java/org/asf/centuria/tools/legacyclienttools/ProxyHttpProcessor.java b/src/main/java/org/asf/centuria/tools/legacyclienttools/ProxyHttpProcessor.java index b4e5e793..5ac2d538 100644 --- a/src/main/java/org/asf/centuria/tools/legacyclienttools/ProxyHttpProcessor.java +++ b/src/main/java/org/asf/centuria/tools/legacyclienttools/ProxyHttpProcessor.java @@ -3,13 +3,13 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.net.HttpURLConnection; -import java.net.Socket; import java.net.URL; import java.util.UUID; -import org.asf.rats.processors.HttpUploadProcessor; +import org.asf.connective.RemoteClient; +import org.asf.connective.processors.HttpPushProcessor; -public class ProxyHttpProcessor extends HttpUploadProcessor { +public class ProxyHttpProcessor extends HttpPushProcessor { private String proxy; public ProxyHttpProcessor(String proxy) { @@ -17,7 +17,7 @@ public ProxyHttpProcessor(String proxy) { } @Override - public void process(String contentType, Socket client, String method) { + public void process(String path, String method, RemoteClient client, String contentType) throws IOException { byte[] body = new byte[0]; if (method.equals("POST")) { ByteArrayOutputStream strm = new ByteArrayOutputStream(); @@ -30,7 +30,7 @@ public void process(String contentType, Socket client, String method) { // Proxy request try { - String rq = getRequest().path; + String rq = getRequest().getRawRequestResource(); System.out.println("Proxy HTTP: " + rq); if (rq.startsWith("/r/player/")) { String plr = rq.substring("/r/player/".length()); @@ -47,8 +47,9 @@ public void process(String contentType, Socket client, String method) { } HttpURLConnection conn = (HttpURLConnection) new URL(proxy + "/" + rq).openConnection(); conn.setRequestMethod(method); - for (String header : getRequest().headers.keySet()) { - conn.addRequestProperty(header, getRequest().headers.get(header)); + for (String header : getRequest().getHeaderNames()) { + for (String value : getRequest().getHeader(header).getValues()) + conn.addRequestProperty(header, value); } if (body.length != 0) { conn.setDoOutput(true); @@ -66,16 +67,15 @@ public void process(String contentType, Socket client, String method) { conn.getHeaderFields().forEach((k, v) -> { if (k == null) return; - v.forEach(t -> getResponse().setHeader(k, t, true)); + v.forEach(t -> getResponse().addHeader(k, t, true)); }); } catch (IOException e) { - setResponseCode(503); - setResponseMessage("Service unavailable"); + setResponseStatus(503, "Service unavailable"); } } @Override - public HttpUploadProcessor createNewInstance() { + public HttpPushProcessor createNewInstance() { return new ProxyHttpProcessor(proxy); } @@ -85,7 +85,7 @@ public String path() { } @Override - public boolean supportsGet() { + public boolean supportsNonPush() { return true; } @@ -93,4 +93,5 @@ public boolean supportsGet() { public boolean supportsChildPaths() { return true; } + } diff --git a/src/main/java/org/asf/connective/https/ConnectiveHTTPSServer.java b/src/main/java/org/asf/connective/https/ConnectiveHTTPSServer.java deleted file mode 100644 index a818c88a..00000000 --- a/src/main/java/org/asf/connective/https/ConnectiveHTTPSServer.java +++ /dev/null @@ -1,91 +0,0 @@ -package org.asf.connective.https; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.net.InetAddress; -import java.net.ServerSocket; -import java.nio.file.Files; -import java.nio.file.Path; -import java.security.KeyManagementException; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.UnrecoverableKeyException; -import java.security.cert.CertificateException; - -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.SSLContext; - -import org.asf.rats.ConnectiveHTTPServer; - -public class ConnectiveHTTPSServer extends ConnectiveHTTPServer { - - private File keystoreFile = null; - private SSLContext context = null; - private char[] keystorePassword; - - public ConnectiveHTTPSServer() { - port = 8043; - } - - public void assignAsMainImplementation() { - implementation = this; - } - - @Override - public void start() throws IOException { - if (socket != null) - throw new IllegalStateException("Server already running!"); - - if (keystoreFile == null) { - String keystore = "keystore.jks"; - String dir = System.getProperty("rats.config.dir") == null ? "." : System.getProperty("rats.config.dir"); - if (this.ip.isLoopbackAddress()) { - keystore = "keystore.localhost.jks"; - } else if (!this.ip.toString().equals("/0.0.0.0")) { - String str = ip.getHostAddress().replaceAll(":|\\.", "-"); - - keystore = "keystore-" + str + ".jks"; - } - keystoreFile = new File(dir + "/" + keystore); - keystorePassword = Files.readString(Path.of(dir + "/" + keystore + ".password")).toCharArray(); - } - - try { - context = getContext(keystoreFile, keystorePassword); - } catch (KeyStoreException | NoSuchAlgorithmException | UnrecoverableKeyException | KeyManagementException - | CertificateException e) { - throw new RuntimeException(e); - } - - super.start(); - } - - @Override - public ServerSocket getServerSocket(int port, InetAddress addr) throws IOException { - return context.getServerSocketFactory().createServerSocket(port, 0, addr); - } - - public void setKeystoreFile(File keystore, char[] password) { - keystoreFile = keystore; - keystorePassword = password; - } - - public SSLContext getContext(File keystore, char[] password) - throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyManagementException, - CertificateException, FileNotFoundException, IOException { - KeyStore mainStore = KeyStore.getInstance("JKS"); - mainStore.load(new FileInputStream(keystore), password); - - KeyManagerFactory managerFactory = KeyManagerFactory.getInstance("SunX509"); - managerFactory.init(mainStore, password); - - SSLContext cont = SSLContext.getInstance("TLS"); - cont.init(managerFactory.getKeyManagers(), null, null); - - return cont; - } - -} diff --git a/src/main/resources/leveling/leveltriggers.json b/src/main/resources/leveling/leveltriggers.json index bc95c4e1..4ac02fc8 100644 --- a/src/main/resources/leveling/leveltriggers.json +++ b/src/main/resources/leveling/leveltriggers.json @@ -4,11 +4,21 @@ "xp": 8 } ], + "levelevents.minigames.dizzywingdispatch": [ + { + "xp": 8 + } + ], "levelevents.minigames.doordye": [ { "xp": 10 } ], + "levelevents.minigames.kinoparlor": [ + { + "xp": 15 + } + ], "levelevents.harvest": [ { "xp": 10, diff --git a/src/main/resources/minigames/dizzywingdispatch.json b/src/main/resources/minigames/dizzywingdispatch.json new file mode 100644 index 00000000..9b3c72e7 --- /dev/null +++ b/src/main/resources/minigames/dizzywingdispatch.json @@ -0,0 +1,580 @@ +{ +"specialOrders": [ + { + "_fromLevelNumber": 1, + "_toLevelNumber": 9, + "_isToLevelInfinite": false, + "_regularLevelAppearancePercent": 100, + "_accessoriesAppearancePercent": 0, + "_eggsAppearancePercent": 0, + "_limitedMovesAppearancePercent": 0, + "_comboAppearancePercent": 0 + }, + { + "_fromLevelNumber": 10, + "_toLevelNumber": 24, + "_isToLevelInfinite": false, + "_regularLevelAppearancePercent": 20, + "_accessoriesAppearancePercent": 30, + "_eggsAppearancePercent": 30, + "_limitedMovesAppearancePercent": 0, + "_comboAppearancePercent": 15 + }, + { + "_fromLevelNumber": 25, + "_toLevelNumber": 49, + "_isToLevelInfinite": false, + "_regularLevelAppearancePercent": 20, + "_accessoriesAppearancePercent": 18, + "_eggsAppearancePercent": 17, + "_limitedMovesAppearancePercent": 25, + "_comboAppearancePercent": 15 + }, + { + "_fromLevelNumber": 50, + "_toLevelNumber": 74, + "_isToLevelInfinite": false, + "_regularLevelAppearancePercent": 10, + "_accessoriesAppearancePercent": 18, + "_eggsAppearancePercent": 17, + "_limitedMovesAppearancePercent": 30, + "_comboAppearancePercent": 25 + }, + { + "_fromLevelNumber": 75, + "_toLevelNumber": 149, + "_isToLevelInfinite": false, + "_regularLevelAppearancePercent": 20, + "_accessoriesAppearancePercent": 13, + "_eggsAppearancePercent": 12, + "_limitedMovesAppearancePercent": 30, + "_comboAppearancePercent": 25 + }, + { + "_fromLevelNumber": 150, + "_toLevelNumber": 249, + "_isToLevelInfinite": false, + "_regularLevelAppearancePercent": 5, + "_accessoriesAppearancePercent": 8, + "_eggsAppearancePercent": 7, + "_limitedMovesAppearancePercent": 35, + "_comboAppearancePercent": 45 + }, + { + "_fromLevelNumber": 250, + "_toLevelNumber": 300, + "_isToLevelInfinite": true, + "_regularLevelAppearancePercent": 0, + "_accessoriesAppearancePercent": 8, + "_eggsAppearancePercent": 7, + "_limitedMovesAppearancePercent": 50, + "_comboAppearancePercent": 35 + } +], +"specialOrderCountRanges": [ + { + "_fromLevelNumber": 1, + "_toLevelNumber": 9, + "_isToLevelInfinite": false, + "_minimumAccessoryCount": 0, + "_maximumAccessoryCount": 0, + "_minimumEggRowCount": 0, + "_maximumEggRowCount": 0, + "_minimumLimitedMovesOnlyCount": 0, + "_maximumLimitedMovesOnlyCount": 0, + "_minimumLimitedMovesComboCount": 0, + "_maximumLimitedMovesComboCount": 0 + }, + { + "_fromLevelNumber": 10, + "_toLevelNumber": 49, + "_isToLevelInfinite": false, + "_minimumAccessoryCount": 1, + "_maximumAccessoryCount": 5, + "_minimumEggRowCount": 0, + "_maximumEggRowCount": 1, + "_minimumLimitedMovesOnlyCount": 10, + "_maximumLimitedMovesOnlyCount": 20, + "_minimumLimitedMovesComboCount": 30, + "_maximumLimitedMovesComboCount": 35 + }, + { + "_fromLevelNumber": 50, + "_toLevelNumber": 74, + "_isToLevelInfinite": false, + "_minimumAccessoryCount": 5, + "_maximumAccessoryCount": 10, + "_minimumEggRowCount": 1, + "_maximumEggRowCount": 3, + "_minimumLimitedMovesOnlyCount": 10, + "_maximumLimitedMovesOnlyCount": 15, + "_minimumLimitedMovesComboCount": 30, + "_maximumLimitedMovesComboCount": 35 + }, + { + "_fromLevelNumber": 75, + "_toLevelNumber": 149, + "_isToLevelInfinite": false, + "_minimumAccessoryCount": 1, + "_maximumAccessoryCount": 7, + "_minimumEggRowCount": 0, + "_maximumEggRowCount": 4, + "_minimumLimitedMovesOnlyCount": 10, + "_maximumLimitedMovesOnlyCount": 15, + "_minimumLimitedMovesComboCount": 25, + "_maximumLimitedMovesComboCount": 30 + }, + { + "_fromLevelNumber": 150, + "_toLevelNumber": 249, + "_isToLevelInfinite": false, + "_minimumAccessoryCount": 5, + "_maximumAccessoryCount": 10, + "_minimumEggRowCount": 2, + "_maximumEggRowCount": 4, + "_minimumLimitedMovesOnlyCount": 7, + "_maximumLimitedMovesOnlyCount": 10, + "_minimumLimitedMovesComboCount": 20, + "_maximumLimitedMovesComboCount": 25 + }, + { + "_fromLevelNumber": 250, + "_toLevelNumber": 300, + "_isToLevelInfinite": true, + "_minimumAccessoryCount": 5, + "_maximumAccessoryCount": 15, + "_minimumEggRowCount": 3, + "_maximumEggRowCount": 6, + "_minimumLimitedMovesOnlyCount": 5, + "_maximumLimitedMovesOnlyCount": 7, + "_minimumLimitedMovesComboCount": 15, + "_maximumLimitedMovesComboCount": 20 + } +], +"achievementToUserVarIndexList": [ + { + "achievementType": 0, + "userVarIndex": 0 + }, + { + "achievementType": 1, + "userVarIndex": 1 + }, + { + "achievementType": 2, + "userVarIndex": 2 + }, + { + "achievementType": 3, + "userVarIndex": 13 + }, + { + "achievementType": 4, + "userVarIndex": 3 + }, + { + "achievementType": 5, + "userVarIndex": 14 + }, + { + "achievementType": 6, + "userVarIndex": 4 + }, + { + "achievementType": 7, + "userVarIndex": 15 + }, + { + "achievementType": 8, + "userVarIndex": 5 + }, + { + "achievementType": 9, + "userVarIndex": 21 + }, + { + "achievementType": 10, + "userVarIndex": 6 + }, + { + "achievementType": 11, + "userVarIndex": 17 + }, + { + "achievementType": 12, + "userVarIndex": 7 + }, + { + "achievementType": 13, + "userVarIndex": 18 + }, + { + "achievementType": 14, + "userVarIndex": 8 + }, + { + "achievementType": 15, + "userVarIndex": 23 + }, + { + "achievementType": 17, + "userVarIndex": 9 + }, + { + "achievementType": 18, + "userVarIndex": 19 + }, + { + "achievementType": 19, + "userVarIndex": 10 + }, + { + "achievementType": 20, + "userVarIndex": 22 + }, + { + "achievementType": 21, + "userVarIndex": 11 + }, + { + "achievementType": 22, + "userVarIndex": 20 + }, + { + "achievementType": 23, + "userVarIndex": 12 + }, + { + "achievementType": 24, + "userVarIndex": 16 + }, + { + "achievementType": 25, + "userVarIndex": 24 + }, + { + "achievementType": 26, + "userVarIndex": 25 + }, + { + "achievementType": 27, + "userVarIndex": 26 + }, + { + "achievementType": 28, + "userVarIndex": 27 + }, + { + "achievementType": 29, + "userVarIndex": 28 + }, + { + "achievementType": 31, + "userVarIndex": 29 + }, + { + "achievementType": 32, + "userVarIndex": 30 + }, + { + "achievementType": 33, + "userVarIndex": 31 + }, + { + "achievementType": 34, + "userVarIndex": 32 + }, + { + "achievementType": 35, + "userVarIndex": 33 + }, + { + "achievementType": 36, + "userVarIndex": 44 + }, + { + "achievementType": 37, + "userVarIndex": 45 + }, + { + "achievementType": 38, + "userVarIndex": 36 + }, + { + "achievementType": 39, + "userVarIndex": 37 + }, + { + "achievementType": 40, + "userVarIndex": 38 + }, + { + "achievementType": 41, + "userVarIndex": 39 + }, + { + "achievementType": 42, + "userVarIndex": 48 + }, + { + "achievementType": 43, + "userVarIndex": 49 + }, + { + "achievementType": 44, + "userVarIndex": 40 + }, + { + "achievementType": 45, + "userVarIndex": 41 + }, + { + "achievementType": 46, + "userVarIndex": 46 + }, + { + "achievementType": 47, + "userVarIndex": 47 + }, + { + "achievementType": 48, + "userVarIndex": 42 + }, + { + "achievementType": 49, + "userVarIndex": 43 + }, + { + "achievementType": 50, + "userVarIndex": 34 + }, + { + "achievementType": 51, + "userVarIndex": 35 + }, + { + "achievementType": 52, + "userVarIndex": 51 + }, + { + "achievementType": 53, + "userVarIndex": 50 + }, + { + "achievementType": 54, + "userVarIndex": 54 + }, + { + "achievementType": 55, + "userVarIndex": 55 + }, + { + "achievementType": 56, + "userVarIndex": 56 + }, + { + "achievementType": 57, + "userVarIndex": 57 + }, + { + "achievementType": 58, + "userVarIndex": 52 + }, + { + "achievementType": 59, + "userVarIndex": 53 + }, + { + "achievementType": 60, + "userVarIndex": 58 + }, + { + "achievementType": 61, + "userVarIndex": 59 + }, + { + "achievementType": 62, + "userVarIndex": 60 + }, + { + "achievementType": 63, + "userVarIndex": 61 + }, + { + "achievementType": 64, + "userVarIndex": 62 + }, + { + "achievementType": 65, + "userVarIndex": 63 + } +], +"levelRewards":[ + { + "levelIndex": 1, + "endLevelIndex": 25, + "isEndLevelInfinite": false, + "lootData": + [ + { + "weight": 35, + "lootTableDefID": "13629", + "itemDefID": "", + "minCount": 1, + "maxCount": 1 + }, + { + "weight": 35, + "lootTableDefID": "13635", + "itemDefID": "", + "minCount": 1, + "maxCount": 1 + }, + { + "weight": 20, + "lootTableDefID": "13636", + "itemDefID": "", + "minCount": 1, + "maxCount": 1 + }, + { + "weight": 5, + "lootTableDefID": "13637", + "itemDefID": "", + "minCount": 1, + "maxCount": 1 + }, + { + "weight": 5, + "lootTableDefID": "13638", + "itemDefID": "", + "minCount": 1, + "maxCount": 1 + } + ] + }, + { + "levelIndex": 26, + "endLevelIndex": 50, + "isEndLevelInfinite": false, + "lootData": + [ + { + "weight": 20, + "lootTableDefID": "13629", + "itemDefID": "-1", + "minCount": 1, + "maxCount": 1 + }, + { + "weight": 25, + "lootTableDefID": "13635", + "itemDefID": "", + "minCount": 1, + "maxCount": 1 + }, + { + "weight": 20, + "lootTableDefID": "13636", + "itemDefID": "", + "minCount": 1, + "maxCount": 1 + }, + { + "weight": 10, + "lootTableDefID": "13637", + "itemDefID": "", + "minCount": 1, + "maxCount": 1 + }, + { + "weight": 25, + "lootTableDefID": "13638", + "itemDefID": "", + "minCount": 1, + "maxCount": 1 + } + ] + }, + { + "levelIndex": 51, + "endLevelIndex": 100, + "isEndLevelInfinite": false, + "lootData": + [ + { + "weight": 5, + "lootTableDefID": "13629", + "itemDefID": "", + "minCount": 1, + "maxCount": 1 + }, + { + "weight": 20, + "lootTableDefID": "13635", + "itemDefID": "", + "minCount": 1, + "maxCount": 1 + }, + { + "weight": 15, + "lootTableDefID": "13636", + "itemDefID": "", + "minCount": 1, + "maxCount": 1 + }, + { + "weight": 10, + "lootTableDefID": "13637", + "itemDefID": "", + "minCount": 1, + "maxCount": 1 + }, + { + "weight": 50, + "lootTableDefID": "13638", + "itemDefID": "", + "minCount": 1, + "maxCount": 1 + } + ] + }, + { + "levelIndex": 101, + "endLevelIndex": 300, + "isEndLevelInfinite": true, + "lootData": + [ + { + "weight": 10, + "lootTableDefID": "13635", + "itemDefID": "", + "minCount": 1, + "maxCount": 1 + }, + { + "weight": 10, + "lootTableDefID": "13636", + "itemDefID": "", + "minCount": 1, + "maxCount": 1 + }, + { + "weight": 5, + "lootTableDefID": "13637", + "itemDefID": "", + "minCount": 1, + "maxCount": 1 + }, + { + "weight": 75, + "lootTableDefID": "13638", + "itemDefID": "", + "minCount": 1, + "maxCount": 1 + } + ] + } +], +"puzzleRewards": ["12591", "12594", "12597", "12600", "12603", "12606"] +} \ No newline at end of file diff --git a/src/main/resources/minigames/doordye.json b/src/main/resources/minigames/doordye.json index 515ba9ab..c8a14aa3 100644 --- a/src/main/resources/minigames/doordye.json +++ b/src/main/resources/minigames/doordye.json @@ -2392,7 +2392,7 @@ "threeStarScore": 1060, "timeBonus": { "50": 4, - "70": 43, + "70": 3, "100": 2, "150": 1 }, diff --git a/src/test/java/org/asf/centuria/TestChatClient.java b/src/test/java/org/asf/centuria/TestChatClient.java index 58a75447..2ed75ab3 100644 --- a/src/test/java/org/asf/centuria/TestChatClient.java +++ b/src/test/java/org/asf/centuria/TestChatClient.java @@ -152,13 +152,13 @@ private void initialize() { panel_1.add(lblDirectorIp); txtDirectorServer = new JTextField(); - txtDirectorServer.setText("http://aerialworks.ddns.net:6969/v1/bestGameServer"); + txtDirectorServer.setText("http://emuferal.ddns.net:6969/v1/bestGameServer"); txtDirectorServer.setColumns(10); txtDirectorServer.setBounds(246, 28, 272, 19); panel_1.add(txtDirectorServer); txtAPIServer = new JTextField(); - txtAPIServer.setText("https://aerialworks.ddns.net:6970"); + txtAPIServer.setText("https://emuferal.ddns.net:6970"); txtAPIServer.setColumns(10); txtAPIServer.setBounds(246, 80, 272, 19); panel_1.add(txtAPIServer); diff --git a/src/test/java/org/asf/centuria/TestModule.java b/src/test/java/org/asf/centuria/TestModule.java index d6a3d747..4526ee10 100644 --- a/src/test/java/org/asf/centuria/TestModule.java +++ b/src/test/java/org/asf/centuria/TestModule.java @@ -1,6 +1,8 @@ package org.asf.centuria; +import org.asf.centuria.accounts.PlayerInventory; import org.asf.centuria.accounts.SaveMode; +import org.asf.centuria.accounts.SaveSettings; import org.asf.centuria.modules.ICenturiaModule; import org.asf.centuria.modules.eventbus.EventListener; import org.asf.centuria.modules.events.accounts.AccountPreloginEvent; @@ -41,6 +43,8 @@ public void prelogin(AccountPreloginEvent event) { public void registerCommands(ModuleCommandSyntaxListEvent event) { event.addCommandSyntaxMessage("test"); event.addCommandSyntaxMessage("migrate"); + event.addCommandSyntaxMessage("creativesave"); + event.addCommandSyntaxMessage("switchsave"); } @EventListener @@ -57,6 +61,36 @@ public void runCommand(ChatCommandEvent event) { event.getAccount().getOnlinePlayerInstance().client .sendPacket("%xt%mod:ft%-1%disconnect%Disconnected%Account data migration in progress%Log out%"); event.getAccount().migrateSaveDataToManagedMode(); + } else if (event.getCommandID().equals("creativesave")) { + if (event.getAccount().getSaveMode() != SaveMode.MANAGED) { + event.respond("Not using managed data"); + return; + } + + if (event.getAccount().getSaveManager().createSave("creative")) { + PlayerInventory inv = event.getAccount().getSaveManager().getSaveSpecificInventoryOf("creative"); + SaveSettings settings = inv.getSaveSettings(); + settings.giveAllAvatars = true; + settings.giveAllClothes = true; + settings.giveAllCurrency = true; + settings.giveAllFurnitureItems = true; + settings.giveAllMods = true; + settings.giveAllResources = true; + settings.giveAllSanctuaryTypes = true; + settings.giveAllWings = true; + inv.writeSaveSettings(); + event.respond("Done"); + } else + event.respond("Failed"); + } else if (event.getCommandID().equals("switchsave")) { + if (event.getAccount().getSaveMode() != SaveMode.MANAGED) { + event.respond("Not using managed data"); + return; + } + + event.getAccount().getSaveManager().switchSave(event.getCommandArguments()[0]); + event.getAccount().getOnlinePlayerInstance().client + .sendPacket("%xt%mod:ft%-1%disconnect%Disconnected%Save switched%Log out%"); } } diff --git a/updaterats.bat b/updaterats.bat deleted file mode 100644 index a47660dc..00000000 --- a/updaterats.bat +++ /dev/null @@ -1,30 +0,0 @@ -@echo off -set git="https://aerialworks.ddns.net/ASF/RATS.git" -set dir="%cd%" - -echo Updating RaTs! installation for libraries... -if EXIST libraries rmdir /S /Q libraries - -echo Cloning git repository... -set tmpdir="%userprofile%\AppData\Local\Temp\build-rats-connective-http-standalone" -if EXIST "%tmpdir%" rmdir /S /Q "%tmpdir%" - -mkdir "%tmpdir%" -git clone %git% "%tmpdir%" -cd "%tmpdir%" -echo. - -echo Building... -goto execute - -:execute -cmd /c java -cp gradle/wrapper/gradle-wrapper.jar org.gradle.wrapper.GradleWrapperMain installation - -if NOT EXIST %dir%\libraries mkdir %dir%\libraries -for /R build\Binaries\ConnectiveHTTP %%f in (*.jar) do copy /Y %%f %dir%\libraries >NUL -goto exitmeth - -:exitmeth -cd "%dir%" -rmdir /S /Q %tmpdir% -echo. diff --git a/updaterats.sh b/updaterats.sh deleted file mode 100644 index f38fca68..00000000 --- a/updaterats.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash -git="https://aerialworks.ddns.net/ASF/RATS.git" -dir="$(pwd)" - -echo 'Updating RaTs! installation for libraries...' -rm -rf libraries/ 2>/dev/null - -echo Cloning git repository... -tmpdir="/tmp/build-rats-connective-http-standalone/$(date "+%s-%N")" -rm -rf "$tmpdir" -mkdir -p "$tmpdir" -git clone $git "$tmpdir" -cd "$tmpdir" -echo - -function exitmeth() { - cd "$dir" - rm -rf "$tmpdir" - echo - exit $1 -} - -function execute() { - ./gradlew installation || return $? - - if [ ! -d "$dir/libraries" ]; then mkdir "$dir/libraries"; fi - cp -r "build/Binaries/ConnectiveHTTP/"*.jar "$dir/libraries" -} - -echo Building... -execute -exitmeth $? diff --git a/uploadupdate.sh b/uploadupdate.sh old mode 100644 new mode 100755 index 031fb6fe..1ad4d72f --- a/uploadupdate.sh +++ b/uploadupdate.sh @@ -1,21 +1,21 @@ #!/bin/bash -server="https://aerialworks.ddns.net/extra/centuria" +serverurl="https://emuferal.ddns.net" function uploadToServers() { version="$1" channel="$2" - base="$server/$channel" + baseurl="$serverurl/$channel" function upload() { for file in $1/* ; do pref=$2 - target="${file:${#pref}}" - if [ -f "$file" ]; then - echo curl -X PUT "$base/$version/$target" -u "REDACTED" --data-binary @"$file" - curl -X PUT "$base/$version/$target" -u "$username:$password" --data-binary @"$file" - fi + targetf="${file:${#pref}}" + if [ -f "$file" ]; then + echo 'curl "'"${baseurl}/${version}/${targetf}"'" -X PUT -u "REDACTED" --data-binary @"'"$file"'"' + curl "${baseurl}/${version}/${targetf}" -X PUT -u "$username:$password" --data-binary @"$file" + fi if [ "$?" != "0" ]; then echo Upload failure! exit 1 @@ -27,15 +27,15 @@ function uploadToServers() { } upload build/update build/update/ - echo curl -X PUT "$base/update.info" -u "REDACTED" --data-binary "$version" - curl -X PUT "$base/update.info" -u "$username:$password" --data-binary "$version" + echo curl -X PUT "$baseurl/update.info" -u "REDACTED" --data-binary "$version" + curl -X PUT "$baseurl/update.info" -u "$username:$password" --data-binary "$version" source version.info } # Current channel source version.info -rm -rf build/Update +rm -rf build/update read -rp "Server username: " username read -rsp "Server upload password: " password echo diff --git a/version.info b/version.info index b7ef4762..8a8239c3 100644 --- a/version.info +++ b/version.info @@ -1,2 +1,2 @@ -version=1.6.4.B3 +version=b1.7 channel=beta