diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..f91f646 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,12 @@ +# +# https://help.github.com/articles/dealing-with-line-endings/ +# +# Linux start script should use lf +/gradlew text eol=lf + +# These are Windows script files and should use crlf +*.bat text eol=crlf + +# Binary files should be left untouched +*.jar binary + diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 8efb003..178d985 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,7 +5,7 @@ version: 2 updates: - - package-ecosystem: "maven" # See documentation for possible values + - package-ecosystem: "gradle" # See documentation for possible values directory: "/" # Location of package manifests schedule: interval: "weekly" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4789211..e5eee58 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,14 +1,8 @@ -# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time -# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven - -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. +# Smoke compile on PR name: Java CI with Maven -on: [push, pull_request] +on: [pull_request] jobs: build: @@ -16,17 +10,12 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - name: Set up JDK 17 - uses: actions/setup-java@v4 - with: - java-version: '17' - distribution: 'temurin' - cache: maven - - name: Build with Maven - run: mvn -B package --file pom.xml - - run: mkdir staging && cp target/*.jar staging - - uses: actions/upload-artifact@v4 - with: - name: Package - path: staging + - uses: actions/checkout@v4 + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + cache: gradle + - name: Build with Maven + run: ./gradlew assemble diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..4937dbf --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,52 @@ +name: Publish to Maven Central + +on: + release: + types: [released] + tags: + - '[0-9]+.[0-9]+.[0-9]+' + +jobs: + build: + if: github + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + cache: gradle + - name: Build with Gradle + # This just publishes to the local file system; jreleaser is responsible for uploading to maven central + run: ./gradlew publish + - name: Upload to Maven Central + env: + # Needs to have access to the io.github.ArchipelagoMW namespace in maven central + JRELEASER_MAVENCENTRAL_USERNAME: ${{ secrets.mavenCentralUsername }} + JRELEASER_MAVENCENTRAL_TOKEN: ${{ secrets.mavenCentralToken }} +# JRELEASER_DEPLOY_MAVEN_NEXUS2_USERNAME: ${{ secrets.mavenCentralUsername }} +# JRELEASER_DEPLOY_MAVEN_NEXUS2_TOKEN: ${{ secrets.mavenCentralToken }} + # The content uploaded to maven central needs to be signed. The public keys need to be published, + # see https://central.sonatype.org/publish/requirements/gpg/#distributing-your-public-key for accepted + # locations + JRELEASER_GPG_PASSPHRASE: ${{ secrets.gpgPassphrase }} + # The content here needs to be the values of the public and secret key pair + JRELEASER_GPG_PUBLIC_KEY: ${{ secrets.gpgPublicKey }} + JRELEASER_GPG_SECRET_KEY: ${{ secrets.gpgSecretKey }} + # Overrides the configuration of 'NEVER' + JRELEASER_DEPLOY_MAVEN_MAVENCENTRAL_RELEASE_DEPLOY_ACTIVE: 'RELEASE' + run: ./gradlew jreleaserDeploy + - name: Store JReleaser Logs + if: failure() + uses: actions/upload-artifact@v4 + with: + name: jreleaser + path: build/jreleaser + - name: Store Reports + if: failure() + uses: actions/upload-artifact@v4 + with: + name: reports + path: build/reports diff --git a/.github/workflows/snapshot.yml b/.github/workflows/snapshot.yml new file mode 100644 index 0000000..8b10626 --- /dev/null +++ b/.github/workflows/snapshot.yml @@ -0,0 +1,48 @@ +name: Publish to Maven Central + +on: + workflow_dispatch: + push: + branches: + - 'main' + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + cache: gradle + - name: Build with Gradle + # This just publishes to the local file system; jreleaser is responsible for uploading to maven central + run: ./gradlew publish -Psnapshot=true + - name: Upload to Maven Central Snapshots + env: + # Needs to have access to the io.github.ArchipelagoMW namespace in maven central + JRELEASER_DEPLOY_MAVEN_NEXUS2_USERNAME: ${{ secrets.mavenCentralUsername }} + JRELEASER_DEPLOY_MAVEN_NEXUS2_TOKEN: ${{ secrets.mavenCentralToken }} + # The content uploaded to maven central needs to be signed. The public keys need to be published, + # see https://central.sonatype.org/publish/requirements/gpg/#distributing-your-public-key for accepted + # locations + JRELEASER_GPG_PASSPHRASE: ${{ secrets.gpgPassphrase }} + # The content here needs to be the values of the public and secret key pair + JRELEASER_GPG_PUBLIC_KEY: ${{ secrets.gpgPublicKey }} + JRELEASER_GPG_SECRET_KEY: ${{ secrets.gpgSecretKey }} + JRELEASER_DEPLOY_MAVEN_MAVENCENTRAL_RELEASE_DEPLOY_ACTIVE: 'NEVER' + run: ./gradlew jreleaserDeploy + - name: Store JReleaser Logs + if: failure() + uses: actions/upload-artifact@v4 + with: + name: jreleaser + path: build/jreleaser + - name: Store Reports + if: failure() + uses: actions/upload-artifact@v4 + with: + name: reports + path: build/reports diff --git a/.github/workflows/update-dependancy-graph.yml b/.github/workflows/update-dependancy-graph.yml index f61cdcc..f7d4e09 100644 --- a/.github/workflows/update-dependancy-graph.yml +++ b/.github/workflows/update-dependancy-graph.yml @@ -16,6 +16,6 @@ jobs: with: java-version: '17' distribution: 'temurin' - cache: maven + cache: gradle - name: Submit Dependency Snapshot - uses: advanced-security/maven-dependency-submission-action@v4 + uses: gradle/actions/dependency-submission@v4 diff --git a/.gitignore b/.gitignore index 54fc534..5f6ca03 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,8 @@ release.properties dependency-reduced-pom.xml buildNumber.properties .mvn/timing.properties +.gradle +build/ ### IntelliJ IDEA ### .idea/ @@ -40,4 +42,6 @@ build/ .vscode/ ### Mac OS ### -.DS_Store \ No newline at end of file +.DS_Store +# Ignore Gradle build output directory +build diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..a5eb3c5 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,146 @@ +import org.jreleaser.model.Active + +plugins { + `java-library` + `maven-publish` + id("org.jreleaser") version "1.20.0" +} + +val snapshot = providers.gradleProperty("snapshot").getOrElse("false") +group = "io.github.archipelagomw" +version = "0.2.1" + if("true" == snapshot) "-SNAPSHOT" else "" + +repositories { + mavenCentral() +} + +dependencies { + testImplementation(libs.junit.jupiter) + + testRuntimeOnly("org.junit.platform:junit-platform-launcher") + + // Gson is indirectly exposed via slotdata helpers + api(libs.gson) + implementation(libs.java.websocket) + implementation(libs.httpclient) + implementation(libs.httpcore) +} + +// Apply a specific Java toolchain to ease working on different environments. +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + withSourcesJar() + withJavadocJar() + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } +} + +tasks.withType().configureEach { + options.release = 8 +} + +tasks.named("test") { + // Use JUnit Platform for unit tests. + useJUnitPlatform() +} + +tasks.named("javadoc") { + options { + (this as CoreJavadocOptions).addBooleanOption("Xdoclint:none", true) + } +} + +publishing { + publications { + create("javaClient") { + from(components["java"]) + repositories { + // For the time being + mavenLocal() + maven { + url = uri(layout.buildDirectory.dir("staging-deploy")) + } + } + pom { + name = "Archipelago Java Library" + description = "Java library to connect to an Archipelago Server" + url = "https://github.com/ArchipelagoMW/Java-Client" + scm { + connection = "scm:git://github.com/ArchipelagoMW/Java-Client" + developerConnection = "scm:git:https://github.com/ArchipelagoMW/Java-Client.git" + url = "https://github.com/ArchipelagoMW/Java-Client" + } + inceptionYear = "2021" + licenses { + license { + name = "MIT License" + url = "https://github.com/ArchipelagoMW/Java-Client/blob/main/LICENSE" + distribution = "repo" + } + } + developers { + developer { + name = "PlatanoBailando" + email = "cjmang@gmail.com" + } + developer { + name = "digiholic" + } + } + contributors { + contributor { + name = "Kono Tyran" + roles.add("Author") + } + contributor { + name = "mattman107" + } + contributor { + name = "charlesfire" + } + } + } + } + } +} + +jreleaser { + signing { + active = Active.ALWAYS + armored = true + } + release { + github { + enabled = true + repoOwner = "ArchipelagoMW" + overwrite = false + skipRelease = true + } + } + deploy { + maven { + mavenCentral { + register("release-deploy") { + // Releases intended to be turned on via environment variable + applyMavenCentralRules = true + url = "https://central.sonatype.com/api/v1/publisher" + stagingRepository("build/staging-deploy") + } + } + nexus2 { + register("snapshot-deploy") { + active = Active.SNAPSHOT + applyMavenCentralRules = true + snapshotSupported = true + closeRepository = true + releaseRepository = true + url = "https://central.sonatype.com/api/v1/publisher" + snapshotUrl = "https://central.sonatype.com/repository/maven-snapshots/" + stagingRepository("build/staging-deploy") + } + } + } + } +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..a4a7119 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,6 @@ +# This file was generated by the Gradle 'init' task. +# https://docs.gradle.org/current/userguide/build_environment.html#sec:gradle_configuration_properties + +# jreleaser breaks with this +org.gradle.configuration-cache=false + diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..6014073 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,16 @@ +# This file was generated by the Gradle 'init' task. +# https://docs.gradle.org/current/userguide/platforms.html#sub::toml-dependencies-format + +[versions] +junit-jupiter = "5.13.4" +java-websocket = "1.6.0" +gson = "2.13.2" +httpclient = "5.5.1" +httpcore = "5.3.6" + +[libraries] +junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit-jupiter" } +java-websocket = { module = "org.java-websocket:Java-WebSocket", version.ref = "java-websocket" } +gson = { module = "com.google.code.gson:gson", version.ref = "gson" } +httpclient = { module = "org.apache.httpcomponents.client5:httpclient5", version.ref = "httpclient" } +httpcore = { module = "org.apache.httpcomponents.core5:httpcore5", version.ref = "httpcore" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..1b33c55 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..ca025c8 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..23d15a9 --- /dev/null +++ b/gradlew @@ -0,0 +1,251 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH="\\\"\\\"" + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..db3a6ac --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH= + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/pom.xml b/pom.xml deleted file mode 100644 index 44cae2b..0000000 --- a/pom.xml +++ /dev/null @@ -1,130 +0,0 @@ - - - 4.0.0 - - dev.koifysh - archipelago-client - 0.1.20-SNAPSHOT - - Archipelago Java Library - Library to connect to an Archipelago Server - https://github.com/ArchipelagoMW/Java-Client - - - - MIT License - https://github.com/ArchipelagoMW/Java-Client/blob/main/LICENSE - repo - - - - - scm:git://github.com/ArchipelagoMW/Java-Client - scm:git:https://github.com/ArchipelagoMW/Java-Client.git - https://github.com/ArchipelagoMW/Java-Client - archipelago-client-0.1.20 - - - - - Kono Tyran - Kono@koifysh.dev - - - mattman107 - - - - - 8 - 8 - UTF-8 - - - - - org.java-websocket - Java-WebSocket - 1.6.0 - - - com.google.code.gson - gson - 2.13.1 - - - org.apache.httpcomponents.client5 - httpclient5 - 5.4.4 - - - org.apache.httpcomponents.core5 - httpcore5 - 5.3.4 - - - - - - - org.apache.maven.plugins - maven-release-plugin - 3.1.1 - - - org.apache.maven.plugins - maven-source-plugin - 3.3.1 - - - attach-sources - - jar-no-fork - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 3.8.0 - - - attach-javadocs - - jar - - - - - none - - - - org.apache.maven.plugins - maven-gpg-plugin - 3.2.4 - - - sign-artifacts - verify - - sign - - - - - - org.sonatype.central - central-publishing-maven-plugin - 0.5.0 - true - - central - true - - - - - - \ No newline at end of file diff --git a/readme.md b/readme.md index 935525b..9e09101 100644 --- a/readme.md +++ b/readme.md @@ -1,6 +1,6 @@ Archipelago Java Client ======================= -[![Maven Central Version](https://img.shields.io/maven-central/v/dev.koifysh/archipelago-client)](https://central.sonatype.com/artifact/dev.koifysh/archipelago-client) +[![Maven Central Version](https://img.shields.io/maven-central/v/io.github.archipelagomw/Java-Client)](https://central.sonatype.com/artifact/io.github.archipelagomw/Java-Client) A java client Library to connect to an [Archipelago](http://github.com/ArchipelagoMW/Archipelago) Server. @@ -12,15 +12,77 @@ Use the following Code snippits to add this library to your project using the fo To use maven add this dependency to your `pom.xml`: ```xml - dev.koifysh - archipelago-client - 0.1.19 + io.github.archipelagomw + Java-Client + 0.2.0 ``` ### Gradle To use Gradle add the maven central repository to your repositories list: then add this to your `dependancy` section -```java -implementation 'dev.koifysh:archipelago-client:0.1.19' -``` \ No newline at end of file +```groovy +implementation 'io.github.archipelagomw:Java-Client:0.2.0' +``` + +## Using Snapshots +This repository is setup to publish snapshots when new commits hit `main`. If you want +to use the snapshot version you will need to do the following: + +### Maven +From [Maven Central Documentation](https://central.sonatype.org/publish/publish-portal-snapshots/#publishing-via-other-methods) + +Configure your pom.xml file with the following section: + +```xml + + + Central Portal Snapshots + central-portal-snapshots + https://central.sonatype.com/repository/maven-snapshots/ + + false + + + true + + + +``` + +And add the snapshot version into your dependencies: + +```xml + + io.github.archipelagomw + Java-Client + 0.2.1-SNAPSHOT + +``` + + +### Gradle + +From [Maven Central Documentation](https://central.sonatype.org/publish/publish-portal-snapshots/#consuming-via-gradle) + +Configure your `build.gradle` with the following: + +```groovy +repositories { + maven { + name = 'Central Portal Snapshots' + url = 'https://central.sonatype.com/repository/maven-snapshots/' + + // Only search this repository for the specific dependency + content { + includeModule("io.github.archipelagomw", "Java-Client") + } + } + mavenCentral() +} +``` + +And add the snapshot version into your dependencies: +```groovy +implementation 'io.github.archipelagomw:Java-Client:0.2.1-SNAPSHOT' +``` diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..c9b7028 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,14 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * The settings file is used to specify which projects to include in your build. + * For more detailed information on multi-project builds, please refer to https://docs.gradle.org/8.14/userguide/multi_project_builds.html in the Gradle documentation. + */ + +plugins { + // Apply the foojay-resolver plugin to allow automatic download of JDKs + id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" +} + +rootProject.name = "Java-Client" +//include("lib") diff --git a/src/main/java/dev/koifysh/archipelago/Client.java b/src/main/java/dev/koifysh/archipelago/Client.java deleted file mode 100644 index 19ec0bb..0000000 --- a/src/main/java/dev/koifysh/archipelago/Client.java +++ /dev/null @@ -1,628 +0,0 @@ -package dev.koifysh.archipelago; - -import dev.koifysh.archipelago.events.RetrievedEvent; -import dev.koifysh.archipelago.flags.ItemsHandling; -import dev.koifysh.archipelago.network.server.ConnectUpdatePacket; -import dev.koifysh.archipelago.network.server.RoomInfoPacket; -import dev.koifysh.archipelago.parts.DataPackage; -import dev.koifysh.archipelago.parts.NetworkSlot; -import dev.koifysh.archipelago.parts.Version; -import dev.koifysh.archipelago.network.client.*; -import org.apache.hc.core5.net.URIBuilder; - -import com.google.gson.Gson; - -import java.io.*; -import java.net.URI; -import java.net.URISyntaxException; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.*; -import java.util.logging.Logger; - -public abstract class Client { - - private final static Logger LOGGER = Logger.getLogger(Client.class.getName()); - - private static String OS = System.getProperty("os.name").toLowerCase(); - - private static final Path windowsDataPackageCache; - - private static final Path otherDataPackageCache; - - static - { - String appData = System.getenv("LOCALAPPDATA"); - String winHome = System.getenv("USERPROFILE"); - String userHome = System.getProperty("user.home"); - - if(appData == null || appData.isEmpty()) { - windowsDataPackageCache = Paths.get(winHome, "appdata","local","Archipelago","cache","datapackage"); - } else { - windowsDataPackageCache = Paths.get(appData, "Archipelago", "cache", "datapackage"); - } - - otherDataPackageCache = Paths.get(userHome, ".cache", "Archipelago", "datapackage"); - } - - private static Path dataPackageLocation; - - protected Map versions; - - protected ArrayList games; - - private final static Gson gson = new Gson(); - - private int hintPoints; - - private WebSocket webSocket; - - private String password; - - private final String UUID; - - private RoomInfoPacket roomInfo; - - private DataPackage dataPackage; - - public static Client client; - - private final LocationManager locationManager; - private final ItemManager itemManager; - private final EventManager eventManager; - - public static final Version protocolVersion = new Version(0, 6, 1); - - private int team; - private int slot; - private HashMap slotInfo; - private String name = "Name not set"; - private String game = "Game not set"; - private String alias; - private Set tags = new HashSet<>(); - private int itemsHandlingFlags = 0b000; - - public Client() { - //Determine what platform we are on - if(OS.startsWith("windows")){ - dataPackageLocation = windowsDataPackageCache; - } else{ - dataPackageLocation = otherDataPackageCache; - } - - if(dataPackage == null){ - dataPackage = new DataPackage(); - } - - UUID = dataPackage.getUUID(); - - eventManager = new EventManager(); - locationManager = new LocationManager(this); - itemManager = new ItemManager(this); - client = this; - } - - /** - * Sets the name of the game to send to Archipelago's servers - * @param game the name of your game. - */ - public void setGame(String game) { - this.game = game; - } - - /** - * overwrite, and set all tags sent to the Archipelago server. - * this will overwrite any previous tags that have been set. - * @param tags a Set of tags to send. - */ - public void setTags(Set tags) { - if (!this.tags.equals(tags)) { - this.tags = tags; - if (isConnected()) { - ConnectUpdatePacket packet = new ConnectUpdatePacket(); - packet.tags = this.tags; - webSocket.sendPacket(packet); - } - } - } - - /** - * add a tag to your list, keeping all previous tags intact. - * @param tag String tag to be added. - */ - public void addTag(String tag) { - if (!this.tags.contains(tag)) { - tags.add(tag); - if (isConnected()) { - ConnectUpdatePacket packet = new ConnectUpdatePacket(); - packet.tags = this.tags; - webSocket.sendPacket(packet); - } - } - } - - /** - * removes supplied tag, if it exists. - * @param tag String tag to be removed. - */ - public void removeTag(String tag) { - if (this.tags.contains(tag)) { - tags.remove(tag); - if (isConnected()) { - ConnectUpdatePacket packet = new ConnectUpdatePacket(); - packet.tags = this.tags; - webSocket.sendPacket(packet); - } - } - } - - - protected void loadDataPackage() { - synchronized (Client.class){ - File directoryPath = dataPackageLocation.toFile(); - - //ensure the path to the cache exists - if(directoryPath.exists() && directoryPath.isDirectory()){ - //loop through all Archipelago cache folders to find valid data package files - Map localGamesList = new HashMap(); - - for(File gameDir : directoryPath.listFiles()){ - if(gameDir.isDirectory()){ - localGamesList.put(gameDir.getName(), gameDir); - } - } - - if(localGamesList.isEmpty()){ - //cache doesn't exist. Create the filepath - boolean success = directoryPath.mkdirs(); - if(success){ - LOGGER.info("DataPackage directory didn't exist. Starting from a new one."); - } else{ - LOGGER.severe("Failed to make directories for datapackage cache."); - } - return; - } - - for(String gameName : games) { - File dir = localGamesList.get(gameName); - - if(null == dir){ - continue; - } - - //check all checksums - for(File version : dir.listFiles()){ - String versionStr = versions.get(gameName); - if(versionStr != null && versionStr.equals(version.getName())) { - try(FileReader reader = new FileReader(version)){ - updateDataPackage(gson.fromJson(reader, DataPackage.class)); - LOGGER.info("Read datapackage for Game: ".concat(gameName).concat(" Checksum: ").concat(version.getName())); - } catch (IOException e){ - LOGGER.info("Failed to read a datapackage. Starting with a new one."); - } - } - } - } - } - } - } - - public void saveDataPackage() { - synchronized (Client.class){ - //Loop through games to ensure we have folders for each of them in the cache - for(String gameName : games){ - File gameFolder = dataPackageLocation.resolve(gameName).toFile(); - if(!gameFolder.exists()){ - //game folder not found. Make it - gameFolder.mkdirs(); - } - - //save the datapackage - String gameVersion = versions.get(gameName); - if(gameVersion == null) { - continue; - } - - //if key is for this game - File filePath = dataPackageLocation.resolve(gameName).resolve(gameVersion).toFile(); - - try (Writer writer = new FileWriter(filePath)){ - //if game is in list of games, save it - gson.toJson(dataPackage.getGame(gameName), writer); - LOGGER.info("Saving datapackage for Game: ".concat(gameName).concat(" Checksum: ").concat(gameVersion)); - } catch (IOException e) { - LOGGER.warning("unable to save DataPackage."); - } - - } - } - } - - /** - * Returns true only if connected to an Archipelago server. - * @return true if connected, otherwise false - */ - public boolean isConnected() { - return webSocket != null && webSocket.isOpen(); - } - - /** - * closes a connection to the Archipelago server if connected. - */ - public void close() { - if (webSocket != null) - webSocket.close(); - } - - /** - * sets a password to authenticate with to join a password protected room. - * @param password room password - */ - public void setPassword(String password) { - this.password = password; - } - - void setHintPoints(int hintPoints) { - this.hintPoints = hintPoints; - } - - /** - * sets the slot name to connect to an Archcipelago server with. - * @param name - */ - public void setName(String name) { - this.name = name; - } - - void setSlot(int slot) { - this.slot = slot; - } - - void setTeam(int team) { - this.team = team; - } - - void setSlotInfo(HashMap slotInfo) { - this.slotInfo = slotInfo; - } - - void setRoomInfo(RoomInfoPacket roomInfo) { - this.roomInfo = roomInfo; - } - - void updateDataPackage(DataPackage newData) { - dataPackage.update(newData); - } - - /** - * - * @return team ID - */ - public int getTeam() { - return team; - } - - /** - * - * @return Slot ID - */ - public int getSlot() { - return slot; - } - - /** - * fetches the - * @return Room info. - */ - public RoomInfoPacket getRoomInfo() { - return roomInfo; - } - - public HashMap getSlotInfo() {return slotInfo;} - - /** - * Works exactly like {@link #connect(URI, boolean)} with allowDowngrade set to true; - * @param address - * @throws URISyntaxException on malformed address - */ - public void connect(String address) throws URISyntaxException { - URIBuilder builder = new URIBuilder((!address.contains("//")) ? "//" + address : address); - if (builder.getPort() == -1) { //set default port if not included - builder.setPort(38281); - } - - if (webSocket != null && webSocket.isOpen()) { - LOGGER.fine("previous WebSocket is open, closing."); - webSocket.close(); - } - - if (builder.getScheme() == null) { - builder.setScheme("wss"); - connect(builder.build(), true); - return; - } - - connect(builder.build()); - } - - /** - * Works exactly like {@link #connect(URI, boolean)} but allowDowngrade is False - * @param address Address to connect to - */ - public void connect(URI address) { - connect(address, false); - } - - /** - * Connects to an Archipelago server with previously provided info. - *
- * supply the following info before calling this method - *
- * game: {@link #setGame(String)}
- * slot name: {@link #setName(String)}
- *
- * if no protocol wss:// or ws:// is given will attempt a ssl connection - * to the supplied address, if that fails it will then try a non-ssl connection, unless allowDowngrade is false.
- *
- * Do not prefix address with wss:// or ws://. let the user enter a protocol to use. - * by default ssl will be tried first, if that fails then non-ssl will be used. unless allowDowngrade is set to false. - * @param address address of the archipelago server. - * @param allowDowngrade if set to false will prevent auto downgrade of ssl connection. - */ - public void connect(URI address, boolean allowDowngrade) { - LOGGER.fine("attempting WebSocket connection to " + address.toString()); - webSocket = new WebSocket(address, this); - locationManager.setAPWebSocket(webSocket); - itemManager.setAPWebSocket(webSocket); - webSocket.connect(allowDowngrade); - } - - /** - * Sends a Chat message to all other connected Clients. - * @param message Message to send. - */ - public void sendChat(String message) { - if (webSocket == null) - return; - if (webSocket.isAuthenticated()) { - webSocket.sendChat(message); - } - } - - /** - * inform the Archipelago server that a location ID has been checked. - * @param locationID id of a location. - * @return true if packet was successfully sent. False if not connected or otherwise failed to send. - */ - public boolean checkLocation(long locationID) { - return locationManager.checkLocation(locationID); - } - - /** - * inform the Archipelago server that a collection of location ID has been checked. - * @param locationIDs a collection of a locations. - * @return true if packet was successfully sent. False if not connected or otherwise failed to send. - */ - public boolean checkLocations(Collection locationIDs) { - return locationManager.checkLocations(locationIDs); - } - - /** - * Ask the server for information about what is in locations. you will get a response in the {@link dev.koifysh.archipelago.events.LocationInfoEvent} event. - * @param locationIDs List of location ID's to request info on. - */ - public void scoutLocations(ArrayList locationIDs) { - locationIDs.removeIf( location -> !dataPackage.getGame(game).locationNameToId.containsValue(location)); - webSocket.scoutLocation(locationIDs); - } - - public abstract void onError(Exception ex); - - public abstract void onClose(String Reason, int attemptingReconnect); - - public DataPackage getDataPackage() { - return dataPackage; - } - - public String getMyName() { - return name; - } - - public String getPassword() { - return password; - } - - public int getHintPoints() { - return hintPoints; - } - - public String getGame() { - return game; - } - - public String getConnectedAddress() { - if (isConnected()) - return webSocket.getRemoteSocketAddress().getHostName()+":"+ webSocket.getRemoteSocketAddress().getPort(); - else - return ""; - } - - /** - * this should not need to be called externally but is left public just in case. - */ - public void reconnect() { - webSocket.reconnect(); - } - - /** - * Gets the UUID of this client. - * @return UUID of the client, this should theoretically never change. - */ - public String getUUID() { - return UUID; - } - - /** - * gets the alias of this slot. - * @return Alias of the slot connected to. - */ - public String getAlias() { - return alias; - } - - /** - * sets an Alias for this slot on the Archipelago server. - * @param alias Name to set the alias to. - */ - void setAlias(String alias) { - this.alias = alias; - } - - public LocationManager getLocationManager() { - return locationManager; - } - - public ItemManager getItemManager() { - return itemManager; - } - - /** - * Update the current game status. - * @see ClientStatus - * - * @param status a {@link ClientStatus} to send to the server. - */ - public void setGameState(ClientStatus status) { - if (webSocket == null) - return; - if (webSocket.isAuthenticated()) - webSocket.sendPacket(new StatusUpdatePacket(status)); - } - - /** - * manually trigger a resync to the Archipelago server. this should be done automatically if the library detects a desync. - */ - public void sync() { - webSocket.sendPacket(new SyncPacket()); - } - - public void sendBounce(BouncePacket bouncePacket) { - if (webSocket == null) - return; - if (webSocket.isAuthenticated()) - webSocket.sendPacket(bouncePacket); - } - - /** - * disconnects from a connected Archipelago server. - */ - public void disconnect() { - webSocket.close(); - } - - /** - * @return set of tags currently in use. - */ - public Set getTags() { - return tags; - } - - /** - * fetch the itemflags that have been set, bitwise Or against {@link ItemsHandling} to read. - * @return items handling int. - */ - public int getItemsHandlingFlags() { - return itemsHandlingFlags; - } - - /** - * fetch the itemflags that have been set, bitwise Or against {@link ItemsHandling} to read. - */ - public void setItemsHandlingFlags(int itemsHandlingFlags) { - this.itemsHandlingFlags = itemsHandlingFlags; - } - - /** - * @return the event manager. - */ - public EventManager getEventManager() { - return eventManager; - } - - /** - * Uses DataStorage to save a value on the AP server. - * - */ - public int dataStorageSet(SetPacket setPacket) { - if (webSocket == null || !webSocket.isAuthenticated()) - return 0; - - webSocket.sendPacket(setPacket); - return setPacket.getRequestID(); - } - - /** - * Registers to receive updates of when a key in the Datastorage has been changed on the server. - * - * @param keys List of Keys to be notified of. - */ - public void dataStorageSetNotify(Collection keys) { - if (webSocket == null || !webSocket.isAuthenticated()) - return; - webSocket.sendPacket(new SetNotifyPacket(keys)); - } - - /** - * Uses DataStorage to reterieve a value from the server will get value back though a - * {@link RetrievedEvent RetrievedEvent}.
- * see following table for list of reserved keys. - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
NameTypeNotes
hints_{team}_{slot} list[Hint] All Hints belonging to the requested Player.
slot_data_{slot} dict[str, any] slot_data belonging to the requested slot.
item_name_groups_{game_name} dict[str, list[str]] item_name_groups belonging to the requested game.
location_name_groups_{game_name} dict[str, list[str]] location_name_groups belonging to the requested game.
client_status_{team}_{slot} ClientStatus The current game status of the requested player.
race_mode int 0 if race mode is disabled, and 1 if it's enabled.
- * - * @param keys a list of keys to retrieve values for - */ - public int dataStorageGet(Collection keys) { - if (webSocket == null || !webSocket.isAuthenticated()) - return 0; - - GetPacket getPacket = new GetPacket(keys); - webSocket.sendPacket(getPacket); - return getPacket.getRequestID(); - } - -} diff --git a/src/main/java/dev/koifysh/archipelago/ItemManager.java b/src/main/java/dev/koifysh/archipelago/ItemManager.java deleted file mode 100644 index 9766d00..0000000 --- a/src/main/java/dev/koifysh/archipelago/ItemManager.java +++ /dev/null @@ -1,74 +0,0 @@ -package dev.koifysh.archipelago; - -import dev.koifysh.archipelago.events.ReceiveItemEvent; -import dev.koifysh.archipelago.network.client.SyncPacket; -import dev.koifysh.archipelago.parts.DataPackage; -import dev.koifysh.archipelago.parts.NetworkItem; - -import java.util.ArrayList; - -public class ItemManager { - - - Client client; - WebSocket webSocket; - - ArrayList receivedItems = new ArrayList<>(); - - int index; - - public ItemManager(Client client) { - this.client = client; - } - - public void receiveItems(ArrayList ids, int index) { - if (index == 0) { - receivedItems = new ArrayList<>(); - } - if (receivedItems.size() == index) { - receivedItems.addAll(ids); - DataPackage dp = client.getDataPackage(); - int myTeam = client.getTeam(); - for (int i = this.index; i < receivedItems.size(); i++) { - NetworkItem item = receivedItems.get(i); - item.itemName = dp.getItem(item.itemID, client.getGame()); - item.locationName = dp.getLocation(item.locationID, client.getSlotInfo().get(item.playerID).game); - item.playerName = client.getRoomInfo().getPlayer(myTeam,item.playerID).alias; - client.getEventManager().callEvent(new ReceiveItemEvent(item, i+1)); - } - - this.index = receivedItems.size(); - } - else { - if(webSocket != null) { - webSocket.sendPacket(new SyncPacket()); - client.getLocationManager().resendAllCheckedLocations(); - } - } - } - - public void writeFromSave(ArrayList receivedItems, int index) { - this.receivedItems = receivedItems; - this.index = index; - } - - public void setAPWebSocket(WebSocket webSocket) { - this.webSocket = webSocket; - } - - public int getIndex() { - return index; - } - - public ArrayList getReceivedItems() { - return receivedItems; - } - - public ArrayList getReceivedItemIDs() { - ArrayList ids = new ArrayList<>(); - for (NetworkItem receivedItem : receivedItems) { - ids.add(receivedItem.itemID); - } - return ids; - } -} diff --git a/src/main/java/dev/koifysh/archipelago/LocationManager.java b/src/main/java/dev/koifysh/archipelago/LocationManager.java deleted file mode 100644 index 10232c7..0000000 --- a/src/main/java/dev/koifysh/archipelago/LocationManager.java +++ /dev/null @@ -1,83 +0,0 @@ -package dev.koifysh.archipelago; - -import dev.koifysh.archipelago.network.client.LocationChecks; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; - -public class LocationManager { - - Client client; - WebSocket webSocket; - - Set checkedLocations = new HashSet<>(); - - Set missingLocations = new HashSet<>(); - - public LocationManager(Client client) { - this.client = client; - } - - public boolean checkLocation(long id) { - return checkLocations(new ArrayList(1) {{add(id);}}); - } - - public boolean checkLocations(Collection ids) { - ids.removeIf( location -> !missingLocations.contains(location)); - checkedLocations.addAll(ids); - missingLocations.removeAll(ids); - LocationChecks packet = new LocationChecks(); - packet.locations.addAll(ids); - if(webSocket == null) - return false; - - if(webSocket.isAuthenticated()) { - webSocket.sendPacket(packet); - return true; - } - return false; - } - - public void sendIfChecked(Set missingChecks) { - LocationChecks packet = new LocationChecks(); - packet.locations = new HashSet<>(); - for (Long missingCheck : missingChecks) { - if(checkedLocations.contains(missingCheck)) { - packet.locations.add(missingCheck); - } - } - if(webSocket != null && !packet.locations.isEmpty()) - webSocket.sendPacket(packet); - } - - public void resendAllCheckedLocations() { - if (webSocket == null) - return; - LocationChecks packet = new LocationChecks(); - packet.locations = checkedLocations; - webSocket.sendPacket(packet); - } - - protected void setAPWebSocket(WebSocket webSocket) { - this.webSocket = webSocket; - } - - public Set getCheckedLocations() { - return checkedLocations; - } - - public Set getMissingLocations() { - return missingLocations; - } - - public void addCheckedLocations(Set newLocations) { - this.checkedLocations.addAll(newLocations); - this.missingLocations.removeAll(newLocations); - } - - public void setMissingLocations(HashSet missingLocations) { - this.missingLocations = missingLocations; - } -} diff --git a/src/main/java/dev/koifysh/archipelago/events/Event.java b/src/main/java/dev/koifysh/archipelago/events/Event.java deleted file mode 100644 index 51ed3a4..0000000 --- a/src/main/java/dev/koifysh/archipelago/events/Event.java +++ /dev/null @@ -1,4 +0,0 @@ -package dev.koifysh.archipelago.events; - -public interface Event { -} diff --git a/src/main/java/dev/koifysh/archipelago/helper/DeathLink.java b/src/main/java/dev/koifysh/archipelago/helper/DeathLink.java deleted file mode 100644 index 4946adc..0000000 --- a/src/main/java/dev/koifysh/archipelago/helper/DeathLink.java +++ /dev/null @@ -1,60 +0,0 @@ -package dev.koifysh.archipelago.helper; - -import dev.koifysh.archipelago.network.client.BouncePacket; -import dev.koifysh.archipelago.network.server.BouncedPacket; -import dev.koifysh.archipelago.events.DeathLinkEvent; - -import java.util.HashMap; - -import static dev.koifysh.archipelago.Client.client; - -/** - * a helper-class for sending and receiving death links. - *
- * enable death links by calling {@link #setDeathLinkEnabled(boolean)} - */ -public class DeathLink { - - static private double lastDeath = 0; - - public static void receiveDeathLink(BouncedPacket bounced) { - try { - if ((Double) bounced.data.getOrDefault("time", 0d) == lastDeath) - return; - - DeathLinkEvent dl = new DeathLinkEvent((String)bounced.data.get("source"), (String)bounced.data.get("cause"), (Double)bounced.data.get("time")); - client.getEventManager().callEvent(dl); - } catch (ClassCastException ex) { - System.out.println("Error Receiving DeathLink, possible malformed bounce packet"); - } - } - - /** - * helper for sending a death link bounce packet. you can send these without enabling death link first, but it is frowned upon. - * @param source A String that is the name of the player sending the death link (does not have to be slot name) - * @param cause A String that is the cause of this death. may be empty. - */ - public static void SendDeathLink(String source, String cause) { - lastDeath = (double)System.currentTimeMillis() / 1000D; - - BouncePacket deathLinkPacket = new BouncePacket(); - deathLinkPacket.tags = new String[]{"DeathLink"}; - deathLinkPacket.setData(new HashMap(){{ - put("cause",cause); - put("time", lastDeath); - put("source",source); - }}); - client.sendBounce(deathLinkPacket); - } - - /** - * Enable or Disable receiving death links. - * @param enabled set to TRUE to enable death links, FALSE to disable. - */ - public static void setDeathLinkEnabled(boolean enabled) { - if(enabled) - client.addTag("DeathLink"); - else - client.removeTag("DeathLink"); - } -} diff --git a/src/main/java/dev/koifysh/archipelago/network/DataStorageOperation.java b/src/main/java/dev/koifysh/archipelago/network/DataStorageOperation.java deleted file mode 100644 index 2346540..0000000 --- a/src/main/java/dev/koifysh/archipelago/network/DataStorageOperation.java +++ /dev/null @@ -1,3 +0,0 @@ -package dev.koifysh.archipelago.network; - - diff --git a/src/main/java/dev/koifysh/archipelago/network/Permission.java b/src/main/java/dev/koifysh/archipelago/network/Permission.java deleted file mode 100644 index b8c7c07..0000000 --- a/src/main/java/dev/koifysh/archipelago/network/Permission.java +++ /dev/null @@ -1,14 +0,0 @@ -package dev.koifysh.archipelago.network; - -public enum Permission { - disabled(0b000), - enabled(0b001), - goal(0b010), - auto(0b110), - auto_enabled(0b111); - - public final int value; - Permission(int value) { - this.value=value; - } -} diff --git a/src/main/java/dev/koifysh/archipelago/network/client/HintStatus.java b/src/main/java/dev/koifysh/archipelago/network/client/HintStatus.java deleted file mode 100644 index 4935873..0000000 --- a/src/main/java/dev/koifysh/archipelago/network/client/HintStatus.java +++ /dev/null @@ -1,21 +0,0 @@ -package dev.koifysh.archipelago.network.client; - -import com.google.gson.annotations.SerializedName; - -public enum HintStatus { - @SerializedName("HINT_UNSPECIFIED") - HINT_UNSPECIFIED(0), - @SerializedName("HINT_NO_PRIORITY") - HINT_NO_PRIORITY(10), - @SerializedName("HINT_AVOID") - HINT_AVOID(20), - @SerializedName("HINT_PRIORITY") - HINT_PRIORITY(30), - @SerializedName("HINT_FOUND") - HINT_FOUND(40); - - public final int value; - HintStatus(int value) { - this.value=value; - } -} \ No newline at end of file diff --git a/src/main/java/dev/koifysh/archipelago/network/client/LocationScouts.java b/src/main/java/dev/koifysh/archipelago/network/client/LocationScouts.java deleted file mode 100644 index e099785..0000000 --- a/src/main/java/dev/koifysh/archipelago/network/client/LocationScouts.java +++ /dev/null @@ -1,16 +0,0 @@ -package dev.koifysh.archipelago.network.client; - -import dev.koifysh.archipelago.network.APPacket; -import dev.koifysh.archipelago.network.APPacketType; - -import java.util.ArrayList; - -public class LocationScouts extends APPacket { - - public ArrayList locations = new ArrayList<>(); - - public LocationScouts(ArrayList locations) { - super(APPacketType.LocationScouts); - this.locations = locations; - } -} diff --git a/src/main/java/dev/koifysh/archipelago/network/client/SyncPacket.java b/src/main/java/dev/koifysh/archipelago/network/client/SyncPacket.java deleted file mode 100644 index 940b259..0000000 --- a/src/main/java/dev/koifysh/archipelago/network/client/SyncPacket.java +++ /dev/null @@ -1,12 +0,0 @@ -package dev.koifysh.archipelago.network.client; - -import dev.koifysh.archipelago.network.APPacket; -import dev.koifysh.archipelago.network.APPacketType; - -public class SyncPacket extends APPacket { - - - public SyncPacket() { - super(APPacketType.Sync); - } -} diff --git a/src/main/java/dev/koifysh/archipelago/network/server/JsonPrintPacket.java b/src/main/java/dev/koifysh/archipelago/network/server/JsonPrintPacket.java deleted file mode 100644 index 6c7b874..0000000 --- a/src/main/java/dev/koifysh/archipelago/network/server/JsonPrintPacket.java +++ /dev/null @@ -1,13 +0,0 @@ -package dev.koifysh.archipelago.network.server; - -import dev.koifysh.archipelago.Print.APPrintPart; -import dev.koifysh.archipelago.network.APPacket; -import dev.koifysh.archipelago.network.APPacketType; - -public class JsonPrintPacket extends APPacket { - APPrintPart[] parts; - - public JsonPrintPacket() { - super(APPacketType.PrintJSON); - } -} diff --git a/src/main/java/io/github/archipelagomw/APResult.java b/src/main/java/io/github/archipelagomw/APResult.java new file mode 100644 index 0000000..aec88e7 --- /dev/null +++ b/src/main/java/io/github/archipelagomw/APResult.java @@ -0,0 +1,55 @@ +package io.github.archipelagomw; + +/** + * Small container class for the outcome of client requests. + * @param The type of the data held + */ +public class APResult { + + public static enum ResultCode { + SUCCESS, + DISCONNECTED, + UNAUTHENTICATED, + OTHER_ERROR; + } + + private final ResultCode code; + private final T value; + + private APResult(ResultCode code) { + this(code, null); + } + + private APResult(ResultCode code, T value) { + this.code = code; + this.value = value; + } + + public static APResult success() { + return new APResult<>(ResultCode.SUCCESS); + } + + public static APResult success(T value) { + return new APResult<>(ResultCode.SUCCESS, value); + } + + public static APResult disconnected() { + return new APResult<>(ResultCode.DISCONNECTED); + } + + public static APResult unauthenticated() { + return new APResult<>(ResultCode.UNAUTHENTICATED); + } + + public static APResult error() { + return new APResult<>(ResultCode.OTHER_ERROR); + } + + public ResultCode getCode() { + return code; + } + + public T getValue() { + return value; + } +} diff --git a/src/main/java/io/github/archipelagomw/Client.java b/src/main/java/io/github/archipelagomw/Client.java new file mode 100644 index 0000000..c407026 --- /dev/null +++ b/src/main/java/io/github/archipelagomw/Client.java @@ -0,0 +1,901 @@ +package io.github.archipelagomw; + +import io.github.archipelagomw.bounce.BouncedManager; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import io.github.archipelagomw.events.LocationInfoEvent; +import io.github.archipelagomw.events.RetrievedEvent; +import io.github.archipelagomw.flags.ItemsHandling; +import io.github.archipelagomw.bounce.DeathLinkHandler; +import io.github.archipelagomw.network.APPacket; +import io.github.archipelagomw.network.client.*; +import io.github.archipelagomw.network.server.ConnectUpdatePacket; +import io.github.archipelagomw.network.server.RoomInfoPacket; +import io.github.archipelagomw.parts.DataPackage; +import io.github.archipelagomw.parts.Game; +import io.github.archipelagomw.parts.NetworkSlot; +import io.github.archipelagomw.parts.Version; +import org.apache.hc.core5.net.URIBuilder; + +import com.google.gson.Gson; + +import javax.net.SocketFactory; +import java.io.*; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +public abstract class Client { + + private final static Logger LOGGER = Logger.getLogger(Client.class.getName()); + + public static final Version protocolVersion = new Version(0, 6, 1); + private final static Gson gson = GsonUtils.getGson(); + + private static final String OS = System.getProperty("os.name").toLowerCase(); + + private static final Path cachePath; + private static final Path datapackageCachePath; + + static + { + String appData = System.getenv("LOCALAPPDATA"); + String userHome = System.getProperty("user.home"); + String xdg = System.getenv("XDG_CACHE_HOME"); + + if(OS.startsWith("windows")) + { + cachePath = Paths.get(appData, "Archipelago", "Cache"); + } + else if(OS.startsWith("Mac") || OS.startsWith("Darwin")) + { + cachePath = Paths.get(userHome, "Library", "Caches", "Archipelago"); + } + else if(xdg == null || xdg.isEmpty() ) + { + cachePath = Paths.get(userHome, ".cache", "Archipelago"); + } + else + { + cachePath = Paths.get(xdg, "Archipelago"); + } + datapackageCachePath = cachePath.resolve("datapackage"); + + } + + private static String uuid = null; + + private static Path dataPackageLocation; + + protected Map versions; + + protected List games; + + private int hintPoints; + + private WebSocket webSocket; + + private String password; + + private RoomInfoPacket roomInfo; + + private final DataPackage dataPackage = new DataPackage(); + + public static Client client; + + private final LocationManager locationManager; + private final ItemManager itemManager; + private final EventManager eventManager; + private final BouncedManager bouncedManager; + private final DeathLinkHandler deathLinkHandler; + + private int team; + private int slot; + private Map slotInfo; + private String name = "Name not set"; + private String game = "Game not set"; + private String alias; + private final Set tags = Collections.newSetFromMap(new ConcurrentHashMap<>()); + private int itemsHandlingFlags = 0b000; + + public Client() { + dataPackageLocation = datapackageCachePath; + eventManager = new EventManager(); + locationManager = new LocationManager(this); + itemManager = new ItemManager(this); + bouncedManager = new BouncedManager(); + deathLinkHandler = new DeathLinkHandler(this); + bouncedManager.addHandler(deathLinkHandler); + client = this; + } + + /** + * Sets the name of the game to send to Archipelago's servers + * @param game the name of your game. + */ + public void setGame(String game) { + this.game = game; + } + + /** + * overwrite, and set all tags sent to the Archipelago server. + * this will overwrite any previous tags that have been set. + * @param tags a Set of tags to send. + * @return Whether the send was successful or not + */ + public APResult setTags(Set tags) { + if (!this.tags.equals(tags)) { + this.tags.clear(); + this.tags.addAll(tags); + APResult ret = ensureConnectedAndAuth(); + if(ret.getCode() == APResult.ResultCode.SUCCESS) { + ConnectUpdatePacket packet = new ConnectUpdatePacket(); + packet.tags = this.tags; + webSocket.sendPacket(packet); + ret = APResult.success(); + } + return ret; + } + return APResult.success(); + } + + /** + * add a tag to your list, keeping all previous tags intact. + * @param tag String tag to be added. + * @return Whether the send was successful or not + */ + public APResult addTag(String tag) { + if(tags.add(tag)) { + APResult ret = ensureConnectedAndAuth(); + if(ret.getCode() == APResult.ResultCode.SUCCESS) { + ConnectUpdatePacket packet = new ConnectUpdatePacket(); + packet.tags = this.tags; + webSocket.sendPacket(packet); + ret = APResult.success(); + } + return ret; + } + return APResult.success(); + } + + /** + * removes supplied tag, if it exists. + * @param tag String tag to be removed. + * @return Whether the send was successful or not + */ + public APResult removeTag(String tag) { + if(tags.remove(tag)) { + APResult ret = ensureConnectedAndAuth(); + if(ret.getCode() == APResult.ResultCode.SUCCESS) { + ConnectUpdatePacket packet = new ConnectUpdatePacket(); + packet.tags = this.tags; + webSocket.sendPacket(packet); + ret = APResult.success(); + } + return ret; + } + return APResult.success(); + } + + + /** + * Gets the UUID of clients on this machine + * @return UUID of the client, this should theoretically never change. + */ + public static String getUUID() { + if(uuid == null) + { + synchronized (DataPackage.class) + { + if(uuid != null) { + return uuid; + } + String tmp = null; + File common = cachePath.resolve("common.json").toFile(); + JsonObject data = new JsonObject(); + if(common.exists()) + { + try(BufferedReader reader = Files.newBufferedReader(common.toPath(), StandardCharsets.UTF_8)) + { + data = gson.fromJson(reader, JsonObject.class); + } + catch(IOException ex) + { + LOGGER.log(Level.WARNING,"Failed to load common uuid", ex); + // We probably will fail to write + return uuid = UUID.randomUUID().toString(); + } + } + JsonElement uuidEle = data.get("uuid"); + if(uuidEle != null && !uuidEle.isJsonNull()) + { + tmp = uuidEle.getAsString(); + } + if(tmp != null) + { + return uuid = tmp; + } + + tmp = UUID.randomUUID().toString(); + data.addProperty("uuid", tmp); + try(BufferedWriter writer = Files.newBufferedWriter(common.toPath(),StandardCharsets.UTF_8)) + { + writer.write(gson.toJson(data)); + } + catch(IOException ex) + { + LOGGER.log(Level.WARNING,"Failed to save common uuid", ex); + } + return uuid = tmp; + } + } + return uuid; + } + + protected void loadDataPackage() { + synchronized (Client.class){ + File directoryPath = dataPackageLocation.toFile(); + + if(!directoryPath.exists()) + { + boolean success = directoryPath.mkdirs(); + if(success){ + LOGGER.info("DataPackage directory didn't exist. Starting from a new one."); + } else{ + LOGGER.severe("Failed to make directories for datapackage cache."); + } + return; + } + + //ensure the path to the cache exists + if(!directoryPath.isDirectory()) { + return; + } + //loop through all Archipelago cache folders to find valid data package files + Map localGamesList = new HashMap(); + + for(File gameDir : directoryPath.listFiles()){ + if(gameDir.isDirectory()){ + localGamesList.put(gameDir.getName(), gameDir); + } + } + + if(localGamesList.isEmpty()){ + LOGGER.info("Datapackage is empty"); + return; + } + + for(String gameName : games) { + String safeName = Utils.getFileSafeName(gameName); + File dir = localGamesList.get(safeName); + + if(null == dir){ + continue; + } + + //check all checksums + for(File version : dir.listFiles()){ + String versionStr = versions.get(gameName); + String versionFromFile = version.getName(); + int index = versionFromFile.lastIndexOf('.'); + if(index > 0) + { + versionFromFile = versionFromFile.substring(0, index); + } + + if(versionStr != null && versionStr.equals(versionFromFile)) { + try(BufferedReader reader = Files.newBufferedReader(version.toPath(), StandardCharsets.UTF_8)){ + Game game = gson.fromJson(reader, Game.class); + dataPackage.update(gameName, game); + LOGGER.info("Read datapackage for Game: ".concat(gameName).concat(" Checksum: ").concat(version.getName())); + } catch (IOException e){ + LOGGER.info("Failed to read a datapackage. Starting with a new one."); + } + } + } + } + } + } + + public void saveDataPackage() { + synchronized (Client.class){ + //Loop through games to ensure we have folders for each of them in the cache + for(String gameName : games){ + String safeName = Utils.getFileSafeName(gameName); + File gameFolder = dataPackageLocation.resolve(safeName).toFile(); + if(!gameFolder.exists()){ + //game folder not found. Make it + gameFolder.mkdirs(); + } + + //save the datapackage + String gameVersion = versions.get(gameName); + if(gameVersion == null) { + continue; + } + + //if key is for this game + File filePath = dataPackageLocation.resolve(safeName).resolve(gameVersion + ".json").toFile(); + + try (Writer writer = Files.newBufferedWriter(filePath.toPath(), StandardCharsets.UTF_8)){ + //if game is in list of games, save it + gson.toJson(dataPackage.getGame(gameName), writer); + LOGGER.info("Saving datapackage for Game: ".concat(gameName).concat(" Checksum: ").concat(gameVersion)); + } catch (IOException e) { + LOGGER.warning("unable to save DataPackage."); + } + + } + } + } + + /** + * Returns true only if connected to an Archipelago server. + * @return true if connected, otherwise false + */ + public boolean isConnected() { + return webSocket != null && webSocket.isOpen(); + } + + /** + * closes a connection to the Archipelago server if connected. + */ + public void close() { + if (webSocket != null) + webSocket.close(); + } + + /** + * sets a password to authenticate with to join a password protected room. + * @param password room password + */ + public void setPassword(String password) { + this.password = password; + } + + void setHintPoints(int hintPoints) { + this.hintPoints = hintPoints; + } + + /** + * sets the slot name to connect to an Archcipelago server with. + * @param name + */ + public void setName(String name) { + this.name = name; + } + + void setSlot(int slot) { + this.slot = slot; + } + + void setTeam(int team) { + this.team = team; + } + + void setSlotInfo(Map slotInfo) { + this.slotInfo = slotInfo; + } + + void setRoomInfo(RoomInfoPacket roomInfo) { + this.roomInfo = roomInfo; + } + + void updateDataPackage(DataPackage newData) { + dataPackage.update(newData); + } + + /** + * + * @return team ID + */ + public int getTeam() { + return team; + } + + /** + * + * @return Slot ID + */ + public int getSlot() { + return slot; + } + + /** + * fetches the + * @return Room info. + */ + public RoomInfoPacket getRoomInfo() { + return roomInfo; + } + + public Map getSlotInfo() {return slotInfo;} + + /** + * Works exactly like {@link #connect(URI, boolean)} with allowDowngrade set to true; + * @param address + * @throws URISyntaxException on malformed address + */ + public void connect(String address) throws URISyntaxException { + URIBuilder builder = new URIBuilder((!address.contains("//")) ? "//" + address : address); + if (builder.getPort() == -1) { //set default port if not included + builder.setPort(38281); + } + + if (webSocket != null && webSocket.isOpen()) { + LOGGER.fine("previous WebSocket is open, closing."); + webSocket.close(); + } + + if (builder.getScheme() == null) { + builder.setScheme("wss"); + connect(builder.build(), true); + return; + } + + connect(builder.build()); + } + + /** + * Works exactly like {@link #connect(URI, boolean)} but allowDowngrade is False + * @param address Address to connect to + */ + public void connect(URI address) { + connect(address, false); + } + + /** + * Equivalent to {@link #connect(URI, boolean, SocketFactory)} socketFactory being null. + */ + public void connect(URI address, boolean allowDowngrade) { + connect(address, allowDowngrade, null); + } + + /** + * Connects to an Archipelago server with previously provided info. + *
+ * supply the following info before calling this method + *
+ * game: {@link #setGame(String)}
+ * slot name: {@link #setName(String)}
+ *
+ * if no protocol wss:// or ws:// is given will attempt a ssl connection + * to the supplied address, if that fails it will then try a non-ssl connection, unless allowDowngrade is false.
+ *
+ * Do not prefix address with wss:// or ws://. let the user enter a protocol to use. + * by default ssl will be tried first, if that fails then non-ssl will be used. unless allowDowngrade is set to false. + * @param address address of the archipelago server. + * @param allowDowngrade if set to false will prevent auto downgrade of ssl connection. + */ + public void connect(URI address, boolean allowDowngrade, SocketFactory socketFactory) { + LOGGER.fine("attempting WebSocket connection to " + address.toString()); + webSocket = new WebSocket(address, this); + if(null != socketFactory) + { + webSocket.setSocketFactory(socketFactory); + } + locationManager.setAPWebSocket(webSocket); + itemManager.setAPWebSocket(webSocket); + webSocket.connect(allowDowngrade); + } + + + /** + * Sends a Chat message to all other connected Clients. + * @param message Message to send. + * @return Whether the send was successful or not + */ + public APResult sendChat(String message) { + APResult ret = ensureConnectedAndAuth(); + if(ret.getCode() == APResult.ResultCode.SUCCESS) + { + webSocket.sendChat(message); + ret = APResult.success(); + } + return ret; + } + + /** + * inform the Archipelago server that a location ID has been checked. + * @param locationID id of a location. + * @return Whether send was successful or not + */ + public APResult checkLocation(long locationID) { + return locationManager.checkLocation(locationID); + } + + /** + * inform the Archipelago server that a collection of location ID has been checked. + * @param locationIDs a collection of a locations. + * @return Whether send was successful or not + */ + public APResult checkLocations(Collection locationIDs) { + return locationManager.checkLocations(locationIDs); + } + + /** + * Ask the server for information about what is in locations. you will get a response in the {@link LocationInfoEvent} event. + * @param locationIDs List of location ID's to request info on. + * @return Whether the send was successful or not + */ + public APResult scoutLocations(ArrayList locationIDs) { + APResult ret = ensureConnectedAndAuth(); + if(ret.getCode() == APResult.ResultCode.SUCCESS ) + { + locationIDs.removeIf( location -> !dataPackage.getGame(game).locationNameToId.containsValue(location)); + webSocket.scoutLocation(locationIDs); + ret = APResult.success(); + } + return ret; + } + + /** + * + * Ask the server for information about what is in locations. you will get a response in the {@link LocationInfoEvent} event. + * @param locationIDs List of location ID's to request info on. + * @param createAsHint Whether to create hints for the provided locations + * @return Whether the send was successful or not + */ + public APResult scoutLocations(ArrayList locationIDs, CreateAsHint createAsHint) + { + APResult ret = ensureConnectedAndAuth(); + if(ret.getCode() == APResult.ResultCode.SUCCESS) + { + ArrayList sendMe = new ArrayList<>(); + locationIDs.stream().filter(location -> locationManager.getCheckedLocations().contains(location) || + locationManager.getMissingLocations().contains(location)) + .forEach(sendMe::add); + if(sendMe.isEmpty()) + { + LOGGER.fine("Locations provided for scout do not exist for this world"); + return APResult.error(); + } + webSocket.scoutLocations(sendMe, createAsHint); + ret = APResult.success(); + } + return ret; + } + + public abstract void onError(Exception ex); + + public abstract void onClose(String Reason, int attemptingReconnect); + + public DataPackage getDataPackage() { + return dataPackage; + } + + public String getMyName() { + return name; + } + + public String getPassword() { + return password; + } + + public int getHintPoints() { + return hintPoints; + } + + public String getGame() { + return game; + } + + public String getConnectedAddress() { + if (isConnected()) + return webSocket.getRemoteSocketAddress().getHostName()+":"+ webSocket.getRemoteSocketAddress().getPort(); + else + return ""; + } + + /** + * this should not need to be called externally but is left public just in case. + */ + public void reconnect() { + webSocket.reconnect(); + } + + /** + * gets the alias of this slot. + * @return Alias of the slot connected to. + */ + public String getAlias() { + return alias; + } + + /** + * sets an Alias for this slot on the Archipelago server. + * @param alias Name to set the alias to. + */ + void setAlias(String alias) { + this.alias = alias; + } + + public LocationManager getLocationManager() { + return locationManager; + } + + public ItemManager getItemManager() { + return itemManager; + } + + protected APResult ensureConnectedAndAuth() + { + if(!isConnected()) { + return APResult.disconnected(); + } + if(!webSocket.isAuthenticated()) + { + return APResult.unauthenticated(); + } + return APResult.success(); + } + /** + * Update the current game status. + * @see ClientStatus + * + * @param status a {@link ClientStatus} to send to the server. + * @return Whether the send was successful or not + */ + public APResult setGameState(ClientStatus status) { + APResult ret = ensureConnectedAndAuth(); + if(ret.getCode() == APResult.ResultCode.SUCCESS) { + webSocket.sendPacket(new StatusUpdatePacket(status)); + ret = APResult.success(); + } + return ret; + } + + /** + * manually trigger a resync to the Archipelago server. this should be done automatically if the library detects a desync. + * @return Whether the send was successful or not + */ + public APResult sync() { + APResult ret = ensureConnectedAndAuth(); + if(ret.getCode() == APResult.ResultCode.SUCCESS) { + webSocket.sendPacket(new SyncPacket()); + ret = APResult.success(); + } + return ret; + } + + /** + * sends a bouncepacket to the server, such as for deathlink + * @param bouncePacket The packet to send + * @return Whether the send was successful or not + */ + public APResult sendBounce(BouncePacket bouncePacket) { + APResult ret = ensureConnectedAndAuth(); + if(ret.getCode() == APResult.ResultCode.SUCCESS) { + webSocket.sendPacket(bouncePacket); + ret = APResult.success(); + } + return ret; + } + + /** + * disconnects from a connected Archipelago server. + */ + public void disconnect() { + webSocket.close(); + } + + /** + * @return set of tags currently in use. + */ + public Set getTags() { + return tags; + } + + /** + * fetch the itemflags that have been set, bitwise Or against {@link ItemsHandling} to read. + * @return items handling int. + */ + public int getItemsHandlingFlags() { + return itemsHandlingFlags; + } + + /** + * fetch the itemflags that have been set, bitwise Or against {@link ItemsHandling} to read. + */ + public void setItemsHandlingFlags(int itemsHandlingFlags) { + this.itemsHandlingFlags = itemsHandlingFlags; + } + + /** + * @return the event manager. + */ + public EventManager getEventManager() { + return eventManager; + } + + /** + * @return the bounced packet handler + */ + public BouncedManager getBouncedManager() + { + return bouncedManager; + } + + /** + * Uses DataStorage to save a value on the AP server. + * @return Whether the send was successful or not, and the requestId + */ + public APResult dataStorageSet(SetPacket setPacket) { + APResult ret = ensureConnectedAndAuth(); + if(ret.getCode() == APResult.ResultCode.SUCCESS) { + webSocket.sendPacket(setPacket); + ret = APResult.success(setPacket.getRequestID()); + } + return ret; + } + + /** + * Registers to receive updates of when a key in the Datastorage has been changed on the server. + * + * @param keys List of Keys to be notified of. + * @return Whether the send was successful or not + */ + public APResult dataStorageSetNotify(Collection keys) { + APResult ret = ensureConnectedAndAuth(); + if(ret.getCode() == APResult.ResultCode.SUCCESS) { + webSocket.sendPacket(new SetNotifyPacket(keys)); + ret = APResult.success(); + } + return ret; + } + + /** + * Uses DataStorage to reterieve a value from the server will get value back though a + * {@link RetrievedEvent RetrievedEvent}.
+ * see following table for list of reserved keys. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
NameTypeNotes
hints_{team}_{slot} list[Hint] All Hints belonging to the requested Player.
slot_data_{slot} dict[str, any] slot_data belonging to the requested slot.
item_name_groups_{game_name} dict[str, list[str]] item_name_groups belonging to the requested game.
location_name_groups_{game_name} dict[str, list[str]] location_name_groups belonging to the requested game.
client_status_{team}_{slot} ClientStatus The current game status of the requested player.
race_mode int 0 if race mode is disabled, and 1 if it's enabled.
+ * + * @param keys a list of keys to retrieve values for + * @return Whether the send was successful or not, and the requestId + */ + public APResult dataStorageGet(Collection keys) { + GetPacket getPacket = new GetPacket(keys); + APResult ret = ensureConnectedAndAuth(); + if(ret.getCode() == APResult.ResultCode.SUCCESS) { + webSocket.sendPacket(getPacket); + ret = APResult.success(getPacket.getRequestID()); + } + return ret; + } + + /** + * Helper for sending a death link bounce packet. You can send these without enabling death link first, but it is frowned upon. + * @param source A String that is the name of the player sending the death link (does not have to be slot name) + * @param cause A String that is the cause of this death. may be empty. + * @return Whether the send was successful or not + */ + public APResult sendDeathlink(String source, String cause) + { + APResult ret = ensureConnectedAndAuth(); + if(ret.getCode() == APResult.ResultCode.SUCCESS) { + deathLinkHandler.sendDeathLink(source, cause); + ret = APResult.success(); + } + return ret; + } + + /** + * Enable or disable receiving death links. + * @param enabled set to TRUE to enable death links, FALSE to disable. + * @return Whether the send was successful or not + */ + public APResult setDeathLinkEnabled(boolean enabled) { + if(enabled) + return addTag(DeathLinkHandler.DEATHLINK_TAG); + else + return removeTag(DeathLinkHandler.DEATHLINK_TAG); + } + + /** + * Enables sending multiple packets at once. + * @param packets The packets to send + * @return Whether the send was successful + */ + public APResult sendPackets(List packets) + { + APResult ret = ensureConnectedAndAuth(); + if(ret.getCode() == APResult.ResultCode.SUCCESS) { + webSocket.sendManyPackets(packets); + ret = APResult.success(); + } + return ret; + } + + /** + * Creates hints for the provided locations + * @param locations The locations to hint for. + * @param player The id of the player whose locations are being hinted for + * @param status Sets the status of the hint to this status; server rejects HINT_FOUND + * @return Whether the send was successful + */ + public APResult createHints(List locations, int player, HintStatus status) + { + APResult ret = ensureConnectedAndAuth(); + if(ret.getCode() == APResult.ResultCode.SUCCESS) + { + ArrayList sendMe = new ArrayList<>(); + if(player == this.slot) { + locations.stream().filter(location -> locationManager.getMissingLocations().contains(location)) + .forEach(sendMe::add); + } + if(locations.isEmpty()) + { + LOGGER.fine("Locations provided for create hint all either checked or do not exist"); + return APResult.error(); + } + CreateHintPacket packet = new CreateHintPacket(sendMe, player, status); + webSocket.sendPacket(packet); + ret = APResult.success(); + } + return ret; + } + + /** + * Creates hints for the provided locations + * @param locations The locations to hint for. + * @param player The id of the player whose locations are being hinted for + * @return Whether the send was successful + */ + public APResult createHints(List locations, int player) + { + return createHints(locations, player, HintStatus.HINT_UNSPECIFIED); + } + + /** + * Creates hints for the provided locations with this client's slot + * @param locations The locations to hint for. + * @return Whether the send was successful + */ + public APResult createHints(List locations) + { + return createHints(locations, client.slot, HintStatus.HINT_UNSPECIFIED); + } +} diff --git a/src/main/java/dev/koifysh/archipelago/ClientStatus.java b/src/main/java/io/github/archipelagomw/ClientStatus.java similarity index 73% rename from src/main/java/dev/koifysh/archipelago/ClientStatus.java rename to src/main/java/io/github/archipelagomw/ClientStatus.java index 1dcc8d6..4e6832d 100644 --- a/src/main/java/dev/koifysh/archipelago/ClientStatus.java +++ b/src/main/java/io/github/archipelagomw/ClientStatus.java @@ -1,6 +1,6 @@ -package dev.koifysh.archipelago; +package io.github.archipelagomw; -import com.google.gson.annotations.SerializedName; +import io.github.archipelagomw.utils.IntEnum; /** * A Status to send to the server.
@@ -9,15 +9,11 @@ * {@link #CLIENT_PLAYING} - Player has started playing.
* {@link #CLIENT_GOAL} - Player has finished their game. This will trigger an auto-release depending on server settings. */ -public enum ClientStatus { +public enum ClientStatus implements IntEnum { - @SerializedName("0") CLIENT_UNKNOWN(0), - @SerializedName("10") CLIENT_READY(10), - @SerializedName("20") CLIENT_PLAYING(20), - @SerializedName("30") CLIENT_GOAL(30); private final int value; @@ -25,6 +21,7 @@ public enum ClientStatus { this.value = value; } + @Override public int getValue() { return value; } diff --git a/src/main/java/dev/koifysh/archipelago/EventManager.java b/src/main/java/io/github/archipelagomw/EventManager.java similarity index 73% rename from src/main/java/dev/koifysh/archipelago/EventManager.java rename to src/main/java/io/github/archipelagomw/EventManager.java index 6fbd71c..93336fb 100644 --- a/src/main/java/dev/koifysh/archipelago/EventManager.java +++ b/src/main/java/io/github/archipelagomw/EventManager.java @@ -1,13 +1,13 @@ -package dev.koifysh.archipelago; +package io.github.archipelagomw; -import dev.koifysh.archipelago.events.ArchipelagoEventListener; -import dev.koifysh.archipelago.events.Event; +import io.github.archipelagomw.events.ArchipelagoEventListener; +import io.github.archipelagomw.events.Event; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; /** * Manages registering and calling events @@ -15,7 +15,7 @@ */ public class EventManager { - private final Map registeredListeners = new HashMap<>(); + private final Map registeredListeners = new ConcurrentHashMap<>(); /** * Use to register for Events that come from the Archipelago server. @@ -24,8 +24,9 @@ public class EventManager { * @param listener the object containing a listener method. */ public void registerListener(Object listener) { - for (Method method : listener.getClass().getMethods()) { - if (isEventListenerMethod(listener, method)) continue; + Method[] methods = listener instanceof Class ? ((Class)listener).getMethods() : listener.getClass().getMethods(); + for (Method method : methods) { + if (isNotEventListenerMethod(listener, method)) continue; registeredListeners.put(method, listener); } @@ -38,14 +39,15 @@ public void registerListener(Object listener) { * @param listener the object containing a listener method. */ public void unRegisterListener(Object listener) { - for (Method method : listener.getClass().getMethods()) { - if (isEventListenerMethod(listener, method)) continue; + Method[] methods = listener instanceof Class ? ((Class)listener).getMethods() : listener.getClass().getMethods(); + for (Method method : methods) { + if (isNotEventListenerMethod(listener, method)) continue; registeredListeners.remove(method, listener); } } - private boolean isEventListenerMethod(Object listener, Method method) { + private boolean isNotEventListenerMethod(Object listener, Method method) { if(listener instanceof Class) if (!Modifier.isStatic(method.getModifiers())) return true; diff --git a/src/main/java/io/github/archipelagomw/GsonUtils.java b/src/main/java/io/github/archipelagomw/GsonUtils.java new file mode 100644 index 0000000..26526ad --- /dev/null +++ b/src/main/java/io/github/archipelagomw/GsonUtils.java @@ -0,0 +1,17 @@ +package io.github.archipelagomw; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import io.github.archipelagomw.utils.IntEnumAdapterFactory; + +// Default visibility until a good way of having Gson shared is decided. +class GsonUtils { + + // TODO: for some reason, a lot of instances of this class get created everywhere, and it's unnecessary + private static final Gson gson = new GsonBuilder().registerTypeAdapterFactory(new IntEnumAdapterFactory()).create(); + + static Gson getGson() + { + return gson; + } +} diff --git a/src/main/java/io/github/archipelagomw/ItemManager.java b/src/main/java/io/github/archipelagomw/ItemManager.java new file mode 100644 index 0000000..5c9ec45 --- /dev/null +++ b/src/main/java/io/github/archipelagomw/ItemManager.java @@ -0,0 +1,82 @@ +package io.github.archipelagomw; + +import io.github.archipelagomw.events.ReceiveItemEvent; +import io.github.archipelagomw.network.client.SyncPacket; +import io.github.archipelagomw.parts.DataPackage; +import io.github.archipelagomw.parts.NetworkItem; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +public class ItemManager { + + + private final Client client; + private WebSocket webSocket; + + private List receivedItems = new ArrayList<>(); + + private final AtomicInteger index = new AtomicInteger(); + + public ItemManager(Client client) { + this.client = client; + } + + public void receiveItems(List ids, int index) { + if (index == 0) { + receivedItems = new ArrayList<>(); + } + if (receivedItems.size() == index) { + synchronized (this) { + receivedItems.addAll(ids); + } + DataPackage dp = client.getDataPackage(); + int myTeam = client.getTeam(); + for (int i = this.index.get(); i < receivedItems.size(); i++) { + NetworkItem item = receivedItems.get(i); + item.itemName = dp.getItem(item.itemID, client.getGame()); + item.locationName = dp.getLocation(item.locationID, client.getSlotInfo().get(item.playerID).game); + item.playerName = client.getRoomInfo().getPlayer(myTeam,item.playerID).alias; + client.getEventManager().callEvent(new ReceiveItemEvent(item, i+1)); + } + + this.index.set(receivedItems.size()); + } + else { + if(webSocket != null) { + webSocket.sendPacket(new SyncPacket()); + client.getLocationManager().resendAllCheckedLocations(); + } + } + } + + public void writeFromSave(List receivedItems, int index) { + this.receivedItems = new ArrayList<>(receivedItems); + this.index.set(index); + } + + void setAPWebSocket(WebSocket webSocket) { + this.webSocket = webSocket; + } + + public int getIndex() { + return index.get(); + } + + public List getReceivedItems() { + synchronized (this) { + return new ArrayList<>(receivedItems); + } + } + + public List getReceivedItemIDs() { + List ids = new ArrayList<>(); + synchronized (this) { + for (NetworkItem receivedItem : receivedItems) { + ids.add(receivedItem.itemID); + } + } + return ids; + } +} diff --git a/src/main/java/io/github/archipelagomw/LocationManager.java b/src/main/java/io/github/archipelagomw/LocationManager.java new file mode 100644 index 0000000..f74c459 --- /dev/null +++ b/src/main/java/io/github/archipelagomw/LocationManager.java @@ -0,0 +1,114 @@ +package io.github.archipelagomw; + +import io.github.archipelagomw.network.client.LocationChecks; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +public class LocationManager { + + // TODO: why is this field unused? + private final Client client; + private WebSocket webSocket; + + private final Set checkedLocations = Collections.newSetFromMap(new ConcurrentHashMap<>()); + + private final Set missingLocations = Collections.newSetFromMap(new ConcurrentHashMap<>()); + + public LocationManager(Client client) { + this.client = client; + } + + public APResult checkLocation(long id) { + return checkLocations(new ArrayList(1) {{add(id);}}); + } + + public APResult checkLocations(Collection ids) { + ids.removeIf( location -> !missingLocations.contains(location)); + checkedLocations.addAll(ids); + missingLocations.removeAll(ids); + LocationChecks packet = new LocationChecks(); + packet.locations.addAll(ids); + APResult ret = client.ensureConnectedAndAuth(); + if(ret.getCode() == APResult.ResultCode.SUCCESS) + { + webSocket.sendPacket(packet); + ret = APResult.success(); + } + return ret; + } + + public APResult sendIfChecked(Set missingChecks) { + LocationChecks packet = new LocationChecks(); + packet.locations = new HashSet<>(); + for (Long missingCheck : missingChecks) { + if(checkedLocations.contains(missingCheck)) { + packet.locations.add(missingCheck); + } + } + if(packet.locations.isEmpty()) + { + return APResult.success(); + } + APResult ret = client.ensureConnectedAndAuth(); + if(ret.getCode() == APResult.ResultCode.SUCCESS) + { + webSocket.sendPacket(packet); + ret = APResult.success(); + } + return ret; + } + + public APResult resendAllCheckedLocations() { + LocationChecks packet = new LocationChecks(); + packet.locations = checkedLocations; + APResult ret = client.ensureConnectedAndAuth(); + if(ret.getCode() == APResult.ResultCode.SUCCESS) + { + webSocket.sendPacket(packet); + ret = APResult.success(); + } + return ret; + } + + void setAPWebSocket(WebSocket webSocket) { + this.webSocket = webSocket; + } + + public Set getCheckedLocations() { + return checkedLocations; + } + + public Set getMissingLocations() { + return missingLocations; + } + + public void addCheckedLocations(Set newLocations) { + this.checkedLocations.addAll(newLocations); + this.missingLocations.removeAll(newLocations); + } + + public void setMissingLocations(Set missingLocations) { + this.missingLocations.clear(); + this.missingLocations.addAll(missingLocations); + } + + /** + Helper to get the location ID from an inputted location name as a String. Only use this + if you know what you're dealing with, and can accept that getting this from the datapackage + is not 100% reliable. + + @param locationName The name of the location you wish to look up. + @return The ID of the location that you have looked up from the datapackage. + */ + public Optional getLocationNameFromID(String locationName){ + return Optional.ofNullable( + this.client + .getDataPackage() + .getGame(this.client.getGame()) + ) + .map(game -> game.locationNameToId + .get(locationName) + ); + } +} diff --git a/src/main/java/dev/koifysh/archipelago/Print/APPrint.java b/src/main/java/io/github/archipelagomw/Print/APPrint.java similarity index 90% rename from src/main/java/dev/koifysh/archipelago/Print/APPrint.java rename to src/main/java/io/github/archipelagomw/Print/APPrint.java index ceca8f6..3364201 100644 --- a/src/main/java/dev/koifysh/archipelago/Print/APPrint.java +++ b/src/main/java/io/github/archipelagomw/Print/APPrint.java @@ -1,7 +1,7 @@ -package dev.koifysh.archipelago.Print; +package io.github.archipelagomw.Print; import com.google.gson.annotations.SerializedName; -import dev.koifysh.archipelago.parts.NetworkItem; +import io.github.archipelagomw.parts.NetworkItem; public class APPrint { diff --git a/src/main/java/dev/koifysh/archipelago/Print/APPrintColor.java b/src/main/java/io/github/archipelagomw/Print/APPrintColor.java similarity index 90% rename from src/main/java/dev/koifysh/archipelago/Print/APPrintColor.java rename to src/main/java/io/github/archipelagomw/Print/APPrintColor.java index 35b0e9b..abd4578 100644 --- a/src/main/java/dev/koifysh/archipelago/Print/APPrintColor.java +++ b/src/main/java/io/github/archipelagomw/Print/APPrintColor.java @@ -1,4 +1,4 @@ -package dev.koifysh.archipelago.Print; +package io.github.archipelagomw.Print; import java.awt.*; @@ -23,6 +23,7 @@ public enum APPrintColor { purple_bg(Color.decode("#800080")), cyan_bg(Color.cyan), white_bg(Color.white), + magenta_bg(Color.magenta), gold(Color.decode("#FFD700")); public final Color color; diff --git a/src/main/java/dev/koifysh/archipelago/Print/APPrintJsonType.java b/src/main/java/io/github/archipelagomw/Print/APPrintJsonType.java similarity index 97% rename from src/main/java/dev/koifysh/archipelago/Print/APPrintJsonType.java rename to src/main/java/io/github/archipelagomw/Print/APPrintJsonType.java index ce1458c..4c6e6d4 100644 --- a/src/main/java/dev/koifysh/archipelago/Print/APPrintJsonType.java +++ b/src/main/java/io/github/archipelagomw/Print/APPrintJsonType.java @@ -1,4 +1,4 @@ -package dev.koifysh.archipelago.Print; +package io.github.archipelagomw.Print; import com.google.gson.annotations.SerializedName; diff --git a/src/main/java/dev/koifysh/archipelago/Print/APPrintPart.java b/src/main/java/io/github/archipelagomw/Print/APPrintPart.java similarity index 84% rename from src/main/java/dev/koifysh/archipelago/Print/APPrintPart.java rename to src/main/java/io/github/archipelagomw/Print/APPrintPart.java index a65dfef..d124eb4 100644 --- a/src/main/java/dev/koifysh/archipelago/Print/APPrintPart.java +++ b/src/main/java/io/github/archipelagomw/Print/APPrintPart.java @@ -1,4 +1,4 @@ -package dev.koifysh.archipelago.Print; +package io.github.archipelagomw.Print; public class APPrintPart { public APPrintType type = APPrintType.text; diff --git a/src/main/java/dev/koifysh/archipelago/Print/APPrintType.java b/src/main/java/io/github/archipelagomw/Print/APPrintType.java similarity index 92% rename from src/main/java/dev/koifysh/archipelago/Print/APPrintType.java rename to src/main/java/io/github/archipelagomw/Print/APPrintType.java index 0a1f680..80d3132 100644 --- a/src/main/java/dev/koifysh/archipelago/Print/APPrintType.java +++ b/src/main/java/io/github/archipelagomw/Print/APPrintType.java @@ -1,4 +1,4 @@ -package dev.koifysh.archipelago.Print; +package io.github.archipelagomw.Print; import com.google.gson.annotations.SerializedName; diff --git a/src/main/java/io/github/archipelagomw/Utils.java b/src/main/java/io/github/archipelagomw/Utils.java new file mode 100644 index 0000000..80db398 --- /dev/null +++ b/src/main/java/io/github/archipelagomw/Utils.java @@ -0,0 +1,35 @@ +package io.github.archipelagomw; + +public class Utils { + + public static String getFileSafeName(String text) + { + if(text == null) + { + return null; + } + StringBuilder sb = new StringBuilder(); + + for(int i = 0; i < text.length(); i++) + { + char c = text.charAt(i); + switch(c) + { + case '<': + case '>': + case ':': + case '"': + case '/': + case '\\': + case '|': + case '?': + case '*': + continue; + default: + sb.append(c); + + } + } + return sb.toString(); + } +} diff --git a/src/main/java/dev/koifysh/archipelago/WebSocket.java b/src/main/java/io/github/archipelagomw/WebSocket.java similarity index 90% rename from src/main/java/dev/koifysh/archipelago/WebSocket.java rename to src/main/java/io/github/archipelagomw/WebSocket.java index e548b79..1b361e3 100644 --- a/src/main/java/dev/koifysh/archipelago/WebSocket.java +++ b/src/main/java/io/github/archipelagomw/WebSocket.java @@ -1,27 +1,20 @@ -package dev.koifysh.archipelago; - -import com.google.gson.Gson; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonParser; -import dev.koifysh.archipelago.Print.APPrint; -import dev.koifysh.archipelago.Print.APPrintJsonType; -import dev.koifysh.archipelago.Print.APPrintPart; -import dev.koifysh.archipelago.Print.APPrintType; -import dev.koifysh.archipelago.flags.NetworkPlayer; -import dev.koifysh.archipelago.helper.DeathLink; -import dev.koifysh.archipelago.network.APPacket; -import dev.koifysh.archipelago.network.ConnectionResult; -import dev.koifysh.archipelago.network.client.ConnectPacket; -import dev.koifysh.archipelago.network.client.GetDataPackagePacket; -import dev.koifysh.archipelago.network.client.LocationScouts; -import dev.koifysh.archipelago.network.client.SayPacket; -import dev.koifysh.archipelago.parts.DataPackage; -import dev.koifysh.archipelago.parts.NetworkItem; -import dev.koifysh.archipelago.events.*; -import dev.koifysh.archipelago.network.server.*; - -import dev.koifysh.archipelago.parts.NetworkSlot; +package io.github.archipelagomw; + +import com.google.gson.*; +import io.github.archipelagomw.Print.APPrint; +import io.github.archipelagomw.Print.APPrintJsonType; +import io.github.archipelagomw.Print.APPrintPart; +import io.github.archipelagomw.Print.APPrintType; +import io.github.archipelagomw.events.*; +import io.github.archipelagomw.flags.NetworkPlayer; +import io.github.archipelagomw.network.APPacket; +import io.github.archipelagomw.network.ConnectionResult; +import io.github.archipelagomw.network.client.*; +import io.github.archipelagomw.network.server.*; +import io.github.archipelagomw.parts.DataPackage; +import io.github.archipelagomw.parts.NetworkItem; + +import io.github.archipelagomw.parts.NetworkSlot; import org.apache.hc.core5.net.URIBuilder; import org.java_websocket.client.WebSocketClient; import org.java_websocket.drafts.Draft; @@ -41,7 +34,8 @@ class WebSocket extends WebSocketClient { private final Client client; - private final Gson gson = new Gson(); + // TODO: why do we have multiple of this class floating around? + private final Gson gson = GsonUtils.getGson(); private boolean authenticated = false; @@ -101,7 +95,7 @@ public void onMessage(String message) { connectPacket.version = Client.protocolVersion; connectPacket.name = client.getMyName(); connectPacket.password = (client.getPassword() == null) ? "" : client.getPassword(); - connectPacket.uuid = client.getUUID(); + connectPacket.uuid = Client.getUUID(); connectPacket.game = client.getGame(); connectPacket.tags = client.getTags(); connectPacket.itemsHandling = client.getItemsHandlingFlags(); @@ -125,7 +119,7 @@ public void onMessage(String message) { teams = teamsOptional.getAsInt() + 1; } for (int i = 0; i < teams; i++) { - client.getRoomInfo().networkPlayers.add( new dev.koifysh.archipelago.parts.NetworkPlayer(i, 0, "Archipelago")); + client.getRoomInfo().networkPlayers.add( new io.github.archipelagomw.parts.NetworkPlayer(i, 0, "Archipelago")); } client.setAlias(client.getRoomInfo().getPlayer(connectedPacket.team, connectedPacket.slot).alias); @@ -157,7 +151,6 @@ public void onMessage(String message) { case DataPackage: JsonElement data = packet.getAsJsonObject().get("data"); DataPackage dataPackage = gson.fromJson(data, DataPackage.class); - dataPackage.uuid = client.getUUID(); client.updateDataPackage(dataPackage); client.saveDataPackage(); break; @@ -171,7 +164,7 @@ public void onMessage(String message) { if (part.type == APPrintType.playerID) { int playerID = Integer.parseInt(part.text); - dev.koifysh.archipelago.parts.NetworkPlayer player = client.getRoomInfo().getPlayer(client.getTeam(), playerID); + io.github.archipelagomw.parts.NetworkPlayer player = client.getRoomInfo().getPlayer(client.getTeam(), playerID); part.text = player.alias; } else if (part.type == APPrintType.itemID) { @@ -204,10 +197,10 @@ else if (part.type == APPrintType.locationID) { break; case Bounced: BouncedPacket bounced = gson.fromJson(packet, BouncedPacket.class); - if (bounced.tags.contains("DeathLink")) - DeathLink.receiveDeathLink(bounced); - else + if(!client.getBouncedManager().handle(bounced)) + { client.getEventManager().callEvent(new BouncedEvent(bounced.games, bounced.tags, bounced.slots, bounced.data)); + } break; case LocationInfo: LocationInfoPacket locations = gson.fromJson(packet, LocationInfoPacket.class); @@ -234,8 +227,7 @@ else if (part.type == APPrintType.locationID) { } } } catch (Exception e) { - LOGGER.warning("Error proccessing incoming packet: " + e.getMessage()); - //e.printStackTrace(); + LOGGER.log(Level.WARNING, "Error processing incoming packet: ", e); } } @@ -281,7 +273,11 @@ public void sendPacket(APPacket packet) { sendManyPackets(new APPacket[]{packet}); } - private void sendManyPackets(APPacket[] packet) { + void sendManyPackets(APPacket[] packet) { + sendManyPackets(Arrays.asList(packet)); + } + + void sendManyPackets(List packet) { if (!isOpen()) return; String json = gson.toJson(packet); @@ -368,4 +364,10 @@ public void scoutLocation(ArrayList locationIDs) { LocationScouts packet = new LocationScouts(locationIDs); sendPacket(packet); } + + public void scoutLocations(ArrayList locationIDs, CreateAsHint createAsHint) + { + LocationScouts packet = new LocationScouts(locationIDs, createAsHint); + sendPacket(packet); + } } diff --git a/src/main/java/io/github/archipelagomw/bounce/BouncedManager.java b/src/main/java/io/github/archipelagomw/bounce/BouncedManager.java new file mode 100644 index 0000000..e6bab4a --- /dev/null +++ b/src/main/java/io/github/archipelagomw/bounce/BouncedManager.java @@ -0,0 +1,62 @@ +package io.github.archipelagomw.bounce; + +import io.github.archipelagomw.events.BouncedEvent; +import io.github.archipelagomw.network.server.BouncedPacket; + +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A handler for managing protocol specific bounce packets. If a handler registered with the BouncedManager + * can handle a packet, then the BouncedPacket does not get propagated as + * a {@link BouncedEvent} + * + * This class is intended to help create protocols over the Bounce Packet. + */ +public class BouncedManager { + + private final static Logger LOGGER = Logger.getLogger(BouncedManager.class.getCanonicalName()); + + // Assuming the array doesn't change that often, copy on write should be ok + private final List handlers = new CopyOnWriteArrayList<>(); + + /** + * Adds a handler to intercept bounce packets with + * @param handler The handler to register + */ + public void addHandler(BouncedPacketHandler handler) + { + handlers.add(handler); + } + + /** + * Removes a handler from intercepting packets with. + * @param handler the handler to remove + */ + public void removeHandler(BouncedPacketHandler handler) + { + handlers.remove(handler); + } + + public boolean handle(BouncedPacket packet) + { + for(BouncedPacketHandler handler : handlers) + { + try { + if (handler.canHandle(packet)) { + handler.handle(packet); + return true; + } + } + catch(RuntimeException ex) + { + LOGGER.log(Level.WARNING, "Error while handling bounce packet", ex); + // Well we were supposed to handle it... + return true; + } + } + return false; + } +} diff --git a/src/main/java/io/github/archipelagomw/bounce/BouncedPacketHandler.java b/src/main/java/io/github/archipelagomw/bounce/BouncedPacketHandler.java new file mode 100644 index 0000000..d361038 --- /dev/null +++ b/src/main/java/io/github/archipelagomw/bounce/BouncedPacketHandler.java @@ -0,0 +1,26 @@ +package io.github.archipelagomw.bounce; + +import io.github.archipelagomw.events.BouncedEvent; +import io.github.archipelagomw.network.server.BouncedPacket; + +/** + * Used to facilitate protocols over bounce packets. Clients should use this when they want to implement + * protocol specific bounce packets, such as deathlink, ringlink, and traplink. + */ +public interface BouncedPacketHandler { + + /** + * Whether this BouncedPacketHandler can handle the provided packet. If it can, no other + * packet handler will be called, and the {@link BouncedEvent} will not + * be emitted. + * @param packet The packet to check + * @return true if this handler should process the packet + */ + boolean canHandle(BouncedPacket packet); + + /** + * Called after {@link #canHandle}. + * @param packet The BouncedPacket. + */ + void handle(BouncedPacket packet); +} diff --git a/src/main/java/io/github/archipelagomw/bounce/DeathLinkHandler.java b/src/main/java/io/github/archipelagomw/bounce/DeathLinkHandler.java new file mode 100644 index 0000000..6d62bde --- /dev/null +++ b/src/main/java/io/github/archipelagomw/bounce/DeathLinkHandler.java @@ -0,0 +1,59 @@ +package io.github.archipelagomw.bounce; + +import io.github.archipelagomw.Client; +import io.github.archipelagomw.events.DeathLinkEvent; +import io.github.archipelagomw.network.client.BouncePacket; +import io.github.archipelagomw.network.server.BouncedPacket; +import io.github.archipelagomw.utils.AtomicDouble; + +import java.util.HashMap; +import java.util.Map; + +public class DeathLinkHandler implements BouncedPacketHandler { + public static final String DEATHLINK_TAG = "DeathLink"; + + private final Client client; + + private final AtomicDouble lastDeath = new AtomicDouble(0d); + + public DeathLinkHandler(Client client) { + this.client = client; + } + + @Override + public boolean canHandle(BouncedPacket packet) { + return packet.tags.contains(DEATHLINK_TAG); + } + + @Override + public void handle(BouncedPacket packet) { + Map data = packet.data; + DeathLinkEvent event = new DeathLinkEvent((String) data.get("source"), + (String) data.get("cause"), + (Double) data.getOrDefault("time", 0d)); + double recentDeath = lastDeath.getAndUpdate(d -> Math.max(d, event.time)); + if(Math.abs(recentDeath - event.time) <= 1e-6) + { + // We already died, go away! + return; + } + client.getEventManager().callEvent(event); + } + + public void sendDeathLink(String source, String cause) + { + double time = (double)System.currentTimeMillis() / 1000D; + lastDeath.set(time); + + BouncePacket deathLinkPacket = new BouncePacket(); + deathLinkPacket.tags = new String[]{DEATHLINK_TAG}; + + HashMap data = new HashMap<>(); + data.put("cause",cause); + data.put("time", time); + data.put("source",source); + deathLinkPacket.setData(data); + + client.sendBounce(deathLinkPacket); + } +} diff --git a/src/main/java/dev/koifysh/archipelago/events/ArchipelagoEventListener.java b/src/main/java/io/github/archipelagomw/events/ArchipelagoEventListener.java similarity index 86% rename from src/main/java/dev/koifysh/archipelago/events/ArchipelagoEventListener.java rename to src/main/java/io/github/archipelagomw/events/ArchipelagoEventListener.java index ae75a40..bd38c77 100644 --- a/src/main/java/dev/koifysh/archipelago/events/ArchipelagoEventListener.java +++ b/src/main/java/io/github/archipelagomw/events/ArchipelagoEventListener.java @@ -1,4 +1,4 @@ -package dev.koifysh.archipelago.events; +package io.github.archipelagomw.events; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; diff --git a/src/main/java/dev/koifysh/archipelago/events/BouncedEvent.java b/src/main/java/io/github/archipelagomw/events/BouncedEvent.java similarity index 96% rename from src/main/java/dev/koifysh/archipelago/events/BouncedEvent.java rename to src/main/java/io/github/archipelagomw/events/BouncedEvent.java index 82754bf..bdf55c0 100644 --- a/src/main/java/dev/koifysh/archipelago/events/BouncedEvent.java +++ b/src/main/java/io/github/archipelagomw/events/BouncedEvent.java @@ -1,4 +1,4 @@ -package dev.koifysh.archipelago.events; +package io.github.archipelagomw.events; import com.google.gson.annotations.SerializedName; diff --git a/src/main/java/dev/koifysh/archipelago/events/CheckedLocationsEvent.java b/src/main/java/io/github/archipelagomw/events/CheckedLocationsEvent.java similarity index 86% rename from src/main/java/dev/koifysh/archipelago/events/CheckedLocationsEvent.java rename to src/main/java/io/github/archipelagomw/events/CheckedLocationsEvent.java index 1a657d3..8a08ea0 100644 --- a/src/main/java/dev/koifysh/archipelago/events/CheckedLocationsEvent.java +++ b/src/main/java/io/github/archipelagomw/events/CheckedLocationsEvent.java @@ -1,4 +1,4 @@ -package dev.koifysh.archipelago.events; +package io.github.archipelagomw.events; import java.util.ArrayList; diff --git a/src/main/java/dev/koifysh/archipelago/events/ConnectionAttemptEvent.java b/src/main/java/io/github/archipelagomw/events/ConnectionAttemptEvent.java similarity index 96% rename from src/main/java/dev/koifysh/archipelago/events/ConnectionAttemptEvent.java rename to src/main/java/io/github/archipelagomw/events/ConnectionAttemptEvent.java index 30d750e..3ef2398 100644 --- a/src/main/java/dev/koifysh/archipelago/events/ConnectionAttemptEvent.java +++ b/src/main/java/io/github/archipelagomw/events/ConnectionAttemptEvent.java @@ -1,4 +1,4 @@ -package dev.koifysh.archipelago.events; +package io.github.archipelagomw.events; import com.google.gson.Gson; import com.google.gson.JsonElement; diff --git a/src/main/java/dev/koifysh/archipelago/events/ConnectionResultEvent.java b/src/main/java/io/github/archipelagomw/events/ConnectionResultEvent.java similarity index 92% rename from src/main/java/dev/koifysh/archipelago/events/ConnectionResultEvent.java rename to src/main/java/io/github/archipelagomw/events/ConnectionResultEvent.java index 429b3e4..a0cf404 100644 --- a/src/main/java/dev/koifysh/archipelago/events/ConnectionResultEvent.java +++ b/src/main/java/io/github/archipelagomw/events/ConnectionResultEvent.java @@ -1,9 +1,9 @@ -package dev.koifysh.archipelago.events; +package io.github.archipelagomw.events; import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.gson.internal.Primitives; -import dev.koifysh.archipelago.network.ConnectionResult; +import io.github.archipelagomw.network.ConnectionResult; public class ConnectionResultEvent implements Event { diff --git a/src/main/java/dev/koifysh/archipelago/events/DeathLinkEvent.java b/src/main/java/io/github/archipelagomw/events/DeathLinkEvent.java similarity index 69% rename from src/main/java/dev/koifysh/archipelago/events/DeathLinkEvent.java rename to src/main/java/io/github/archipelagomw/events/DeathLinkEvent.java index 367f736..4057ea6 100644 --- a/src/main/java/dev/koifysh/archipelago/events/DeathLinkEvent.java +++ b/src/main/java/io/github/archipelagomw/events/DeathLinkEvent.java @@ -1,7 +1,7 @@ -package dev.koifysh.archipelago.events; +package io.github.archipelagomw.events; /** - * event that is fired whenever you receive a death link from another player. must first enable death links via {@link dev.koifysh.archipelago.helper.DeathLink} + * event that is fired whenever you receive a death link from another player. must first enable death links via {@link io.github.archipelagomw.Client#setDeathLinkEnabled(boolean)} */ public class DeathLinkEvent implements Event { diff --git a/src/main/java/io/github/archipelagomw/events/Event.java b/src/main/java/io/github/archipelagomw/events/Event.java new file mode 100644 index 0000000..068454e --- /dev/null +++ b/src/main/java/io/github/archipelagomw/events/Event.java @@ -0,0 +1,4 @@ +package io.github.archipelagomw.events; + +public interface Event { +} diff --git a/src/main/java/dev/koifysh/archipelago/events/InvalidPacketEvent.java b/src/main/java/io/github/archipelagomw/events/InvalidPacketEvent.java similarity index 94% rename from src/main/java/dev/koifysh/archipelago/events/InvalidPacketEvent.java rename to src/main/java/io/github/archipelagomw/events/InvalidPacketEvent.java index fd6ba61..545f581 100644 --- a/src/main/java/dev/koifysh/archipelago/events/InvalidPacketEvent.java +++ b/src/main/java/io/github/archipelagomw/events/InvalidPacketEvent.java @@ -1,4 +1,4 @@ -package dev.koifysh.archipelago.events; +package io.github.archipelagomw.events; public class InvalidPacketEvent implements Event { diff --git a/src/main/java/dev/koifysh/archipelago/events/LocationInfoEvent.java b/src/main/java/io/github/archipelagomw/events/LocationInfoEvent.java similarity index 72% rename from src/main/java/dev/koifysh/archipelago/events/LocationInfoEvent.java rename to src/main/java/io/github/archipelagomw/events/LocationInfoEvent.java index 95a314d..abcaa28 100644 --- a/src/main/java/dev/koifysh/archipelago/events/LocationInfoEvent.java +++ b/src/main/java/io/github/archipelagomw/events/LocationInfoEvent.java @@ -1,6 +1,6 @@ -package dev.koifysh.archipelago.events; +package io.github.archipelagomw.events; -import dev.koifysh.archipelago.parts.NetworkItem; +import io.github.archipelagomw.parts.NetworkItem; import java.util.ArrayList; diff --git a/src/main/java/dev/koifysh/archipelago/events/PrintJSONEvent.java b/src/main/java/io/github/archipelagomw/events/PrintJSONEvent.java similarity index 78% rename from src/main/java/dev/koifysh/archipelago/events/PrintJSONEvent.java rename to src/main/java/io/github/archipelagomw/events/PrintJSONEvent.java index 3ecf040..7a9153a 100644 --- a/src/main/java/dev/koifysh/archipelago/events/PrintJSONEvent.java +++ b/src/main/java/io/github/archipelagomw/events/PrintJSONEvent.java @@ -1,8 +1,8 @@ -package dev.koifysh.archipelago.events; +package io.github.archipelagomw.events; -import dev.koifysh.archipelago.Print.APPrint; -import dev.koifysh.archipelago.Print.APPrintJsonType; -import dev.koifysh.archipelago.parts.NetworkItem; +import io.github.archipelagomw.Print.APPrint; +import io.github.archipelagomw.Print.APPrintJsonType; +import io.github.archipelagomw.parts.NetworkItem; /** * event that is fired when the server wishes to send a message to the user. diff --git a/src/main/java/dev/koifysh/archipelago/events/ReceiveItemEvent.java b/src/main/java/io/github/archipelagomw/events/ReceiveItemEvent.java similarity index 89% rename from src/main/java/dev/koifysh/archipelago/events/ReceiveItemEvent.java rename to src/main/java/io/github/archipelagomw/events/ReceiveItemEvent.java index a3edf71..c3700c7 100644 --- a/src/main/java/dev/koifysh/archipelago/events/ReceiveItemEvent.java +++ b/src/main/java/io/github/archipelagomw/events/ReceiveItemEvent.java @@ -1,6 +1,6 @@ -package dev.koifysh.archipelago.events; +package io.github.archipelagomw.events; -import dev.koifysh.archipelago.parts.NetworkItem; +import io.github.archipelagomw.parts.NetworkItem; public class ReceiveItemEvent implements Event { diff --git a/src/main/java/dev/koifysh/archipelago/events/RetrievedEvent.java b/src/main/java/io/github/archipelagomw/events/RetrievedEvent.java similarity index 97% rename from src/main/java/dev/koifysh/archipelago/events/RetrievedEvent.java rename to src/main/java/io/github/archipelagomw/events/RetrievedEvent.java index 93b29ad..52ea2e9 100644 --- a/src/main/java/dev/koifysh/archipelago/events/RetrievedEvent.java +++ b/src/main/java/io/github/archipelagomw/events/RetrievedEvent.java @@ -1,4 +1,4 @@ -package dev.koifysh.archipelago.events; +package io.github.archipelagomw.events; import com.google.gson.Gson; import com.google.gson.JsonObject; diff --git a/src/main/java/dev/koifysh/archipelago/events/SetReplyEvent.java b/src/main/java/io/github/archipelagomw/events/SetReplyEvent.java similarity index 96% rename from src/main/java/dev/koifysh/archipelago/events/SetReplyEvent.java rename to src/main/java/io/github/archipelagomw/events/SetReplyEvent.java index ff23305..99edd09 100644 --- a/src/main/java/dev/koifysh/archipelago/events/SetReplyEvent.java +++ b/src/main/java/io/github/archipelagomw/events/SetReplyEvent.java @@ -1,4 +1,4 @@ -package dev.koifysh.archipelago.events; +package io.github.archipelagomw.events; import com.google.gson.Gson; import com.google.gson.JsonElement; diff --git a/src/main/java/dev/koifysh/archipelago/flags/ItemsHandling.java b/src/main/java/io/github/archipelagomw/flags/ItemsHandling.java similarity index 90% rename from src/main/java/dev/koifysh/archipelago/flags/ItemsHandling.java rename to src/main/java/io/github/archipelagomw/flags/ItemsHandling.java index 40ee06d..0365b21 100644 --- a/src/main/java/dev/koifysh/archipelago/flags/ItemsHandling.java +++ b/src/main/java/io/github/archipelagomw/flags/ItemsHandling.java @@ -1,6 +1,6 @@ -package dev.koifysh.archipelago.flags; +package io.github.archipelagomw.flags; -import dev.koifysh.archipelago.Client; +import io.github.archipelagomw.Client; /** * Item Flag variables to read {@link Client#getItemsHandlingFlags()} and set {@link Client#setItemsHandlingFlags(int)} diff --git a/src/main/java/dev/koifysh/archipelago/flags/NetworkItem.java b/src/main/java/io/github/archipelagomw/flags/NetworkItem.java similarity index 93% rename from src/main/java/dev/koifysh/archipelago/flags/NetworkItem.java rename to src/main/java/io/github/archipelagomw/flags/NetworkItem.java index 3a2f23c..212d8b6 100644 --- a/src/main/java/dev/koifysh/archipelago/flags/NetworkItem.java +++ b/src/main/java/io/github/archipelagomw/flags/NetworkItem.java @@ -1,4 +1,4 @@ -package dev.koifysh.archipelago.flags; +package io.github.archipelagomw.flags; /** * Flags that will tell you more about the item that was sent.
@@ -21,4 +21,5 @@ public class NetworkItem { * If set, indicates the item is a trap */ public final static int TRAP = 0b100; + } diff --git a/src/main/java/dev/koifysh/archipelago/flags/NetworkPlayer.java b/src/main/java/io/github/archipelagomw/flags/NetworkPlayer.java similarity index 92% rename from src/main/java/dev/koifysh/archipelago/flags/NetworkPlayer.java rename to src/main/java/io/github/archipelagomw/flags/NetworkPlayer.java index aec898d..3db1b7d 100644 --- a/src/main/java/dev/koifysh/archipelago/flags/NetworkPlayer.java +++ b/src/main/java/io/github/archipelagomw/flags/NetworkPlayer.java @@ -1,4 +1,4 @@ -package dev.koifysh.archipelago.flags; +package io.github.archipelagomw.flags; /** * Flags that will tell you more about the slot type.
diff --git a/src/main/java/dev/koifysh/archipelago/network/APPacket.java b/src/main/java/io/github/archipelagomw/network/APPacket.java similarity index 73% rename from src/main/java/dev/koifysh/archipelago/network/APPacket.java rename to src/main/java/io/github/archipelagomw/network/APPacket.java index 89d40f6..cd4e62e 100644 --- a/src/main/java/dev/koifysh/archipelago/network/APPacket.java +++ b/src/main/java/io/github/archipelagomw/network/APPacket.java @@ -1,11 +1,9 @@ -package dev.koifysh.archipelago.network; +package io.github.archipelagomw.network; -import com.google.gson.annotations.Expose; import com.google.gson.annotations.SerializedName; public class APPacket { - @Expose @SerializedName("cmd") private APPacketType cmd; diff --git a/src/main/java/dev/koifysh/archipelago/network/APPacketType.java b/src/main/java/io/github/archipelagomw/network/APPacketType.java similarity index 92% rename from src/main/java/dev/koifysh/archipelago/network/APPacketType.java rename to src/main/java/io/github/archipelagomw/network/APPacketType.java index 89cb354..2f21c75 100644 --- a/src/main/java/dev/koifysh/archipelago/network/APPacketType.java +++ b/src/main/java/io/github/archipelagomw/network/APPacketType.java @@ -1,4 +1,4 @@ -package dev.koifysh.archipelago.network; +package io.github.archipelagomw.network; import com.google.gson.annotations.SerializedName; @@ -54,5 +54,7 @@ public enum APPacketType { @SerializedName("InvalidPacket") InvalidPacket, @SerializedName("UpdateHint") - UpdateHint + UpdateHint, + @SerializedName("CreateHints") + CreateHints, } diff --git a/src/main/java/dev/koifysh/archipelago/network/ConnectionResult.java b/src/main/java/io/github/archipelagomw/network/ConnectionResult.java similarity index 58% rename from src/main/java/dev/koifysh/archipelago/network/ConnectionResult.java rename to src/main/java/io/github/archipelagomw/network/ConnectionResult.java index e0f709b..523c968 100644 --- a/src/main/java/dev/koifysh/archipelago/network/ConnectionResult.java +++ b/src/main/java/io/github/archipelagomw/network/ConnectionResult.java @@ -1,5 +1,5 @@ -package dev.koifysh.archipelago.network; +package io.github.archipelagomw.network; public enum ConnectionResult { - Success,InvalidSlot, SlotAlreadyTaken, IncompatibleVersion, InvalidPassword + Success,InvalidSlot, SlotAlreadyTaken, IncompatibleVersion, InvalidPassword,InvalidGame } diff --git a/src/main/java/io/github/archipelagomw/network/Permission.java b/src/main/java/io/github/archipelagomw/network/Permission.java new file mode 100644 index 0000000..2067c7b --- /dev/null +++ b/src/main/java/io/github/archipelagomw/network/Permission.java @@ -0,0 +1,21 @@ +package io.github.archipelagomw.network; + +import io.github.archipelagomw.utils.IntEnum; + +public enum Permission implements IntEnum { + disabled(0b000), + enabled(0b001), + goal(0b010), + auto(0b110), + auto_enabled(0b111); + + public final int value; + Permission(int value) { + this.value=value; + } + + @Override + public int getValue() { + return value; + } +} diff --git a/src/main/java/dev/koifysh/archipelago/network/RemainingMode.java b/src/main/java/io/github/archipelagomw/network/RemainingMode.java similarity index 89% rename from src/main/java/dev/koifysh/archipelago/network/RemainingMode.java rename to src/main/java/io/github/archipelagomw/network/RemainingMode.java index c1fb5df..8e92dcb 100644 --- a/src/main/java/dev/koifysh/archipelago/network/RemainingMode.java +++ b/src/main/java/io/github/archipelagomw/network/RemainingMode.java @@ -1,4 +1,4 @@ -package dev.koifysh.archipelago.network; +package io.github.archipelagomw.network; public enum RemainingMode { enabled, diff --git a/src/main/java/dev/koifysh/archipelago/network/client/BouncePacket.java b/src/main/java/io/github/archipelagomw/network/client/BouncePacket.java similarity index 79% rename from src/main/java/dev/koifysh/archipelago/network/client/BouncePacket.java rename to src/main/java/io/github/archipelagomw/network/client/BouncePacket.java index 6d822ff..a165ef8 100644 --- a/src/main/java/dev/koifysh/archipelago/network/client/BouncePacket.java +++ b/src/main/java/io/github/archipelagomw/network/client/BouncePacket.java @@ -1,10 +1,10 @@ -package dev.koifysh.archipelago.network.client; +package io.github.archipelagomw.network.client; import com.google.gson.annotations.Expose; import com.google.gson.annotations.SerializedName; -import dev.koifysh.archipelago.events.Event; -import dev.koifysh.archipelago.network.APPacket; -import dev.koifysh.archipelago.network.APPacketType; +import io.github.archipelagomw.events.Event; +import io.github.archipelagomw.network.APPacket; +import io.github.archipelagomw.network.APPacketType; import java.util.HashMap; diff --git a/src/main/java/dev/koifysh/archipelago/network/client/ConnectPacket.java b/src/main/java/io/github/archipelagomw/network/client/ConnectPacket.java similarity index 75% rename from src/main/java/dev/koifysh/archipelago/network/client/ConnectPacket.java rename to src/main/java/io/github/archipelagomw/network/client/ConnectPacket.java index 99d4b13..7643cb8 100644 --- a/src/main/java/dev/koifysh/archipelago/network/client/ConnectPacket.java +++ b/src/main/java/io/github/archipelagomw/network/client/ConnectPacket.java @@ -1,9 +1,9 @@ -package dev.koifysh.archipelago.network.client; +package io.github.archipelagomw.network.client; import com.google.gson.annotations.SerializedName; -import dev.koifysh.archipelago.network.APPacket; -import dev.koifysh.archipelago.network.APPacketType; -import dev.koifysh.archipelago.parts.Version; +import io.github.archipelagomw.network.APPacket; +import io.github.archipelagomw.network.APPacketType; +import io.github.archipelagomw.parts.Version; import java.util.Set; diff --git a/src/main/java/io/github/archipelagomw/network/client/CreateAsHint.java b/src/main/java/io/github/archipelagomw/network/client/CreateAsHint.java new file mode 100644 index 0000000..429fe8b --- /dev/null +++ b/src/main/java/io/github/archipelagomw/network/client/CreateAsHint.java @@ -0,0 +1,21 @@ +package io.github.archipelagomw.network.client; + +import io.github.archipelagomw.utils.IntEnum; + +public enum CreateAsHint implements IntEnum { + NO(0), + BROADCAST_ALWAYS(1), + BROADCAST_NEW(2); + + public final int value; + + private CreateAsHint(int value) + { + this.value = value; + } + + @Override + public int getValue() { + return value; + } +} diff --git a/src/main/java/io/github/archipelagomw/network/client/CreateHintPacket.java b/src/main/java/io/github/archipelagomw/network/client/CreateHintPacket.java new file mode 100644 index 0000000..df09d7b --- /dev/null +++ b/src/main/java/io/github/archipelagomw/network/client/CreateHintPacket.java @@ -0,0 +1,25 @@ +package io.github.archipelagomw.network.client; + +import io.github.archipelagomw.network.APPacket; +import io.github.archipelagomw.network.APPacketType; + +import java.util.ArrayList; +import java.util.List; + +public class CreateHintPacket extends APPacket { + + public List locations = new ArrayList<>(); + public int player; + public HintStatus status = HintStatus.HINT_UNSPECIFIED; + + public CreateHintPacket(List locations, int player) { + this(locations, player, HintStatus.HINT_UNSPECIFIED); + } + + public CreateHintPacket(List locations, int player, HintStatus status) { + super(APPacketType.CreateHints); + this.locations = locations; + this.player = player; + this.status = status; + } +} diff --git a/src/main/java/dev/koifysh/archipelago/network/client/GetDataPackagePacket.java b/src/main/java/io/github/archipelagomw/network/client/GetDataPackagePacket.java similarity index 66% rename from src/main/java/dev/koifysh/archipelago/network/client/GetDataPackagePacket.java rename to src/main/java/io/github/archipelagomw/network/client/GetDataPackagePacket.java index 267a06d..df5918b 100644 --- a/src/main/java/dev/koifysh/archipelago/network/client/GetDataPackagePacket.java +++ b/src/main/java/io/github/archipelagomw/network/client/GetDataPackagePacket.java @@ -1,7 +1,7 @@ -package dev.koifysh.archipelago.network.client; +package io.github.archipelagomw.network.client; -import dev.koifysh.archipelago.network.APPacket; -import dev.koifysh.archipelago.network.APPacketType; +import io.github.archipelagomw.network.APPacket; +import io.github.archipelagomw.network.APPacketType; import java.util.Set; diff --git a/src/main/java/dev/koifysh/archipelago/network/client/GetPacket.java b/src/main/java/io/github/archipelagomw/network/client/GetPacket.java similarity index 62% rename from src/main/java/dev/koifysh/archipelago/network/client/GetPacket.java rename to src/main/java/io/github/archipelagomw/network/client/GetPacket.java index fa10ce1..33e1c01 100644 --- a/src/main/java/dev/koifysh/archipelago/network/client/GetPacket.java +++ b/src/main/java/io/github/archipelagomw/network/client/GetPacket.java @@ -1,13 +1,13 @@ -package dev.koifysh.archipelago.network.client; +package io.github.archipelagomw.network.client; import com.google.gson.annotations.SerializedName; -import dev.koifysh.archipelago.Client; -import dev.koifysh.archipelago.events.RetrievedEvent; -import dev.koifysh.archipelago.network.APPacket; -import dev.koifysh.archipelago.network.APPacketType; +import io.github.archipelagomw.Client; +import io.github.archipelagomw.events.RetrievedEvent; +import io.github.archipelagomw.network.APPacket; +import io.github.archipelagomw.network.APPacketType; import java.util.Collection; -import java.util.Random; +import java.util.concurrent.atomic.AtomicInteger; /** * Used to request a single or multiple values from the server's data storage, see the @@ -16,6 +16,8 @@ */ public class GetPacket extends APPacket { + private static final AtomicInteger requestIdGen = new AtomicInteger(); + /** * a list of keys to retrieve data for. */ @@ -23,12 +25,12 @@ public class GetPacket extends APPacket { public Collection keys; @SerializedName("request_id") - private int requestID; + private final int requestID; public GetPacket(Collection keys) { super(APPacketType.Get); this.keys = keys; - requestID = new Random().nextInt(Integer.MAX_VALUE); + requestID = requestIdGen.getAndIncrement(); } public int getRequestID() { diff --git a/src/main/java/io/github/archipelagomw/network/client/HintStatus.java b/src/main/java/io/github/archipelagomw/network/client/HintStatus.java new file mode 100644 index 0000000..4657269 --- /dev/null +++ b/src/main/java/io/github/archipelagomw/network/client/HintStatus.java @@ -0,0 +1,23 @@ +package io.github.archipelagomw.network.client; + +import io.github.archipelagomw.utils.IntEnum; + +public enum HintStatus implements IntEnum { + HINT_UNSPECIFIED(0), + HINT_NO_PRIORITY(10), + HINT_AVOID(20), + HINT_PRIORITY(30), + HINT_FOUND(40); + + private final int value; + HintStatus(int value) { + this.value=value; + } + + @Override + public int getValue() + { + return value; + } + +} \ No newline at end of file diff --git a/src/main/java/dev/koifysh/archipelago/network/client/LocationChecks.java b/src/main/java/io/github/archipelagomw/network/client/LocationChecks.java similarity index 67% rename from src/main/java/dev/koifysh/archipelago/network/client/LocationChecks.java rename to src/main/java/io/github/archipelagomw/network/client/LocationChecks.java index e6bf674..2a0e8f4 100644 --- a/src/main/java/dev/koifysh/archipelago/network/client/LocationChecks.java +++ b/src/main/java/io/github/archipelagomw/network/client/LocationChecks.java @@ -1,8 +1,8 @@ -package dev.koifysh.archipelago.network.client; +package io.github.archipelagomw.network.client; import com.google.gson.annotations.SerializedName; -import dev.koifysh.archipelago.network.APPacket; -import dev.koifysh.archipelago.network.APPacketType; +import io.github.archipelagomw.network.APPacket; +import io.github.archipelagomw.network.APPacketType; import java.util.HashSet; import java.util.Set; diff --git a/src/main/java/io/github/archipelagomw/network/client/LocationScouts.java b/src/main/java/io/github/archipelagomw/network/client/LocationScouts.java new file mode 100644 index 0000000..531f143 --- /dev/null +++ b/src/main/java/io/github/archipelagomw/network/client/LocationScouts.java @@ -0,0 +1,24 @@ +package io.github.archipelagomw.network.client; + +import com.google.gson.annotations.SerializedName; +import io.github.archipelagomw.network.APPacket; +import io.github.archipelagomw.network.APPacketType; + +import java.util.ArrayList; + +public class LocationScouts extends APPacket { + + public ArrayList locations = new ArrayList<>(); + @SerializedName("create_as_hint") + public Integer createAsHint; + + public LocationScouts(ArrayList locations) { + this(locations, null); + } + + public LocationScouts(ArrayList locations, CreateAsHint createAsHint) { + super(APPacketType.LocationScouts); + this.locations = locations; + this.createAsHint = createAsHint == null ? null : createAsHint.getValue(); + } +} diff --git a/src/main/java/dev/koifysh/archipelago/network/client/SayPacket.java b/src/main/java/io/github/archipelagomw/network/client/SayPacket.java similarity index 62% rename from src/main/java/dev/koifysh/archipelago/network/client/SayPacket.java rename to src/main/java/io/github/archipelagomw/network/client/SayPacket.java index 1d53ec8..773c255 100644 --- a/src/main/java/dev/koifysh/archipelago/network/client/SayPacket.java +++ b/src/main/java/io/github/archipelagomw/network/client/SayPacket.java @@ -1,8 +1,8 @@ -package dev.koifysh.archipelago.network.client; +package io.github.archipelagomw.network.client; import com.google.gson.annotations.SerializedName; -import dev.koifysh.archipelago.network.APPacket; -import dev.koifysh.archipelago.network.APPacketType; +import io.github.archipelagomw.network.APPacket; +import io.github.archipelagomw.network.APPacketType; public class SayPacket extends APPacket { diff --git a/src/main/java/dev/koifysh/archipelago/network/client/SetNotifyPacket.java b/src/main/java/io/github/archipelagomw/network/client/SetNotifyPacket.java similarity index 68% rename from src/main/java/dev/koifysh/archipelago/network/client/SetNotifyPacket.java rename to src/main/java/io/github/archipelagomw/network/client/SetNotifyPacket.java index aa7ca67..1995aab 100644 --- a/src/main/java/dev/koifysh/archipelago/network/client/SetNotifyPacket.java +++ b/src/main/java/io/github/archipelagomw/network/client/SetNotifyPacket.java @@ -1,7 +1,7 @@ -package dev.koifysh.archipelago.network.client; +package io.github.archipelagomw.network.client; -import dev.koifysh.archipelago.network.APPacket; -import dev.koifysh.archipelago.network.APPacketType; +import io.github.archipelagomw.network.APPacket; +import io.github.archipelagomw.network.APPacketType; import java.util.Collection; diff --git a/src/main/java/dev/koifysh/archipelago/network/client/SetPacket.java b/src/main/java/io/github/archipelagomw/network/client/SetPacket.java similarity index 93% rename from src/main/java/dev/koifysh/archipelago/network/client/SetPacket.java rename to src/main/java/io/github/archipelagomw/network/client/SetPacket.java index f2e2a0c..6ce5468 100644 --- a/src/main/java/dev/koifysh/archipelago/network/client/SetPacket.java +++ b/src/main/java/io/github/archipelagomw/network/client/SetPacket.java @@ -1,15 +1,15 @@ -package dev.koifysh.archipelago.network.client; +package io.github.archipelagomw.network.client; import com.google.gson.annotations.SerializedName; -import dev.koifysh.archipelago.events.SetReplyEvent; -import dev.koifysh.archipelago.network.APPacket; -import dev.koifysh.archipelago.network.APPacketType; +import io.github.archipelagomw.events.SetReplyEvent; +import io.github.archipelagomw.network.APPacket; +import io.github.archipelagomw.network.APPacketType; import java.util.ArrayList; -import java.util.Random; +import java.util.concurrent.atomic.AtomicInteger; public class SetPacket extends APPacket { - + private static final AtomicInteger requestIdGen = new AtomicInteger(1); /** * The key to manipulate. Can never start with "_read". */ @@ -37,13 +37,13 @@ public class SetPacket extends APPacket { public ArrayList operations = new ArrayList<>(); @SerializedName("request_id") - private int requestID; + private final int requestID; public SetPacket(String key, Object defaultValue) { super(APPacketType.Set); this.key = key; this.defaultValue = defaultValue; - requestID = new Random().nextInt(Integer.MAX_VALUE); + requestID = requestIdGen.getAndIncrement(); } /** diff --git a/src/main/java/dev/koifysh/archipelago/network/client/StatusUpdatePacket.java b/src/main/java/io/github/archipelagomw/network/client/StatusUpdatePacket.java similarity index 52% rename from src/main/java/dev/koifysh/archipelago/network/client/StatusUpdatePacket.java rename to src/main/java/io/github/archipelagomw/network/client/StatusUpdatePacket.java index 32320d8..8b969b7 100644 --- a/src/main/java/dev/koifysh/archipelago/network/client/StatusUpdatePacket.java +++ b/src/main/java/io/github/archipelagomw/network/client/StatusUpdatePacket.java @@ -1,8 +1,8 @@ -package dev.koifysh.archipelago.network.client; +package io.github.archipelagomw.network.client; -import dev.koifysh.archipelago.ClientStatus; -import dev.koifysh.archipelago.network.APPacket; -import dev.koifysh.archipelago.network.APPacketType; +import io.github.archipelagomw.ClientStatus; +import io.github.archipelagomw.network.APPacket; +import io.github.archipelagomw.network.APPacketType; public class StatusUpdatePacket extends APPacket { diff --git a/src/main/java/io/github/archipelagomw/network/client/SyncPacket.java b/src/main/java/io/github/archipelagomw/network/client/SyncPacket.java new file mode 100644 index 0000000..d80f968 --- /dev/null +++ b/src/main/java/io/github/archipelagomw/network/client/SyncPacket.java @@ -0,0 +1,12 @@ +package io.github.archipelagomw.network.client; + +import io.github.archipelagomw.network.APPacket; +import io.github.archipelagomw.network.APPacketType; + +public class SyncPacket extends APPacket { + + + public SyncPacket() { + super(APPacketType.Sync); + } +} diff --git a/src/main/java/dev/koifysh/archipelago/network/client/UpdateHintPacket.java b/src/main/java/io/github/archipelagomw/network/client/UpdateHintPacket.java similarity index 73% rename from src/main/java/dev/koifysh/archipelago/network/client/UpdateHintPacket.java rename to src/main/java/io/github/archipelagomw/network/client/UpdateHintPacket.java index 71b3d5b..5e34c26 100644 --- a/src/main/java/dev/koifysh/archipelago/network/client/UpdateHintPacket.java +++ b/src/main/java/io/github/archipelagomw/network/client/UpdateHintPacket.java @@ -1,9 +1,9 @@ -package dev.koifysh.archipelago.network.client; +package io.github.archipelagomw.network.client; import com.google.gson.annotations.SerializedName; -import dev.koifysh.archipelago.network.APPacket; -import dev.koifysh.archipelago.network.APPacketType; +import io.github.archipelagomw.network.APPacket; +import io.github.archipelagomw.network.APPacketType; public class UpdateHintPacket extends APPacket{ /* diff --git a/src/main/java/dev/koifysh/archipelago/network/server/BouncedPacket.java b/src/main/java/io/github/archipelagomw/network/server/BouncedPacket.java similarity index 79% rename from src/main/java/dev/koifysh/archipelago/network/server/BouncedPacket.java rename to src/main/java/io/github/archipelagomw/network/server/BouncedPacket.java index 6c744ba..bb76159 100644 --- a/src/main/java/dev/koifysh/archipelago/network/server/BouncedPacket.java +++ b/src/main/java/io/github/archipelagomw/network/server/BouncedPacket.java @@ -1,8 +1,8 @@ -package dev.koifysh.archipelago.network.server; +package io.github.archipelagomw.network.server; import com.google.gson.annotations.SerializedName; -import dev.koifysh.archipelago.network.APPacket; -import dev.koifysh.archipelago.network.APPacketType; +import io.github.archipelagomw.network.APPacket; +import io.github.archipelagomw.network.APPacketType; import java.util.HashMap; import java.util.HashSet; diff --git a/src/main/java/dev/koifysh/archipelago/network/server/ConnectUpdatePacket.java b/src/main/java/io/github/archipelagomw/network/server/ConnectUpdatePacket.java similarity index 64% rename from src/main/java/dev/koifysh/archipelago/network/server/ConnectUpdatePacket.java rename to src/main/java/io/github/archipelagomw/network/server/ConnectUpdatePacket.java index 2871d70..d7a5376 100644 --- a/src/main/java/dev/koifysh/archipelago/network/server/ConnectUpdatePacket.java +++ b/src/main/java/io/github/archipelagomw/network/server/ConnectUpdatePacket.java @@ -1,8 +1,8 @@ -package dev.koifysh.archipelago.network.server; +package io.github.archipelagomw.network.server; import com.google.gson.annotations.SerializedName; -import dev.koifysh.archipelago.network.APPacket; -import dev.koifysh.archipelago.network.APPacketType; +import io.github.archipelagomw.network.APPacket; +import io.github.archipelagomw.network.APPacketType; import java.util.Set; diff --git a/src/main/java/dev/koifysh/archipelago/network/server/ConnectedPacket.java b/src/main/java/io/github/archipelagomw/network/server/ConnectedPacket.java similarity index 74% rename from src/main/java/dev/koifysh/archipelago/network/server/ConnectedPacket.java rename to src/main/java/io/github/archipelagomw/network/server/ConnectedPacket.java index 9049c49..f430863 100644 --- a/src/main/java/dev/koifysh/archipelago/network/server/ConnectedPacket.java +++ b/src/main/java/io/github/archipelagomw/network/server/ConnectedPacket.java @@ -1,10 +1,10 @@ -package dev.koifysh.archipelago.network.server; +package io.github.archipelagomw.network.server; import com.google.gson.annotations.SerializedName; -import dev.koifysh.archipelago.network.APPacket; -import dev.koifysh.archipelago.network.APPacketType; -import dev.koifysh.archipelago.parts.NetworkPlayer; -import dev.koifysh.archipelago.parts.NetworkSlot; +import io.github.archipelagomw.network.APPacket; +import io.github.archipelagomw.network.APPacketType; +import io.github.archipelagomw.parts.NetworkPlayer; +import io.github.archipelagomw.parts.NetworkSlot; import java.util.ArrayList; import java.util.HashMap; diff --git a/src/main/java/dev/koifysh/archipelago/network/server/ConnectionRefusedPacket.java b/src/main/java/io/github/archipelagomw/network/server/ConnectionRefusedPacket.java similarity index 56% rename from src/main/java/dev/koifysh/archipelago/network/server/ConnectionRefusedPacket.java rename to src/main/java/io/github/archipelagomw/network/server/ConnectionRefusedPacket.java index 0e91e4f..31d03a5 100644 --- a/src/main/java/dev/koifysh/archipelago/network/server/ConnectionRefusedPacket.java +++ b/src/main/java/io/github/archipelagomw/network/server/ConnectionRefusedPacket.java @@ -1,9 +1,9 @@ -package dev.koifysh.archipelago.network.server; +package io.github.archipelagomw.network.server; import com.google.gson.annotations.SerializedName; -import dev.koifysh.archipelago.network.APPacket; -import dev.koifysh.archipelago.network.APPacketType; -import dev.koifysh.archipelago.network.ConnectionResult; +import io.github.archipelagomw.network.APPacket; +import io.github.archipelagomw.network.APPacketType; +import io.github.archipelagomw.network.ConnectionResult; public class ConnectionRefusedPacket extends APPacket { diff --git a/src/main/java/dev/koifysh/archipelago/network/server/InvalidPacket.java b/src/main/java/io/github/archipelagomw/network/server/InvalidPacket.java similarity index 70% rename from src/main/java/dev/koifysh/archipelago/network/server/InvalidPacket.java rename to src/main/java/io/github/archipelagomw/network/server/InvalidPacket.java index f5a855e..0c13066 100644 --- a/src/main/java/dev/koifysh/archipelago/network/server/InvalidPacket.java +++ b/src/main/java/io/github/archipelagomw/network/server/InvalidPacket.java @@ -1,8 +1,8 @@ -package dev.koifysh.archipelago.network.server; +package io.github.archipelagomw.network.server; import com.google.gson.annotations.SerializedName; -import dev.koifysh.archipelago.network.APPacket; -import dev.koifysh.archipelago.network.APPacketType; +import io.github.archipelagomw.network.APPacket; +import io.github.archipelagomw.network.APPacketType; public class InvalidPacket extends APPacket { diff --git a/src/main/java/io/github/archipelagomw/network/server/JsonPrintPacket.java b/src/main/java/io/github/archipelagomw/network/server/JsonPrintPacket.java new file mode 100644 index 0000000..096787c --- /dev/null +++ b/src/main/java/io/github/archipelagomw/network/server/JsonPrintPacket.java @@ -0,0 +1,13 @@ +package io.github.archipelagomw.network.server; + +import io.github.archipelagomw.Print.APPrintPart; +import io.github.archipelagomw.network.APPacket; +import io.github.archipelagomw.network.APPacketType; + +public class JsonPrintPacket extends APPacket { + APPrintPart[] parts; + + public JsonPrintPacket() { + super(APPacketType.PrintJSON); + } +} diff --git a/src/main/java/dev/koifysh/archipelago/network/server/LocationInfoPacket.java b/src/main/java/io/github/archipelagomw/network/server/LocationInfoPacket.java similarity index 59% rename from src/main/java/dev/koifysh/archipelago/network/server/LocationInfoPacket.java rename to src/main/java/io/github/archipelagomw/network/server/LocationInfoPacket.java index 309ca7b..379de58 100644 --- a/src/main/java/dev/koifysh/archipelago/network/server/LocationInfoPacket.java +++ b/src/main/java/io/github/archipelagomw/network/server/LocationInfoPacket.java @@ -1,9 +1,9 @@ -package dev.koifysh.archipelago.network.server; +package io.github.archipelagomw.network.server; import com.google.gson.annotations.SerializedName; -import dev.koifysh.archipelago.network.APPacket; -import dev.koifysh.archipelago.network.APPacketType; -import dev.koifysh.archipelago.parts.NetworkItem; +import io.github.archipelagomw.network.APPacket; +import io.github.archipelagomw.network.APPacketType; +import io.github.archipelagomw.parts.NetworkItem; import java.util.ArrayList; diff --git a/src/main/java/dev/koifysh/archipelago/network/server/PrintPacket.java b/src/main/java/io/github/archipelagomw/network/server/PrintPacket.java similarity index 64% rename from src/main/java/dev/koifysh/archipelago/network/server/PrintPacket.java rename to src/main/java/io/github/archipelagomw/network/server/PrintPacket.java index 8c7a9ed..c02d5b3 100644 --- a/src/main/java/dev/koifysh/archipelago/network/server/PrintPacket.java +++ b/src/main/java/io/github/archipelagomw/network/server/PrintPacket.java @@ -1,8 +1,8 @@ -package dev.koifysh.archipelago.network.server; +package io.github.archipelagomw.network.server; import com.google.gson.annotations.SerializedName; -import dev.koifysh.archipelago.network.APPacket; -import dev.koifysh.archipelago.network.APPacketType; +import io.github.archipelagomw.network.APPacket; +import io.github.archipelagomw.network.APPacketType; public class PrintPacket extends APPacket { diff --git a/src/main/java/dev/koifysh/archipelago/network/server/ReceivedItemsPacket.java b/src/main/java/io/github/archipelagomw/network/server/ReceivedItemsPacket.java similarity index 63% rename from src/main/java/dev/koifysh/archipelago/network/server/ReceivedItemsPacket.java rename to src/main/java/io/github/archipelagomw/network/server/ReceivedItemsPacket.java index a8925e3..d7dc329 100644 --- a/src/main/java/dev/koifysh/archipelago/network/server/ReceivedItemsPacket.java +++ b/src/main/java/io/github/archipelagomw/network/server/ReceivedItemsPacket.java @@ -1,9 +1,9 @@ -package dev.koifysh.archipelago.network.server; +package io.github.archipelagomw.network.server; import com.google.gson.annotations.SerializedName; -import dev.koifysh.archipelago.network.APPacket; -import dev.koifysh.archipelago.network.APPacketType; -import dev.koifysh.archipelago.parts.NetworkItem; +import io.github.archipelagomw.network.APPacket; +import io.github.archipelagomw.network.APPacketType; +import io.github.archipelagomw.parts.NetworkItem; import java.util.ArrayList; diff --git a/src/main/java/dev/koifysh/archipelago/network/server/RetrievedPacket.java b/src/main/java/io/github/archipelagomw/network/server/RetrievedPacket.java similarity index 69% rename from src/main/java/dev/koifysh/archipelago/network/server/RetrievedPacket.java rename to src/main/java/io/github/archipelagomw/network/server/RetrievedPacket.java index 7033406..2eb1b52 100644 --- a/src/main/java/dev/koifysh/archipelago/network/server/RetrievedPacket.java +++ b/src/main/java/io/github/archipelagomw/network/server/RetrievedPacket.java @@ -1,8 +1,8 @@ -package dev.koifysh.archipelago.network.server; +package io.github.archipelagomw.network.server; import com.google.gson.annotations.SerializedName; -import dev.koifysh.archipelago.network.APPacket; -import dev.koifysh.archipelago.network.APPacketType; +import io.github.archipelagomw.network.APPacket; +import io.github.archipelagomw.network.APPacketType; import java.util.HashMap; diff --git a/src/main/java/dev/koifysh/archipelago/network/server/RoomInfoPacket.java b/src/main/java/io/github/archipelagomw/network/server/RoomInfoPacket.java similarity index 83% rename from src/main/java/dev/koifysh/archipelago/network/server/RoomInfoPacket.java rename to src/main/java/io/github/archipelagomw/network/server/RoomInfoPacket.java index 46f2b88..ff58dad 100644 --- a/src/main/java/dev/koifysh/archipelago/network/server/RoomInfoPacket.java +++ b/src/main/java/io/github/archipelagomw/network/server/RoomInfoPacket.java @@ -1,10 +1,10 @@ -package dev.koifysh.archipelago.network.server; +package io.github.archipelagomw.network.server; import com.google.gson.annotations.SerializedName; -import dev.koifysh.archipelago.network.APPacket; -import dev.koifysh.archipelago.network.APPacketType; -import dev.koifysh.archipelago.parts.NetworkPlayer; -import dev.koifysh.archipelago.parts.Version; +import io.github.archipelagomw.network.APPacket; +import io.github.archipelagomw.network.APPacketType; +import io.github.archipelagomw.parts.NetworkPlayer; +import io.github.archipelagomw.parts.Version; import java.util.ArrayList; import java.util.HashMap; diff --git a/src/main/java/dev/koifysh/archipelago/network/server/RoomUpdatePacket.java b/src/main/java/io/github/archipelagomw/network/server/RoomUpdatePacket.java similarity index 81% rename from src/main/java/dev/koifysh/archipelago/network/server/RoomUpdatePacket.java rename to src/main/java/io/github/archipelagomw/network/server/RoomUpdatePacket.java index 10b677f..f2973ff 100644 --- a/src/main/java/dev/koifysh/archipelago/network/server/RoomUpdatePacket.java +++ b/src/main/java/io/github/archipelagomw/network/server/RoomUpdatePacket.java @@ -1,11 +1,11 @@ -package dev.koifysh.archipelago.network.server; +package io.github.archipelagomw.network.server; import com.google.gson.annotations.SerializedName; -import dev.koifysh.archipelago.network.APPacket; -import dev.koifysh.archipelago.network.APPacketType; -import dev.koifysh.archipelago.network.RemainingMode; -import dev.koifysh.archipelago.parts.NetworkPlayer; -import dev.koifysh.archipelago.parts.Version; +import io.github.archipelagomw.network.APPacket; +import io.github.archipelagomw.network.APPacketType; +import io.github.archipelagomw.network.RemainingMode; +import io.github.archipelagomw.parts.NetworkPlayer; +import io.github.archipelagomw.parts.Version; import java.util.ArrayList; import java.util.HashMap; diff --git a/src/main/java/dev/koifysh/archipelago/network/server/SetReplyPacket.java b/src/main/java/io/github/archipelagomw/network/server/SetReplyPacket.java similarity index 75% rename from src/main/java/dev/koifysh/archipelago/network/server/SetReplyPacket.java rename to src/main/java/io/github/archipelagomw/network/server/SetReplyPacket.java index 0c20111..8a14128 100644 --- a/src/main/java/dev/koifysh/archipelago/network/server/SetReplyPacket.java +++ b/src/main/java/io/github/archipelagomw/network/server/SetReplyPacket.java @@ -1,8 +1,8 @@ -package dev.koifysh.archipelago.network.server; +package io.github.archipelagomw.network.server; import com.google.gson.annotations.SerializedName; -import dev.koifysh.archipelago.network.APPacket; -import dev.koifysh.archipelago.network.APPacketType; +import io.github.archipelagomw.network.APPacket; +import io.github.archipelagomw.network.APPacketType; public class SetReplyPacket extends APPacket { @SerializedName("key") diff --git a/src/main/java/dev/koifysh/archipelago/parts/DataPackage.java b/src/main/java/io/github/archipelagomw/parts/DataPackage.java similarity index 77% rename from src/main/java/dev/koifysh/archipelago/parts/DataPackage.java rename to src/main/java/io/github/archipelagomw/parts/DataPackage.java index 85a3573..2dd347f 100644 --- a/src/main/java/dev/koifysh/archipelago/parts/DataPackage.java +++ b/src/main/java/io/github/archipelagomw/parts/DataPackage.java @@ -1,20 +1,15 @@ -package dev.koifysh.archipelago.parts; +package io.github.archipelagomw.parts; -import com.google.gson.annotations.Expose; import com.google.gson.annotations.SerializedName; import java.io.Serializable; import java.util.HashMap; import java.util.Map; -import java.util.UUID; public class DataPackage implements Serializable { - @Expose @SerializedName("games") - HashMap games = new HashMap<>(); - - public String uuid = UUID.randomUUID().toString(); + private final Map games = new HashMap<>(); public String getItem(long itemID, String game) { if (!games.containsKey(game)) @@ -22,7 +17,6 @@ public String getItem(long itemID, String game) { if(!games.get(game).itemNameToId.containsValue(itemID)) return String.format("Unknown Item [%d] for [%s]", itemID, game); - return games.get(game).getItem(itemID); } @@ -37,12 +31,12 @@ public String getLocation(long locationID, String game) { } public Map getChecksums() { - HashMap checksums = new HashMap<>(); + Map checksums = new HashMap<>(); games.forEach((key, value) -> checksums.put(key, value.checksum)); return checksums; } - public HashMap getGames() { + public Map getGames() { return games; } @@ -50,12 +44,12 @@ public Game getGame(String game) { return games.get(game); } - - public String getUUID() { - return uuid; - } - public void update(DataPackage newData) { games.putAll(newData.getGames()); } + + public void update(String name, Game game) + { + games.put(name, game); + } } diff --git a/src/main/java/dev/koifysh/archipelago/parts/Game.java b/src/main/java/io/github/archipelagomw/parts/Game.java similarity index 76% rename from src/main/java/dev/koifysh/archipelago/parts/Game.java rename to src/main/java/io/github/archipelagomw/parts/Game.java index 0d52bc4..fbdd85f 100644 --- a/src/main/java/dev/koifysh/archipelago/parts/Game.java +++ b/src/main/java/io/github/archipelagomw/parts/Game.java @@ -1,10 +1,11 @@ -package dev.koifysh.archipelago.parts; +package io.github.archipelagomw.parts; import com.google.gson.annotations.SerializedName; import java.io.Serializable; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; public class Game implements Serializable { @@ -14,13 +15,13 @@ public class Game implements Serializable { public String checksum; @SerializedName("item_name_to_id") - public HashMap itemNameToId = new HashMap<>(); + public Map itemNameToId = new HashMap<>(); @SerializedName("location_name_to_id") - public HashMap locationNameToId = new HashMap<>(); + public Map locationNameToId = new HashMap<>(); - private final HashMap idToItem = new HashMap<>(); - private final HashMap idToLocation = new HashMap<>(); + private final Map idToItem = new ConcurrentHashMap<>(); + private final Map idToLocation = new ConcurrentHashMap<>(); public String getItem(long itemID) { if(idToItem.isEmpty()) { diff --git a/src/main/java/dev/koifysh/archipelago/parts/NetworkItem.java b/src/main/java/io/github/archipelagomw/parts/NetworkItem.java similarity index 82% rename from src/main/java/dev/koifysh/archipelago/parts/NetworkItem.java rename to src/main/java/io/github/archipelagomw/parts/NetworkItem.java index 0031adb..56874b2 100644 --- a/src/main/java/dev/koifysh/archipelago/parts/NetworkItem.java +++ b/src/main/java/io/github/archipelagomw/parts/NetworkItem.java @@ -1,4 +1,4 @@ -package dev.koifysh.archipelago.parts; +package io.github.archipelagomw.parts; import com.google.gson.annotations.SerializedName; @@ -14,7 +14,7 @@ public class NetworkItem { public int playerID; /** - * Bit flags that tell you information about the item. bitwise AND them with {@link dev.koifysh.archipelago.flags.NetworkItem} to read. + * Bit flags that tell you information about the item. bitwise AND them with {@link io.github.archipelagomw.flags.NetworkItem} to read. */ @SerializedName("flags") public int flags; diff --git a/src/main/java/dev/koifysh/archipelago/parts/NetworkPlayer.java b/src/main/java/io/github/archipelagomw/parts/NetworkPlayer.java similarity index 88% rename from src/main/java/dev/koifysh/archipelago/parts/NetworkPlayer.java rename to src/main/java/io/github/archipelagomw/parts/NetworkPlayer.java index bd5e511..16f8ec6 100644 --- a/src/main/java/dev/koifysh/archipelago/parts/NetworkPlayer.java +++ b/src/main/java/io/github/archipelagomw/parts/NetworkPlayer.java @@ -1,4 +1,4 @@ -package dev.koifysh.archipelago.parts; +package io.github.archipelagomw.parts; public class NetworkPlayer { public int team; diff --git a/src/main/java/dev/koifysh/archipelago/parts/NetworkSlot.java b/src/main/java/io/github/archipelagomw/parts/NetworkSlot.java similarity index 90% rename from src/main/java/dev/koifysh/archipelago/parts/NetworkSlot.java rename to src/main/java/io/github/archipelagomw/parts/NetworkSlot.java index c812f94..d80df1a 100644 --- a/src/main/java/dev/koifysh/archipelago/parts/NetworkSlot.java +++ b/src/main/java/io/github/archipelagomw/parts/NetworkSlot.java @@ -1,4 +1,4 @@ -package dev.koifysh.archipelago.parts; +package io.github.archipelagomw.parts; import com.google.gson.annotations.SerializedName; diff --git a/src/main/java/dev/koifysh/archipelago/parts/Version.java b/src/main/java/io/github/archipelagomw/parts/Version.java similarity index 89% rename from src/main/java/dev/koifysh/archipelago/parts/Version.java rename to src/main/java/io/github/archipelagomw/parts/Version.java index eed4df7..11c5384 100644 --- a/src/main/java/dev/koifysh/archipelago/parts/Version.java +++ b/src/main/java/io/github/archipelagomw/parts/Version.java @@ -1,4 +1,4 @@ -package dev.koifysh.archipelago.parts; +package io.github.archipelagomw.parts; import com.google.gson.annotations.SerializedName; diff --git a/src/main/java/io/github/archipelagomw/utils/AtomicDouble.java b/src/main/java/io/github/archipelagomw/utils/AtomicDouble.java new file mode 100644 index 0000000..8f3b738 --- /dev/null +++ b/src/main/java/io/github/archipelagomw/utils/AtomicDouble.java @@ -0,0 +1,31 @@ +package io.github.archipelagomw.utils; + +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.DoubleUnaryOperator; + +public class AtomicDouble { + // Cause Java hates us, and doesn't want to give us doubles + + private final AtomicLong bits = new AtomicLong(); + + public AtomicDouble(double initialValue) + { + bits.set(Double.doubleToLongBits(initialValue)); + } + + public double get() + { + return Double.longBitsToDouble(bits.get()); + } + + public void set(double value) + { + bits.set(Double.doubleToLongBits(value)); + } + + public double getAndUpdate(DoubleUnaryOperator func) + { + return Double.longBitsToDouble(bits.getAndUpdate(l -> Double.doubleToLongBits(func.applyAsDouble(Double.longBitsToDouble(l))))); + } + +} diff --git a/src/main/java/io/github/archipelagomw/utils/IntEnum.java b/src/main/java/io/github/archipelagomw/utils/IntEnum.java new file mode 100644 index 0000000..19fc8ea --- /dev/null +++ b/src/main/java/io/github/archipelagomw/utils/IntEnum.java @@ -0,0 +1,6 @@ +package io.github.archipelagomw.utils; + +public interface IntEnum { + + public int getValue(); +} diff --git a/src/main/java/io/github/archipelagomw/utils/IntEnumAdapterFactory.java b/src/main/java/io/github/archipelagomw/utils/IntEnumAdapterFactory.java new file mode 100644 index 0000000..6b6b0c8 --- /dev/null +++ b/src/main/java/io/github/archipelagomw/utils/IntEnumAdapterFactory.java @@ -0,0 +1,60 @@ +package io.github.archipelagomw.utils; + +import com.google.gson.Gson; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import io.github.archipelagomw.network.client.HintStatus; + +import java.io.IOException; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Map; +import java.util.function.Function; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +public class IntEnumAdapterFactory implements TypeAdapterFactory { + private static final Logger logger = Logger.getLogger(IntEnumAdapterFactory.class.getCanonicalName()); + + @Override + public TypeAdapter create(Gson gson, TypeToken typeToken) { + + if(IntEnum.class.isAssignableFrom(typeToken.getRawType()) && typeToken.getRawType().isEnum()) + { + try { + return new IntEnumAdapter(typeToken.getRawType()); + } + catch(ReflectiveOperationException ex) + { + logger.log(Level.WARNING, "Error while initializing int enum adapter", ex); + } + } + return null; + } + + private static class IntEnumAdapter & IntEnum> extends TypeAdapter + { + + private final Map lookupMap; + + public IntEnumAdapter(Class clazz) throws ReflectiveOperationException { + Method m = HintStatus.class.getMethod("values"); + T[] values = (T[]) m.invoke(null); + lookupMap = Arrays.stream(values).collect(Collectors.toMap(IntEnum::getValue, Function.identity())); + } + + @Override + public void write(JsonWriter jsonWriter, T intEnum) throws IOException { + jsonWriter.value(intEnum.getValue()); + } + + @Override + public T read(JsonReader jsonReader) throws IOException { + return lookupMap.get(jsonReader.nextInt()); + } + }; +} diff --git a/src/test/java/io/github/archipelagomw/UtilsTest.java b/src/test/java/io/github/archipelagomw/UtilsTest.java new file mode 100644 index 0000000..7754924 --- /dev/null +++ b/src/test/java/io/github/archipelagomw/UtilsTest.java @@ -0,0 +1,20 @@ +package io.github.archipelagomw; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +public class UtilsTest { + + @Test + public void textIsSafe() + { + assertEquals("Slay the Spire", Utils.getFileSafeName("Slay the Spire")); + } + + @Test + public void textIsBad() + { + + assertEquals("Slay the Spire", Utils.getFileSafeName("Slay <>\"/?\\|:*the Spire")); + } +}